diff --git a/BUILD.gn b/BUILD.gn
index 801a0a0..932a881 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -42,6 +42,12 @@
   root_extra_deps = []
 }
 
+# Ninja's console pool
+# https://ninja-build.org/manual.html#_the_literal_console_literal_pool
+pool("console") {
+  depth = 1
+}
+
 if (is_official_build) {
   # An official (maximally optimized!) component (optimized for build times)
   # build doesn't make sense and usually doesn't work.
diff --git a/DEPS b/DEPS
index 57d4b75..e39eecf 100644
--- a/DEPS
+++ b/DEPS
@@ -194,11 +194,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': '3b5a4fac1e5e5c15af2adc56aa7c19d6f6043274',
+  'skia_revision': 'f4f8011aef1912dd54dc703d209b2a4a4d72fe69',
   # 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': '38ef299a11b6e592fe59429ff70efc3d4e1720c4',
+  'v8_revision': '91280009b2d14b754859306d41de232b431517ea',
   # 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.
@@ -206,15 +206,15 @@
   # 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': '92ffd40efaacc78671f409fa7611ebdec22de2bb',
+  'angle_revision': '5e7b1c4126934ab8135a93ddc18dc0dc5fe3b501',
   # 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': '522b34c810c51048102f4f667ecc2d895760a6e6',
+  'swiftshader_revision': 'cd0af6456eb1c427943c2cf60d4f6234f821aa09',
   # 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': 'f28c13ed2325d349db632e9a8f19c4e9696f63ae',
+  'pdfium_revision': 'a61a55a444b3bff3047de4bcc495d1cf92c76d93',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -245,7 +245,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
-  'freetype_revision': 'c922ffa5d2fe359d5e0d788f3a0850a59da4ae20',
+  'freetype_revision': '5fe7c044c25bba9dfae315ef56bacfc83976ddd0',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling HarfBuzz
   # and whatever else without interference from each other.
@@ -257,7 +257,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': 'c4187c19b8f83d2f71e63bfa00b8ffba90691517',
+  'catapult_revision': '49d9f039e26bc6da5267ff4afe44a19db8b2a44a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -265,7 +265,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': '5f4c6e5b4c8917338765d92f210206e26c1eb7b8',
+  'devtools_frontend_revision': '6ec6dea4ea199dcb3ba3ff160d9f33d35c24a226',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -313,11 +313,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'shaderc_revision': '15f98cdafa047cae9c965fd2bad032a8deb9a586',
+  'shaderc_revision': '5515d2a0c20cb73e68190b9a39445d4bea4eeeab',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': 'b9285f69ba2c7a46eeb308a51689786deaf9ef1b',
+  'dawn_revision': '4e2d7cff8fc304aa9cd7cb6476c7ff72f289ed95',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -541,7 +541,7 @@
   },
 
   'src/ios/third_party/material_components_ios/src': {
-      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '98f43faf56fd87f1c07896e22d07604ccc550d2f',
+      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '3117c254c4bd8fc6b681b10334ef0882a9461ff2',
       'condition': 'checkout_ios',
   },
 
@@ -883,12 +883,12 @@
 
   # For Linux and Chromium OS.
   'src/third_party/cros_system_api': {
-      'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + '3161ecee21c5b135bdb7283091ce70d61dc8d3e8',
+      'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + '6b977833f062d1a365ed28352c0c8a42e272e8a3',
       'condition': 'checkout_linux',
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '271a78ac35d784bf93b48831ad91c8d566e67bde',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '68978076fdb7c01737d097fea0763f9745463b1d',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -939,7 +939,7 @@
     Var('chromium_git') + '/codecs/libgav1.git' + '@' + 'ba8dd2919fcaf65646858a6d7fd5e75ed4946cb1',
 
   'src/third_party/glslang/src':
-    Var('chromium_git') + '/external/github.com/KhronosGroup/glslang.git' + '@' + 'f5add0b20d2a58957164fe48fefd63d94de44ea9',
+    Var('chromium_git') + '/external/github.com/KhronosGroup/glslang.git' + '@' + 'b481744aea1ecf52ee4591afaa0f5e270b9d1636',
 
   'src/third_party/google_toolbox_for_mac/src': {
       'url': Var('chromium_git') + '/external/github.com/google/google-toolbox-for-mac.git' + '@' + Var('google_toolbox_for_mac_revision'),
@@ -1241,7 +1241,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '85cf057d6c800c6722161c2e190ee082f0262adb',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'c73e6969fbeb24194933c0a63cf6df7c5977f9bc',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1319,7 +1319,7 @@
       'packages': [
           {
               'package': 'fuchsia/third_party/aemu/linux-amd64',
-              'version': '6bZN4TqtfLba0SiJItNPHZ3i_ngPcqPGRjBKVYDRdkIC'
+              'version': 'i8aupVebW6WFklh4dyIYfS5KFWtblb7-t9-Bx4m-3LIC'
           },
       ],
       'condition': 'host_os == "linux" and checkout_fuchsia',
@@ -1541,7 +1541,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@ecc9f3494ee98ccd39f00a424a7412c0acaa77cb',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@4450958a3152667d1aabf31a1241d54405d39be4',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 8d6fd97..6b6ca04 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -758,9 +758,11 @@
         'std::shared_ptr should not be used. Use scoped_refptr instead.',
       ),
       True,
-      [_THIRD_PARTY_EXCEPT_BLINK,
-       '^third_party/blink/renderer/core/typed_arrays/array_buffer/' +
-         'array_buffer_contents\.(cc|h)'],
+      ['^third_party/blink/renderer/core/typed_arrays/array_buffer/' +
+         'array_buffer_contents\.(cc|h)',
+       # Needed for interop with third-party library
+       'chrome/services/sharing/nearby/',
+       _THIRD_PARTY_EXCEPT_BLINK],  # Not an error in third_party folders.
     ),
     (
       r'/\bstd::weak_ptr\b',
diff --git a/android_webview/browser/aw_content_browser_client.cc b/android_webview/browser/aw_content_browser_client.cc
index 14441b2..aed6ddf 100644
--- a/android_webview/browser/aw_content_browser_client.cc
+++ b/android_webview/browser/aw_content_browser_client.cc
@@ -888,11 +888,11 @@
   return false;
 }
 
-bool AwContentBrowserClient::ShouldLockToOrigin(
+bool AwContentBrowserClient::ShouldLockProcess(
     content::BrowserContext* browser_context,
     const GURL& effective_url) {
   // TODO(lukasza): https://crbug.cmo/869494: Once Android WebView supports
-  // OOPIFs, we should remove this ShouldLockToOrigin overload.  Till then,
+  // OOPIFs, we should remove this ShouldLockProcess overload.  Till then,
   // returning false helps avoid accidentally applying citadel-style Site
   // Isolation enforcement to Android WebView (and causing incorrect renderer
   // kills).
diff --git a/android_webview/browser/aw_content_browser_client.h b/android_webview/browser/aw_content_browser_client.h
index e37b109..eeab941 100644
--- a/android_webview/browser/aw_content_browser_client.h
+++ b/android_webview/browser/aw_content_browser_client.h
@@ -190,8 +190,8 @@
       NonNetworkURLLoaderFactoryMap* factories) override;
   bool ShouldIsolateErrorPage(bool in_main_frame) override;
   bool ShouldEnableStrictSiteIsolation() override;
-  bool ShouldLockToOrigin(content::BrowserContext* browser_context,
-                          const GURL& effective_url) override;
+  bool ShouldLockProcess(content::BrowserContext* browser_context,
+                         const GURL& effective_url) override;
   bool WillCreateURLLoaderFactory(
       content::BrowserContext* browser_context,
       content::RenderFrameHost* frame,
diff --git a/android_webview/browser/aw_render_process.cc b/android_webview/browser/aw_render_process.cc
index 2a1ac8ba..bcfecc3 100644
--- a/android_webview/browser/aw_render_process.cc
+++ b/android_webview/browser/aw_render_process.cc
@@ -84,7 +84,7 @@
     const base::android::JavaParamRef<jobject>& obj) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-  return render_process_host_->IsLockedToOriginForTesting();
+  return render_process_host_->IsProcessLockedForTesting();
 }
 
 base::android::ScopedJavaLocalRef<jobject> AwRenderProcess::GetJavaObject() {
diff --git a/android_webview/browser/cookie_manager.cc b/android_webview/browser/cookie_manager.cc
index e56d1b06..381c3703 100644
--- a/android_webview/browser/cookie_manager.cc
+++ b/android_webview/browser/cookie_manager.cc
@@ -228,14 +228,9 @@
       GetPathInAppDirectory("Default/Cookies-journal");
 
   if (base::PathExists(old_cookie_store_path)) {
+    base::CreateDirectory(cookie_store_path_.DirName());
     base::Move(old_cookie_store_path, cookie_store_path_);
     base::Move(old_cookie_journal_path, new_cookie_journal_path);
-  } else {
-    // Some users got an incomplete version of this migration where the journal
-    // was not moved. Delete the old journal if it exists, as we can't merge
-    // them.
-    // TODO(torne): remove this in a future release (M81?)
-    base::DeleteFile(old_cookie_journal_path, false);
   }
 }
 
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContents.java b/android_webview/java/src/org/chromium/android_webview/AwContents.java
index 4dcc0aa..845e488 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwContents.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwContents.java
@@ -1413,13 +1413,15 @@
     @CalledByNative
     private void onRendererResponsive(AwRenderProcess renderProcess) {
         if (isDestroyed(NO_WARN)) return;
-        mContentsClient.onRendererResponsive(renderProcess);
+        AwThreadUtils.postToCurrentLooper(
+                () -> mContentsClient.onRendererResponsive(renderProcess));
     }
 
     @CalledByNative
     private void onRendererUnresponsive(AwRenderProcess renderProcess) {
         if (isDestroyed(NO_WARN)) return;
-        mContentsClient.onRendererUnresponsive(renderProcess);
+        AwThreadUtils.postToCurrentLooper(
+                () -> mContentsClient.onRendererUnresponsive(renderProcess));
     }
 
     @VisibleForTesting
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientFullScreenTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientFullScreenTest.java
index 6ce642c1..28a5ec9 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientFullScreenTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientFullScreenTest.java
@@ -284,6 +284,7 @@
     @MediumTest
     @Feature({"AndroidWebView"})
     @Test
+    @DisabledTest(message = "Flaky - https://crbug.com/1105220")
     public void testPowerSaveBlockerIsTransferredToFullscreen() throws Throwable {
         Assert.assertFalse(DOMUtils.isFullscreen(getWebContentsOnUiThread()));
         loadTestPage(VIDEO_INSIDE_DIV_TEST_URL);
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 7a81068..590c8710 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -1865,7 +1865,9 @@
     "assistant/model/assistant_query_history_unittest.cc",
     "assistant/ui/assistant_web_container_view_unittest.cc",
     "assistant/ui/main_stage/assistant_onboarding_view_unittest.cc",
+    "assistant/ui/main_stage/suggestion_chip_view_unittest.cc",
     "assistant/util/deep_link_util_unittest.cc",
+    "assistant/util/resource_util_unittest.cc",
     "autoclick/autoclick_drag_event_rewriter_unittest.cc",
     "autoclick/autoclick_unittest.cc",
     "clipboard/clipboard_history_unittest.cc",
diff --git a/ash/app_list/views/expand_arrow_view.cc b/ash/app_list/views/expand_arrow_view.cc
index c5be46d..4231238 100644
--- a/ash/app_list/views/expand_arrow_view.cc
+++ b/ash/app_list/views/expand_arrow_view.cc
@@ -4,6 +4,7 @@
 
 #include "ash/app_list/views/expand_arrow_view.h"
 
+#include <algorithm>
 #include <memory>
 #include <utility>
 
@@ -14,6 +15,7 @@
 #include "ash/public/cpp/app_list/vector_icons/vector_icons.h"
 #include "base/bind.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/numerics/safe_conversions.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/animation/slide_animation.h"
 #include "ui/gfx/canvas.h"
@@ -333,7 +335,7 @@
   }
 
   // Update pulse radius.
-  pulse_radius_ = static_cast<int>(
+  pulse_radius_ = base::Round(
       (kPulseMaxRadius - kPulseMinRadius) *
       gfx::Tween::CalculateValue(
           gfx::Tween::EASE_IN_OUT,
@@ -349,14 +351,14 @@
     const double progress =
         (time - kArrowMoveOutBeginTime).InMillisecondsF() /
         (kArrowMoveOutEndTime - kArrowMoveOutBeginTime).InMillisecondsF();
-    arrow_y_offset_ = static_cast<int>(
-        -kTotalArrowYOffset *
-        gfx::Tween::CalculateValue(gfx::Tween::EASE_IN, progress));
+    arrow_y_offset_ =
+        base::Round(-kTotalArrowYOffset *
+                    gfx::Tween::CalculateValue(gfx::Tween::EASE_IN, progress));
   } else if (time > kArrowMoveInBeginTime && time <= kArrowMoveInEndTime) {
     const double progress =
         (time - kArrowMoveInBeginTime).InMillisecondsF() /
         (kArrowMoveInEndTime - kArrowMoveInBeginTime).InMillisecondsF();
-    arrow_y_offset_ = static_cast<int>(
+    arrow_y_offset_ = base::Round(
         kTotalArrowYOffset *
         (1 - gfx::Tween::CalculateValue(gfx::Tween::EASE_OUT, progress)));
   }
diff --git a/ash/app_list/views/privacy_info_view.cc b/ash/app_list/views/privacy_info_view.cc
index 85219bf..d12233a 100644
--- a/ash/app_list/views/privacy_info_view.cc
+++ b/ash/app_list/views/privacy_info_view.cc
@@ -129,8 +129,6 @@
       l10n_util::GetStringFUTF16(info_string_id_, link, &offset);
   auto text_view = std::make_unique<views::StyledLabel>(text, this);
   views::StyledLabel::RangeStyleInfo style;
-  style.custom_font = text_view->GetDefaultFontList().Derive(
-      0, gfx::Font::FontStyle::NORMAL, gfx::Font::Weight::NORMAL);
   style.override_color = gfx::kGoogleGrey900;
   text_view->AddStyleRange(gfx::Range(0, offset), style);
 
diff --git a/ash/app_list/views/search_result_answer_card_view.cc b/ash/app_list/views/search_result_answer_card_view.cc
index 0f69745..93255399 100644
--- a/ash/app_list/views/search_result_answer_card_view.cc
+++ b/ash/app_list/views/search_result_answer_card_view.cc
@@ -338,10 +338,6 @@
   return "SearchResultAnswerCardView";
 }
 
-int SearchResultAnswerCardView::GetYSize() {
-  return num_results();
-}
-
 int SearchResultAnswerCardView::DoUpdate() {
   std::vector<SearchResult*> display_results =
       SearchModel::FilterSearchResultsByDisplayType(
diff --git a/ash/app_list/views/search_result_answer_card_view.h b/ash/app_list/views/search_result_answer_card_view.h
index 55a4d7a..92a23e8 100644
--- a/ash/app_list/views/search_result_answer_card_view.h
+++ b/ash/app_list/views/search_result_answer_card_view.h
@@ -25,7 +25,6 @@
   const char* GetClassName() const override;
 
   // Overridden from SearchResultContainerView:
-  int GetYSize() override;
   int DoUpdate() override;
   bool OnKeyPressed(const ui::KeyEvent& event) override;
   SearchResultBaseView* GetFirstResultView() override;
diff --git a/ash/app_list/views/search_result_answer_card_view_unittest.cc b/ash/app_list/views/search_result_answer_card_view_unittest.cc
index 64e15dc..1503cc2 100644
--- a/ash/app_list/views/search_result_answer_card_view_unittest.cc
+++ b/ash/app_list/views/search_result_answer_card_view_unittest.cc
@@ -90,8 +90,6 @@
 
   views::View* search_card_view() const { return search_card_view_.get(); }
 
-  int GetYSize() const { return result_container_view_->GetYSize(); }
-
   int GetResultCountFromView() { return result_container_view_->num_results(); }
 
   double GetContainerScore() const {
@@ -125,7 +123,6 @@
   EXPECT_EQ(kDisplayScore, GetContainerScore());
   EXPECT_EQ(1, GetResultCountFromView());
   ASSERT_TRUE(search_card_view()->GetVisible());
-  EXPECT_EQ(1, GetYSize());
 }
 
 TEST_F(SearchResultAnswerCardViewTest, OpenResult) {
@@ -144,7 +141,6 @@
 TEST_F(SearchResultAnswerCardViewTest, DeleteResult) {
   DeleteResult();
   EXPECT_EQ(0UL, GetResults()->item_count());
-  EXPECT_EQ(0, GetYSize());
   ASSERT_FALSE(search_card_view()->GetVisible());
   EXPECT_EQ(-1, GetContainerScore());
 }
diff --git a/ash/app_list/views/search_result_container_view.cc b/ash/app_list/views/search_result_container_view.cc
index e625a16..b6929e8 100644
--- a/ash/app_list/views/search_result_container_view.cc
+++ b/ash/app_list/views/search_result_container_view.cc
@@ -34,11 +34,6 @@
   Update();
 }
 
-int SearchResultContainerView::GetYSize() {
-  NOTREACHED();
-  return 0;
-}
-
 void SearchResultContainerView::Update() {
   update_factory_.InvalidateWeakPtrs();
   num_results_ = DoUpdate();
diff --git a/ash/app_list/views/search_result_container_view.h b/ash/app_list/views/search_result_container_view.h
index 6bc7f67..0ae4315 100644
--- a/ash/app_list/views/search_result_container_view.h
+++ b/ash/app_list/views/search_result_container_view.h
@@ -62,10 +62,6 @@
   void set_container_score(double score) { container_score_ = score; }
   double container_score() const { return container_score_; }
 
-  // Gets the number of down keystrokes from the beginning to the end of this
-  // container.
-  virtual int GetYSize();
-
   // Batching method that actually performs the update and updates layout.
   void Update();
 
diff --git a/ash/app_list/views/search_result_list_view.cc b/ash/app_list/views/search_result_list_view.cc
index 5c7c014..d4160613 100644
--- a/ash/app_list/views/search_result_list_view.cc
+++ b/ash/app_list/views/search_result_list_view.cc
@@ -184,10 +184,6 @@
   return search_result_views_[index];
 }
 
-int SearchResultListView::GetYSize() {
-  return num_results();
-}
-
 SearchResultBaseView* SearchResultListView::GetFirstResultView() {
   DCHECK(!results_container_->children().empty());
   return num_results() <= 0 ? nullptr : search_result_views_[0];
diff --git a/ash/app_list/views/search_result_list_view.h b/ash/app_list/views/search_result_list_view.h
index 1a347ed..2ed6df7 100644
--- a/ash/app_list/views/search_result_list_view.h
+++ b/ash/app_list/views/search_result_list_view.h
@@ -50,7 +50,6 @@
 
   // Overridden from SearchResultContainerView:
   SearchResultView* GetResultViewAt(size_t index) override;
-  int GetYSize() override;
   SearchResultBaseView* GetFirstResultView() override;
 
   AppListMainView* app_list_main_view() const { return main_view_; }
diff --git a/ash/app_list/views/search_result_tile_item_list_view.cc b/ash/app_list/views/search_result_tile_item_list_view.cc
index 36e21509..3086703 100644
--- a/ash/app_list/views/search_result_tile_item_list_view.cc
+++ b/ash/app_list/views/search_result_tile_item_list_view.cc
@@ -122,10 +122,6 @@
   return tile_views_[index];
 }
 
-int SearchResultTileItemListView::GetYSize() {
-  return num_results() ? 1 : 0;
-}
-
 SearchResultBaseView* SearchResultTileItemListView::GetFirstResultView() {
   DCHECK(!tile_views_.empty());
   return num_results() <= 0 ? nullptr : tile_views_[0];
diff --git a/ash/app_list/views/search_result_tile_item_list_view.h b/ash/app_list/views/search_result_tile_item_list_view.h
index 2ecea17..ab26802 100644
--- a/ash/app_list/views/search_result_tile_item_list_view.h
+++ b/ash/app_list/views/search_result_tile_item_list_view.h
@@ -32,7 +32,6 @@
 
   // Overridden from SearchResultContainerView:
   SearchResultTileItemView* GetResultViewAt(size_t index) override;
-  int GetYSize() override;
   SearchResultBaseView* GetFirstResultView() override;
 
   // Overridden from views::View:
diff --git a/ash/assistant/ui/main_stage/assistant_onboarding_view.cc b/ash/assistant/ui/main_stage/assistant_onboarding_view.cc
index 12c43ee..7585fe9 100644
--- a/ash/assistant/ui/main_stage/assistant_onboarding_view.cc
+++ b/ash/assistant/ui/main_stage/assistant_onboarding_view.cc
@@ -14,6 +14,7 @@
 #include "ash/assistant/ui/assistant_ui_constants.h"
 #include "ash/assistant/ui/assistant_view_delegate.h"
 #include "ash/assistant/ui/assistant_view_ids.h"
+#include "ash/assistant/util/resource_util.h"
 #include "ash/public/cpp/assistant/controller/assistant_suggestions_controller.h"
 #include "ash/public/cpp/assistant/controller/assistant_ui_controller.h"
 #include "ash/strings/grit/ash_strings.h"
@@ -36,6 +37,7 @@
 
 namespace {
 
+using assistant::util::ResourceLinkType;
 using chromeos::assistant::AssistantSuggestion;
 using chromeos::assistant::AssistantSuggestionType;
 
@@ -108,13 +110,13 @@
   return background_colors[index];
 }
 
-SkColor GetSuggestionTextColor(int index) {
+SkColor GetSuggestionForegroundColor(int index) {
   DCHECK_GE(index, 0);
   DCHECK_LT(index, kSuggestionsMaxCount);
-  constexpr SkColor text_colors[kSuggestionsMaxCount] = {
+  constexpr SkColor foreground_colors[kSuggestionsMaxCount] = {
       gfx::kGoogleBlue700,   gfx::kGoogleYellow900, gfx::kGoogleGreen800,
       gfx::kGoogleYellow900, gfx::kGoogleGreen800,  gfx::kGoogleRed800};
-  return text_colors[index];
+  return foreground_colors[index];
 }
 
 // SuggestionView --------------------------------------------------------------
@@ -171,8 +173,16 @@
     icon_->SetImageSize({kSuggestionsIconSizeDip, kSuggestionsIconSizeDip});
     icon_->SetPreferredSize({kSuggestionsIconSizeDip, kSuggestionsIconSizeDip});
 
-    if (suggestion.icon_url.is_valid()) {
-      delegate_->DownloadImage(suggestion.icon_url,
+    const GURL& url = suggestion.icon_url;
+    if (assistant::util::IsResourceLinkType(url, ResourceLinkType::kIcon)) {
+      // Handle local images.
+      icon_->SetImage(assistant::util::CreateVectorIcon(
+          assistant::util::AppendOrReplaceColorParam(
+              url, GetSuggestionForegroundColor(index_)),
+          kSuggestionsIconSizeDip));
+    } else if (url.is_valid()) {
+      // Handle remote images.
+      delegate_->DownloadImage(url,
                                base::BindOnce(&SuggestionView::OnIconDownloaded,
                                               weak_factory_.GetWeakPtr()));
     }
@@ -180,7 +190,7 @@
     // Label.
     label_ = AddChildView(std::make_unique<views::Label>());
     label_->SetAutoColorReadabilityEnabled(false);
-    label_->SetEnabledColor(GetSuggestionTextColor(index_));
+    label_->SetEnabledColor(GetSuggestionForegroundColor(index_));
     label_->SetFontList(assistant::ui::GetDefaultFontList().DeriveWithSizeDelta(
         kSuggestionsLabelSizeDelta));
     label_->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
diff --git a/ash/assistant/ui/main_stage/assistant_onboarding_view_unittest.cc b/ash/assistant/ui/main_stage/assistant_onboarding_view_unittest.cc
index 855546a..2ec794e 100644
--- a/ash/assistant/ui/main_stage/assistant_onboarding_view_unittest.cc
+++ b/ash/assistant/ui/main_stage/assistant_onboarding_view_unittest.cc
@@ -5,6 +5,7 @@
 #include "ash/assistant/ui/main_stage/assistant_onboarding_view.h"
 
 #include <memory>
+#include <queue>
 #include <string>
 #include <utility>
 #include <vector>
@@ -12,10 +13,13 @@
 #include "ash/assistant/model/assistant_suggestions_model.h"
 #include "ash/assistant/model/assistant_ui_model.h"
 #include "ash/assistant/test/assistant_ash_test_base.h"
+#include "ash/assistant/ui/test_support/mock_assistant_view_delegate.h"
+#include "ash/assistant/util/test_support/macros.h"
 #include "ash/public/cpp/assistant/controller/assistant_suggestions_controller.h"
 #include "ash/public/cpp/assistant/controller/assistant_ui_controller.h"
 #include "ash/public/cpp/session/session_types.h"
 #include "ash/public/cpp/session/user_info.h"
+#include "ash/public/cpp/vector_icons/vector_icons.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "base/strings/stringprintf.h"
@@ -27,6 +31,9 @@
 #include "chromeos/services/assistant/public/cpp/features.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/image/image_unittest_util.h"
+#include "ui/gfx/paint_vector_icon.h"
+#include "ui/views/controls/image_view.h"
 #include "ui/views/controls/label.h"
 
 namespace ash {
@@ -42,12 +49,27 @@
 
 // Helpers ---------------------------------------------------------------------
 
+AssistantSuggestion CreateSuggestionWithIconUrl(const std::string& icon_url) {
+  AssistantSuggestion suggestion;
+  suggestion.icon_url = GURL(icon_url);
+  return suggestion;
+}
+
 template <typename T>
-void FindChildByClassName(views::View* parent, T** result) {
+void FindDescendentByClassName(views::View* parent, T** result) {
   DCHECK_EQ(nullptr, *result);
-  for (auto* child : parent->children()) {
-    if (child->GetClassName() == T::kViewClassName)
-      *result = static_cast<T*>(child);
+  std::queue<views::View*> children({parent});
+  while (!children.empty()) {
+    auto* candidate = children.front();
+    children.pop();
+
+    if (candidate->GetClassName() == T::kViewClassName) {
+      *result = static_cast<T*>(candidate);
+      return;
+    }
+
+    for (auto* child : candidate->children())
+      children.push(child);
   }
 }
 
@@ -275,7 +297,7 @@
     // Verify that each suggestion view has the expected message.
     for (size_t i = 0; i < expected_messages.size(); ++i) {
       views::Label* label = nullptr;
-      FindChildByClassName(suggestions_grid()->children().at(i), &label);
+      FindDescendentByClassName(suggestions_grid()->children().at(i), &label);
       ASSERT_NE(label, nullptr);
       EXPECT_EQ(label->GetText(), base::UTF8ToUTF16(expected_messages.at(i)));
     }
@@ -319,9 +341,55 @@
   // Verify view state is updated to reflect model state.
   ASSERT_EQ(suggestions_grid()->children().size(), 1u);
   views::Label* label = nullptr;
-  FindChildByClassName(suggestions_grid()->children().at(0), &label);
+  FindDescendentByClassName(suggestions_grid()->children().at(0), &label);
   ASSERT_NE(nullptr, label);
   EXPECT_EQ(label->GetText(), base::UTF8ToUTF16("Forced suggestion"));
 }
 
+TEST_F(AssistantOnboardingViewTest, ShouldHandleLocalIcons) {
+  MockAssistantViewDelegate delegate;
+  EXPECT_CALL(delegate, GetPrimaryUserGivenName)
+      .WillOnce(testing::Return("Primary User Given Name"));
+
+  AssistantOnboardingView onboarding_view(&delegate);
+  SetOnboardingSuggestions({CreateSuggestionWithIconUrl(
+      "googleassistant://resource?type=icon&name=assistant")});
+
+  views::ImageView* icon_view = nullptr;
+  FindDescendentByClassName(&onboarding_view, &icon_view);
+  ASSERT_NE(nullptr, icon_view);
+
+  const auto& actual = icon_view->GetImage();
+  gfx::ImageSkia expected = gfx::CreateVectorIcon(
+      gfx::IconDescription(ash::kAssistantIcon, /*size=*/24));
+
+  ASSERT_PIXELS_EQ(actual, expected);
+}
+
+TEST_F(AssistantOnboardingViewTest, ShouldHandleRemoteIcons) {
+  const gfx::ImageSkia expected =
+      gfx::test::CreateImageSkia(/*width=*/10, /*height=*/10);
+
+  MockAssistantViewDelegate delegate;
+  EXPECT_CALL(delegate, GetPrimaryUserGivenName)
+      .WillOnce(testing::Return("Primary User Given Name"));
+
+  AssistantOnboardingView onboarding_view(&delegate);
+  EXPECT_CALL(delegate, DownloadImage)
+      .WillOnce(testing::Invoke(
+          [&](const GURL& url, ImageDownloader::DownloadCallback callback) {
+            std::move(callback).Run(expected);
+          }));
+
+  SetOnboardingSuggestions({CreateSuggestionWithIconUrl(
+      "https://www.gstatic.com/images/branding/product/2x/googleg_48dp.png")});
+
+  views::ImageView* icon_view = nullptr;
+  FindDescendentByClassName(&onboarding_view, &icon_view);
+  ASSERT_NE(nullptr, icon_view);
+
+  const auto& actual = icon_view->GetImage();
+  EXPECT_TRUE(actual.BackedBySameObjectAs(expected));
+}
+
 }  // namespace ash
diff --git a/ash/assistant/ui/main_stage/suggestion_chip_view.cc b/ash/assistant/ui/main_stage/suggestion_chip_view.cc
index 739c91c..11d77cb3 100644
--- a/ash/assistant/ui/main_stage/suggestion_chip_view.cc
+++ b/ash/assistant/ui/main_stage/suggestion_chip_view.cc
@@ -10,6 +10,7 @@
 
 #include "ash/assistant/ui/assistant_ui_constants.h"
 #include "ash/assistant/ui/assistant_view_delegate.h"
+#include "ash/assistant/util/resource_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "ui/accessibility/ax_enums.mojom.h"
 #include "ui/accessibility/ax_node_data.h"
@@ -24,6 +25,8 @@
 
 namespace {
 
+using assistant::util::ResourceLinkType;
+
 // Appearance.
 constexpr SkColor kBackgroundColor = SK_ColorWHITE;
 constexpr SkColor kFocusColor = SkColorSetA(gfx::kGoogleGrey900, 0x14);
@@ -104,15 +107,19 @@
   icon_view_->SetImageSize(gfx::Size(kIconSizeDip, kIconSizeDip));
   icon_view_->SetPreferredSize(gfx::Size(kIconSizeDip, kIconSizeDip));
 
-  // Download our icon if necessary. Note that we *don't* hide the associated
-  // view while an image is being downloaded. This prevents layout jank that
-  // would otherwise occur when the image is finally rendered.
-  if (suggestion.icon_url.is_empty()) {
-    icon_view_->SetVisible(false);
+  const GURL& url = suggestion.icon_url;
+  if (assistant::util::IsResourceLinkType(url, ResourceLinkType::kIcon)) {
+    // Handle local images.
+    icon_view_->SetImage(assistant::util::CreateVectorIcon(url, kIconSizeDip));
+  } else if (!suggestion.icon_url.is_empty()) {
+    // Handle remote images.
+    delegate_->DownloadImage(url, base::BindOnce(&SuggestionChipView::SetIcon,
+                                                 weak_factory_.GetWeakPtr()));
   } else {
-    delegate_->DownloadImage(suggestion.icon_url,
-                             base::BindOnce(&SuggestionChipView::SetIcon,
-                                            weak_factory_.GetWeakPtr()));
+    // Only hide the view if we have neither a local or a remote image. In the
+    // case of a remote image, this prevents layout jank that would otherwise
+    // occur if we updated the view visibility only after the image downloaded.
+    icon_view_->SetVisible(false);
   }
 
   // Text.
@@ -170,6 +177,10 @@
   icon_view_->SetVisible(!icon.isNull());
 }
 
+const gfx::ImageSkia& SuggestionChipView::GetIcon() const {
+  return icon_view_->GetImage();
+}
+
 void SuggestionChipView::SetText(const base::string16& text) {
   text_view_->SetText(text);
 }
diff --git a/ash/assistant/ui/main_stage/suggestion_chip_view.h b/ash/assistant/ui/main_stage/suggestion_chip_view.h
index 41a9faf..6ed20c3 100644
--- a/ash/assistant/ui/main_stage/suggestion_chip_view.h
+++ b/ash/assistant/ui/main_stage/suggestion_chip_view.h
@@ -43,6 +43,7 @@
   bool OnKeyPressed(const ui::KeyEvent& event) override;
 
   void SetIcon(const gfx::ImageSkia& icon);
+  const gfx::ImageSkia& GetIcon() const;
 
   void SetText(const base::string16& text);
   const base::string16& GetText() const;
diff --git a/ash/assistant/ui/main_stage/suggestion_chip_view_unittest.cc b/ash/assistant/ui/main_stage/suggestion_chip_view_unittest.cc
new file mode 100644
index 0000000..32a1a4d
--- /dev/null
+++ b/ash/assistant/ui/main_stage/suggestion_chip_view_unittest.cc
@@ -0,0 +1,72 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/assistant/ui/main_stage/suggestion_chip_view.h"
+
+#include "ash/assistant/ui/test_support/mock_assistant_view_delegate.h"
+#include "ash/assistant/util/test_support/macros.h"
+#include "ash/public/cpp/vector_icons/vector_icons.h"
+#include "ash/test/ash_test_base.h"
+#include "chromeos/services/assistant/public/cpp/assistant_service.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/image/image_unittest_util.h"
+#include "ui/gfx/paint_vector_icon.h"
+
+namespace ash {
+
+namespace {
+
+using chromeos::assistant::AssistantSuggestion;
+
+// Helpers ---------------------------------------------------------------------
+
+AssistantSuggestion CreateSuggestionWithIconUrl(const std::string& icon_url) {
+  AssistantSuggestion suggestion;
+  suggestion.icon_url = GURL(icon_url);
+  return suggestion;
+}
+
+}  // namespace
+
+// Tests -----------------------------------------------------------------------
+
+using SuggestionChipViewTest = AshTestBase;
+
+TEST_F(SuggestionChipViewTest, ShouldHandleLocalIcons) {
+  SuggestionChipView suggestion_chip_view(
+      /*delegate=*/nullptr,
+      CreateSuggestionWithIconUrl(
+          "googleassistant://resource?type=icon&name=assistant"),
+      /*listener=*/nullptr);
+
+  const auto& actual = suggestion_chip_view.GetIcon();
+  gfx::ImageSkia expected = gfx::CreateVectorIcon(
+      gfx::IconDescription(ash::kAssistantIcon, /*size=*/16));
+
+  ASSERT_PIXELS_EQ(actual, expected);
+}
+
+TEST_F(SuggestionChipViewTest, ShouldHandleRemoteIcons) {
+  const gfx::ImageSkia expected =
+      gfx::test::CreateImageSkia(/*width=*/10, /*height=*/10);
+
+  MockAssistantViewDelegate delegate;
+  EXPECT_CALL(delegate, DownloadImage)
+      .WillOnce(testing::Invoke(
+          [&](const GURL& url, ImageDownloader::DownloadCallback callback) {
+            std::move(callback).Run(expected);
+          }));
+
+  SuggestionChipView suggestion_chip_view(
+      &delegate,
+      CreateSuggestionWithIconUrl("https://www.gstatic.com/images/branding/"
+                                  "product/2x/googleg_48dp.png"),
+      /*listener=*/nullptr);
+
+  const auto& actual = suggestion_chip_view.GetIcon();
+  EXPECT_TRUE(actual.BackedBySameObjectAs(expected));
+}
+
+}  // namespace ash
diff --git a/ash/assistant/util/BUILD.gn b/ash/assistant/util/BUILD.gn
index 6e6a212..aa4c133 100644
--- a/ash/assistant/util/BUILD.gn
+++ b/ash/assistant/util/BUILD.gn
@@ -20,10 +20,13 @@
     "histogram_util.h",
     "i18n_util.cc",
     "i18n_util.h",
+    "resource_util.cc",
+    "resource_util.h",
   ]
 
   deps = [
     "//ash/assistant/model",
+    "//ash/public/cpp/vector_icons",
     "//base",
     "//base:i18n",
     "//chromeos/constants",
diff --git a/ash/assistant/util/DEPS b/ash/assistant/util/DEPS
index 40905a7..19d3edc5 100644
--- a/ash/assistant/util/DEPS
+++ b/ash/assistant/util/DEPS
@@ -2,6 +2,7 @@
   "-ash",
   "+ash/assistant/model",
   "+ash/assistant/util",
+  "+ash/public/cpp/vector_icons",
   "+net",
   "+ui/base",
   "+ui/gfx",
diff --git a/ash/assistant/util/resource_util.cc b/ash/assistant/util/resource_util.cc
new file mode 100644
index 0000000..2b8df17
--- /dev/null
+++ b/ash/assistant/util/resource_util.cc
@@ -0,0 +1,186 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/assistant/util/resource_util.h"
+
+#include <sstream>
+#include <string>
+
+#include "ash/public/cpp/vector_icons/vector_icons.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "net/base/url_util.h"
+
+namespace ash {
+namespace assistant {
+namespace util {
+
+namespace {
+
+// Constants -------------------------------------------------------------------
+
+// Prefix for all resource links.
+constexpr char kResourceLinkPrefix[] = "googleassistant://resource";
+
+// Param keys.
+constexpr char kColorParamKey[] = "color";
+constexpr char kNameParamKey[] = "name";
+constexpr char kTypeParamKey[] = "type";
+
+// Resource types.
+constexpr char kIconResourceType[] = "icon";
+
+// Icon names.
+constexpr char kAssistantIconName[] = "assistant";
+
+// Helpers ---------------------------------------------------------------------
+
+SkColor ToColor(const std::string& aarrggbb) {
+  SkColor color = gfx::kPlaceholderColor;
+  std::istringstream hex_stream(aarrggbb);
+  hex_stream >> std::hex >> color;
+  return color;
+}
+
+IconName ToIconName(const std::string& name) {
+  DCHECK_EQ(name, kAssistantIconName);
+  return IconName::kAssistant;
+}
+
+std::string ToString(SkColor color) {
+  std::stringstream hex_stream;
+  hex_stream << std::hex << color;
+  return hex_stream.str();
+}
+
+std::string ToString(IconName name) {
+  switch (name) {
+    case IconName::kAssistant:
+      return kAssistantIconName;
+  }
+  NOTREACHED();
+  return std::string();
+}
+
+std::string ToString(ResourceLinkParam param) {
+  switch (param) {
+    case ResourceLinkParam::kColor:
+      return kColorParamKey;
+    case ResourceLinkParam::kName:
+      return kNameParamKey;
+    case ResourceLinkParam::kType:
+      return kTypeParamKey;
+  }
+  NOTREACHED();
+  return std::string();
+}
+
+std::string ToString(ResourceLinkType type) {
+  switch (type) {
+    case ResourceLinkType::kIcon:
+      return kIconResourceType;
+    case ResourceLinkType::kUnsupported:
+      return std::string();
+  }
+  NOTREACHED();
+  return std::string();
+}
+
+ResourceLinkType ToType(const std::string& type) {
+  if (type == kIconResourceType)
+    return ResourceLinkType::kIcon;
+  return ResourceLinkType::kUnsupported;
+}
+
+const gfx::VectorIcon& ToVectorIcon(IconName name) {
+  switch (name) {
+    case IconName::kAssistant:
+      return ash::kAssistantIcon;
+  }
+  NOTREACHED();
+  return gfx::kNoneIcon;
+}
+
+const gfx::VectorIcon& ToVectorIcon(const std::string& name) {
+  return ToVectorIcon(ToIconName(name));
+}
+
+base::Optional<std::string> GetParam(const GURL& url, ResourceLinkParam param) {
+  if (!url.has_query())
+    return base::nullopt;
+
+  const std::string param_key = ToString(param);
+
+  auto ToString = [&url](const url::Component& component) {
+    if (component.len <= 0)
+      return std::string();
+    return std::string(url.query(), component.begin, component.len);
+  };
+
+  url::Component query(0, url.query().length()), key, value;
+  while (url::ExtractQueryKeyValue(url.query().c_str(), &query, &key, &value)) {
+    if (ToString(key) == param_key)
+      return ToString(value);
+  }
+
+  return base::nullopt;
+}
+
+}  // namespace
+
+// Utilities -------------------------------------------------------------------
+
+GURL AppendOrReplaceColorParam(const GURL& resource_link, SkColor color) {
+  DCHECK(IsResourceLinkType(resource_link, ResourceLinkType::kIcon));
+  return net::AppendOrReplaceQueryParameter(
+      resource_link, ToString(ResourceLinkParam::kColor), ToString(color));
+}
+
+GURL CreateIconResourceLink(IconName name, base::Optional<SkColor> color) {
+  GURL icon_resource_link(kResourceLinkPrefix);
+  icon_resource_link = net::AppendOrReplaceQueryParameter(
+      icon_resource_link, ToString(ResourceLinkParam::kType),
+      ToString(ResourceLinkType::kIcon));
+  icon_resource_link = net::AppendOrReplaceQueryParameter(
+      icon_resource_link, ToString(ResourceLinkParam::kName), ToString(name));
+  if (color.has_value()) {
+    icon_resource_link = net::AppendOrReplaceQueryParameter(
+        icon_resource_link, ToString(ResourceLinkParam::kColor),
+        ToString(color.value()));
+  }
+  return icon_resource_link;
+}
+
+gfx::ImageSkia CreateVectorIcon(const GURL& url, int size) {
+  if (!IsResourceLinkType(url, ResourceLinkType::kIcon))
+    return gfx::ImageSkia();
+
+  const gfx::VectorIcon& icon =
+      ToVectorIcon((GetParam(url, ResourceLinkParam::kName).value_or("")));
+
+  SkColor color = ToColor(GetParam(url, ResourceLinkParam::kColor)
+                              .value_or(ToString(gfx::kPlaceholderColor)));
+
+  return gfx::CreateVectorIcon(gfx::IconDescription(icon, size, color));
+}
+
+ResourceLinkType GetResourceLinkType(const GURL& url) {
+  return IsResourceLinkUrl(url)
+             ? ToType(GetParam(url, ResourceLinkParam::kType).value_or(""))
+             : ResourceLinkType::kUnsupported;
+}
+
+bool IsResourceLinkType(const GURL& url, ResourceLinkType type) {
+  return GetResourceLinkType(url) == type;
+}
+
+bool IsResourceLinkUrl(const GURL& url) {
+  return base::StartsWith(url.spec(), kResourceLinkPrefix,
+                          base::CompareCase::SENSITIVE);
+}
+
+}  // namespace util
+}  // namespace assistant
+}  // namespace ash
diff --git a/ash/assistant/util/resource_util.h b/ash/assistant/util/resource_util.h
new file mode 100644
index 0000000..85dc1b2
--- /dev/null
+++ b/ash/assistant/util/resource_util.h
@@ -0,0 +1,68 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_ASSISTANT_UTIL_RESOURCE_UTIL_H_
+#define ASH_ASSISTANT_UTIL_RESOURCE_UTIL_H_
+
+#include "base/component_export.h"
+#include "base/optional.h"
+#include "ui/gfx/paint_vector_icon.h"
+#include "url/gurl.h"
+
+namespace ash {
+namespace assistant {
+namespace util {
+
+// Enumeration of resource link types.
+enum class ResourceLinkType {
+  kUnsupported,
+  kIcon,
+};
+
+// Enumeration of resource link parameters.
+// Examples of usage in comments. Note that actual Assistant resource links are
+// prefixed w/ "googleassistant"; "ga" is only used here to avoid line wrapping.
+enum class ResourceLinkParam {
+  kColor,  // ga://resource?type=icon&name=assistant&&color=AARRGGBB
+  kName,   // ga://resource?type=icon&name=assistant
+  kType,   // ga://resource?type=icon&name=assistant
+};
+
+// Enumeration of icon names.
+enum class IconName {
+  kAssistant,
+};
+
+// Returns a new resource link, having appended or replaced the color param from
+// the original |resource_link| with |color|.
+COMPONENT_EXPORT(ASSISTANT_UTIL)
+GURL AppendOrReplaceColorParam(const GURL& resource_link, SkColor color);
+
+// Returns a resource link for the specified icon.
+COMPONENT_EXPORT(ASSISTANT_UTIL)
+GURL CreateIconResourceLink(IconName name,
+                            base::Optional<SkColor> color = base::nullopt);
+
+// Returns an ImageSkia for the icon resource link specified by |url|. If |url|
+// is *not* an icon resource link, the return value is null. If |size| is not
+// specified, the vector icon's default size is used.
+COMPONENT_EXPORT(ASSISTANT_UTIL)
+gfx::ImageSkia CreateVectorIcon(const GURL& url, int size = 0);
+
+// Returns the type of the resource link specified by |url|.
+COMPONENT_EXPORT(ASSISTANT_UTIL)
+ResourceLinkType GetResourceLinkType(const GURL& url);
+
+// Returns true if the specified |url| is a resource link of the given |type|.
+COMPONENT_EXPORT(ASSISTANT_UTIL)
+bool IsResourceLinkType(const GURL& url, ResourceLinkType type);
+
+// Returns true if the specified |url| is a resource link, false otherwise.
+COMPONENT_EXPORT(ASSISTANT_UTIL) bool IsResourceLinkUrl(const GURL& url);
+
+}  // namespace util
+}  // namespace assistant
+}  // namespace ash
+
+#endif  // ASH_ASSISTANT_UTIL_RESOURCE_UTIL_H_
diff --git a/ash/assistant/util/resource_util_unittest.cc b/ash/assistant/util/resource_util_unittest.cc
new file mode 100644
index 0000000..edd34199
--- /dev/null
+++ b/ash/assistant/util/resource_util_unittest.cc
@@ -0,0 +1,156 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/assistant/util/resource_util.h"
+
+#include <map>
+#include <string>
+
+#include "ash/assistant/util/test_support/macros.h"
+#include "ash/public/cpp/vector_icons/vector_icons.h"
+#include "net/base/url_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/canvas.h"
+#include "url/gurl.h"
+
+namespace ash {
+namespace assistant {
+namespace util {
+
+namespace {
+
+// Helpers ---------------------------------------------------------------------
+
+std::string GetParamValue(const GURL& url, const std::string& param_key) {
+  if (!url.has_query())
+    return std::string();
+
+  auto ToString = [&url](const url::Component& component) {
+    if (component.len <= 0)
+      return std::string();
+    return std::string(url.query(), component.begin, component.len);
+  };
+
+  url::Component query(0, url.query().length()), key, value;
+  while (url::ExtractQueryKeyValue(url.query().c_str(), &query, &key, &value)) {
+    if (ToString(key) == param_key)
+      return ToString(value);
+  }
+
+  return std::string();
+}
+
+}  // namespace
+
+// Tests -----------------------------------------------------------------------
+
+using ResourceUtilTest = testing::Test;
+
+TEST_F(ResourceUtilTest, AppendOrReplaceColorParam) {
+  GURL icon_link("googleassistant://resource?type=icon");
+
+  // Test append.
+  icon_link = AppendOrReplaceColorParam(icon_link, SK_ColorBLACK);
+  EXPECT_EQ(GetParamValue(icon_link, "color"), "ff000000");
+
+  // Test replace.
+  icon_link = AppendOrReplaceColorParam(icon_link, SK_ColorWHITE);
+  EXPECT_EQ(GetParamValue(icon_link, "color"), "ffffffff");
+}
+
+TEST_F(ResourceUtilTest, CreateIconResourceLink) {
+  // Test w/ color.
+  EXPECT_EQ(CreateIconResourceLink(IconName::kAssistant, SK_ColorBLACK),
+            GURL("googleassistant://"
+                 "resource?type=icon&name=assistant&color=ff000000"));
+  // Test w/o color.
+  EXPECT_EQ(CreateIconResourceLink(IconName::kAssistant),
+            GURL("googleassistant://resource?type=icon&name=assistant"));
+}
+
+TEST_F(ResourceUtilTest, CreateVectorIcon) {
+  std::string url;
+  gfx::ImageSkia actual, expected;
+
+  // Test w/ name.
+  url = "googleassistant://resource?type=icon&name=assistant";
+  actual = CreateVectorIcon(GURL(url));
+  expected = gfx::CreateVectorIcon(gfx::IconDescription(ash::kAssistantIcon));
+  ASSERT_PIXELS_EQ(actual, expected);
+
+  // Test w/ name and size.
+  url = "googleassistant://resource?type=icon&name=assistant";
+  actual = CreateVectorIcon(GURL(url), /*size=*/24);
+  expected = gfx::CreateVectorIcon(
+      gfx::IconDescription(ash::kAssistantIcon, /*size=*/24));
+  ASSERT_PIXELS_EQ(actual, expected);
+
+  // Test w/ name, size, and color.
+  url = "googleassistant://resource?type=icon&name=assistant&color=ff000000";
+  actual = CreateVectorIcon(GURL(url), /*size=*/24);
+  expected = gfx::CreateVectorIcon(gfx::IconDescription(
+      ash::kAssistantIcon, /*size=*/24, /*color=*/SK_ColorBLACK));
+  ASSERT_PIXELS_EQ(actual, expected);
+
+  // Test w/o icon resource link.
+  actual = CreateVectorIcon(GURL("googleassistant://resource"));
+  EXPECT_TRUE(actual.isNull());
+
+  // Test w/o resource link.
+  actual = CreateVectorIcon(GURL("https://g.co/"));
+  EXPECT_TRUE(actual.isNull());
+
+  // Test w/ empty.
+  actual = CreateVectorIcon(GURL());
+  EXPECT_TRUE(actual.isNull());
+}
+
+TEST_F(ResourceUtilTest, GetResourceLinkType) {
+  const std::map<std::string, ResourceLinkType> test_cases = {
+      // SUPPORTED:
+      {"googleassistant://resource?type=icon", ResourceLinkType::kIcon},
+
+      // UNSUPPORTED:
+      {"googleassistant://resource", ResourceLinkType::kUnsupported},
+      {"GOOGLEASSISTANT://RESOURCE?TYPE=ICON", ResourceLinkType::kUnsupported},
+      {"https://g.co/", ResourceLinkType::kUnsupported},
+      {"", ResourceLinkType::kUnsupported},
+  };
+  for (const auto& test_case : test_cases)
+    EXPECT_EQ(GetResourceLinkType(GURL(test_case.first)), test_case.second);
+}
+
+TEST_F(ResourceUtilTest, IsResourceLinkType) {
+  const std::map<std::string, ResourceLinkType> test_cases = {
+      // SUPPORTED:
+      {"googleassistant://resource?type=icon", ResourceLinkType::kIcon},
+
+      // UNSUPPORTED:
+      {"googleassistant://resource", ResourceLinkType::kUnsupported},
+      {"GOOGLEASSISTANT://RESOURCE?TYPE=ICON", ResourceLinkType::kUnsupported},
+      {"https://g.co/", ResourceLinkType::kUnsupported},
+      {"", ResourceLinkType::kUnsupported},
+  };
+  for (const auto& test_case : test_cases)
+    EXPECT_TRUE(IsResourceLinkType(GURL(test_case.first), test_case.second));
+}
+
+TEST_F(ResourceUtilTest, IsResourceLinkUrl) {
+  const std::map<std::string, bool> test_cases = {
+      // RESOURCE LINK:
+      {"googleassistant://resource", true},
+      {"googleassistant://resource?type=icon", true},
+
+      // NOT RESOURCE LINK:
+      {"GOOGLEASSISTANT://RESOURCE", false},
+      {"https://g.co/", false},
+      {"", false},
+  };
+  for (const auto& test_case : test_cases)
+    EXPECT_EQ(IsResourceLinkUrl(GURL(test_case.first)), test_case.second);
+}
+
+}  // namespace util
+}  // namespace assistant
+}  // namespace ash
diff --git a/ash/assistant/util/test_support/BUILD.gn b/ash/assistant/util/test_support/BUILD.gn
new file mode 100644
index 0000000..0cea744
--- /dev/null
+++ b/ash/assistant/util/test_support/BUILD.gn
@@ -0,0 +1,11 @@
+# Copyright 2020 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.
+
+static_library("test_support") {
+  testonly = true
+
+  sources = [ "macros.h" ]
+
+  deps = []
+}
diff --git a/ash/assistant/util/test_support/macros.h b/ash/assistant/util/test_support/macros.h
new file mode 100644
index 0000000..d8ba83c2
--- /dev/null
+++ b/ash/assistant/util/test_support/macros.h
@@ -0,0 +1,25 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_ASSISTANT_UTIL_TEST_SUPPORT_MACROS_H_
+#define ASH_ASSISTANT_UTIL_TEST_SUPPORT_MACROS_H_
+
+namespace ash {
+
+// Asserts |img_a_| and |img_b_| pixel equivalence.
+#define ASSERT_PIXELS_EQ(img_a_, img_b_)            \
+  {                                                 \
+    ASSERT_EQ(img_a_.width(), img_b_.width());      \
+    ASSERT_EQ(img_a_.height(), img_b_.height());    \
+    for (int x = 0; x < img_a_.width(); ++x) {      \
+      for (int y = 0; y < img_a_.height(); ++y) {   \
+        ASSERT_EQ(img_a_.bitmap()->getColor(x, y),  \
+                  img_b_.bitmap()->getColor(x, y)); \
+      }                                             \
+    }                                               \
+  }
+
+}  // namespace ash
+
+#endif  // ASH_ASSISTANT_UTIL_TEST_SUPPORT_MACROS_H_
diff --git a/ash/clipboard/OWNERS b/ash/clipboard/OWNERS
index bfc2f0a..0cf2588 100644
--- a/ash/clipboard/OWNERS
+++ b/ash/clipboard/OWNERS
@@ -1,3 +1,3 @@
-newcomer@google.com
+newcomer@chromium.org
 
 # COMPONENT: UI>EnhancedClipboard
\ No newline at end of file
diff --git a/ash/clipboard/clipboard_history_controller.cc b/ash/clipboard/clipboard_history_controller.cc
index f2fa6c1..a32284c 100644
--- a/ash/clipboard/clipboard_history_controller.cc
+++ b/ash/clipboard/clipboard_history_controller.cc
@@ -173,15 +173,12 @@
 void ClipboardHistoryController::ShowMenu() {
   auto* host = ash::GetWindowTreeHostForDisplay(
       display::Screen::GetScreen()->GetPrimaryDisplay().id());
+  // Some web apps render the caret in an IFrame, and we will not get the
+  // bounds in that case.
+  // TODO(https://crbug.com/1099930): Show the menu in the middle of the
+  // webview if the bounds are empty.
   const gfx::Rect textfield_bounds =
       host->GetInputMethod()->GetTextInputClient()->GetCaretBounds();
-  if (textfield_bounds.IsEmpty()) {
-    // Some web apps render the caret in an IFrame, and we will not get the
-    // bounds in that case.
-    // TODO(https://crbug.com/1099930): Show the menu in the middle of the
-    // webview if the bounds are empty.
-    return;
-  }
 
   if (!CanShowMenu())
     return;
diff --git a/ash/display/cros_display_config.cc b/ash/display/cros_display_config.cc
index 698b88b..0d990e9 100644
--- a/ash/display/cros_display_config.cc
+++ b/ash/display/cros_display_config.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "ash/display/display_alignment_controller.h"
 #include "ash/display/display_configuration_controller.h"
 #include "ash/display/display_highlight_controller.h"
 #include "ash/display/display_prefs.h"
@@ -1000,10 +1001,19 @@
   return iter == overscan_calibrators_.end() ? nullptr : iter->second.get();
 }
 
-void CrosDisplayConfig::HighlightDisplay(int64_t id) {
+void CrosDisplayConfig::HighlightDisplay(int64_t display_id) {
   DCHECK(base::FeatureList::IsEnabled(features::kDisplayIdentification));
 
-  Shell::Get()->display_highlight_controller()->SetHighlightedDisplay(id);
+  Shell::Get()->display_highlight_controller()->SetHighlightedDisplay(
+      display_id);
+}
+
+void CrosDisplayConfig::DragDisplayDelta(int64_t display_id,
+                                         int32_t delta_x,
+                                         int32_t delta_y) {
+  DCHECK(features::IsDisplayAlignmentAssistanceEnabled());
+  Shell::Get()->display_alignment_controller()->DisplayDragged(
+      display_id, delta_x, delta_y);
 }
 
 }  // namespace ash
diff --git a/ash/display/cros_display_config.h b/ash/display/cros_display_config.h
index f76959f..fa6698c7c 100644
--- a/ash/display/cros_display_config.h
+++ b/ash/display/cros_display_config.h
@@ -53,7 +53,10 @@
                         mojom::DisplayConfigOperation op,
                         mojom::TouchCalibrationPtr calibration,
                         TouchCalibrationCallback callback) override;
-  void HighlightDisplay(int64_t id) override;
+  void HighlightDisplay(int64_t display_id) override;
+  void DragDisplayDelta(int64_t display_id,
+                        int32_t delta_x,
+                        int32_t delta_y) override;
 
   TouchCalibratorController* touch_calibrator_for_test() {
     return touch_calibrator_.get();
diff --git a/ash/display/cros_display_config_unittest.cc b/ash/display/cros_display_config_unittest.cc
index bbdd1c92..45d185b 100644
--- a/ash/display/cros_display_config_unittest.cc
+++ b/ash/display/cros_display_config_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "ash/display/cros_display_config.h"
 
+#include "ash/display/display_alignment_controller.h"
 #include "ash/display/display_highlight_controller.h"
 #include "ash/display/screen_orientation_controller.h"
 #include "ash/display/touch_calibrator_controller.h"
@@ -81,7 +82,8 @@
   ~CrosDisplayConfigTest() override {}
 
   void SetUp() override {
-    scoped_feature_list_.InitAndEnableFeature(features::kDisplayIdentification);
+    scoped_feature_list_.InitWithFeatures(
+        {features::kDisplayIdentification, features::kDisplayAlignAssist}, {});
 
     base::CommandLine::ForCurrentProcess()->AppendSwitch(
         switches::kUseFirstDisplayAsInternal);
@@ -203,6 +205,17 @@
     cros_display_config_->HighlightDisplay(id);
   }
 
+  void DragDisplayDelta(int64_t id, int32_t delta_x, int32_t delta_y) {
+    cros_display_config_->DragDisplayDelta(id, delta_x, delta_y);
+  }
+
+  bool PreviewIndicatorsExist() {
+    return !Shell::Get()
+                ->display_alignment_controller()
+                ->GetActiveIndicatorsForTesting()
+                .empty();
+  }
+
   CrosDisplayConfig* cros_display_config() { return cros_display_config_; }
 
  private:
@@ -842,4 +855,16 @@
             nullptr);
 }
 
+TEST_F(CrosDisplayConfigTest, DragDisplayDelta) {
+  UpdateDisplay("500x400,500x400");
+
+  const auto& display = display_manager()->GetDisplayAt(0);
+
+  EXPECT_FALSE(PreviewIndicatorsExist());
+
+  DragDisplayDelta(display.id(), 0, 16);
+
+  EXPECT_TRUE(PreviewIndicatorsExist());
+}
+
 }  // namespace ash
diff --git a/ash/display/display_alignment_controller.cc b/ash/display/display_alignment_controller.cc
index 964b3e4..3520a603 100644
--- a/ash/display/display_alignment_controller.cc
+++ b/ash/display/display_alignment_controller.cc
@@ -141,6 +141,28 @@
   RefreshState();
 }
 
+void DisplayAlignmentController::DisplayDragged(int64_t display_id,
+                                                int32_t delta_x,
+                                                int32_t delta_y) {
+  if (current_state_ != DisplayAlignmentState::kLayoutPreview) {
+    // Clear existing indicators. They are all regenerated via
+    // OnDisplayConfigurationChanged() after dragging ends.
+    ResetState();
+
+    dragged_display_id_ = display_id;
+    current_state_ = DisplayAlignmentState::kLayoutPreview;
+  }
+
+  // It is not possible to change display being dragged without dropping it
+  // first (and causing update on display configuration).
+  DCHECK_EQ(dragged_display_id_, display_id);
+  DCHECK_NE(dragged_display_id_, display::kInvalidDisplayId);
+
+  dragged_offset_ += gfx::Vector2d(delta_x, delta_y);
+
+  ComputePreviewIndicators();
+}
+
 void DisplayAlignmentController::SetTimerForTesting(
     std::unique_ptr<base::OneShotTimer> timer) {
   action_trigger_timer_ = std::move(timer);
@@ -151,6 +173,10 @@
   return active_indicators_;
 }
 
+int64_t DisplayAlignmentController::GetDraggedDisplayIdForTesting() const {
+  return dragged_display_id_;
+}
+
 void DisplayAlignmentController::ShowIndicators(
     const display::Display& src_display) {
   DCHECK_EQ(src_display.id(), triggered_display_id_);
@@ -197,6 +223,9 @@
   active_indicators_.clear();
   trigger_count_ = 0;
 
+  dragged_display_id_ = display::kInvalidDisplayId;
+  dragged_offset_ = gfx::Vector2d(0, 0);
+
   // Do not re-enable if disabled.
   if (current_state_ != DisplayAlignmentState::kDisabled)
     current_state_ = DisplayAlignmentState::kIdle;
@@ -223,4 +252,59 @@
     current_state_ = DisplayAlignmentState::kIdle;
 }
 
+void DisplayAlignmentController::ComputePreviewIndicators() {
+  DCHECK_EQ(current_state_, DisplayAlignmentState::kLayoutPreview);
+  DCHECK_NE(dragged_display_id_, display::kInvalidDisplayId);
+
+  const display::Display& dragged_display =
+      Shell::Get()->display_manager()->GetDisplayForId(dragged_display_id_);
+  DCHECK(dragged_display.is_valid());
+
+  gfx::Rect bounds = dragged_display.bounds();
+  bounds += dragged_offset_;
+
+  const display::Displays& display_list =
+      Shell::Get()->display_manager()->active_display_list();
+
+  // Iterate through all the active displays and see if they are neighbors to
+  // |dragged_display|.
+  for (const display::Display& peer : display_list) {
+    // Skip currently dragged display or it might be detected as a neighbor
+    if (peer.id() == dragged_display_id_)
+      continue;
+
+    // True if |source| and |peer| are neighbors. Returns |source_edge| and
+    // |peer_edge| that denotes shared edges between |source| and |peer|
+    // displays.
+    gfx::Rect source_edge;
+    gfx::Rect peer_edge;
+    const bool are_neighbors = display::ComputeBoundary(
+        bounds, peer.bounds(), &source_edge, &peer_edge);
+
+    const auto& existing_indicator_it =
+        std::find_if(active_indicators_.begin(), active_indicators_.end(),
+                     [id = peer.id()](const auto& indicator) {
+                       return id == indicator->display_id();
+                     });
+
+    const bool indicator_exists =
+        existing_indicator_it != active_indicators_.end();
+
+    if (indicator_exists) {
+      if (are_neighbors) {
+        // Displays are already neighbors.
+        (*existing_indicator_it)->Update(peer, peer_edge);
+        (*existing_indicator_it)->Show();
+      } else {
+        // Displays are no longer neighbors but previously were neighbors.
+        (*existing_indicator_it)->Hide();
+      }
+    } else if (are_neighbors) {
+      // Displays are newly-neighbored.
+      active_indicators_.push_back(
+          DisplayAlignmentIndicator::Create(peer, peer_edge));
+    }
+  }
+}
+
 }  // namespace ash
diff --git a/ash/display/display_alignment_controller.h b/ash/display/display_alignment_controller.h
index 83ca1c4..22ba0cc4 100644
--- a/ash/display/display_alignment_controller.h
+++ b/ash/display/display_alignment_controller.h
@@ -12,6 +12,7 @@
 #include "ash/public/cpp/session/session_observer.h"
 #include "base/containers/flat_map.h"
 #include "ui/events/event_handler.h"
+#include "ui/gfx/geometry/vector2d.h"
 
 namespace base {
 class OneShotTimer;
@@ -40,6 +41,10 @@
     // The indicators are visible.
     kIndicatorsVisible,
 
+    // A display is being dragged around in display layouts. Preview indicators
+    // are being updated and shown.
+    kLayoutPreview,
+
     // Screen is locked or there is only one display.
     kDisabled,
   };
@@ -60,12 +65,19 @@
   // SessionObserver:
   void OnLockStateChanged(bool locked) override;
 
-  // Overrides the default OneShotTimer for unit testing.
+  // Update positions of display alignment preview highlights. Display being
+  // dragged is specified by |display_id|. |preview_indicators_| is
+  // populated with indicators from this display and its neighbors as
+  // it is not possible for |display_id| to change mid-drag.
+  void DisplayDragged(int64_t display_id, int32_t delta_x, int32_t delta_y);
+
   void SetTimerForTesting(std::unique_ptr<base::OneShotTimer> timer);
 
   const std::vector<std::unique_ptr<DisplayAlignmentIndicator>>&
   GetActiveIndicatorsForTesting();
 
+  int64_t GetDraggedDisplayIdForTesting() const;
+
  private:
   // Show all indicators on |src_display| and other indicators that shares
   // an edge with |src_display|. Indicators on other displays are shown without
@@ -81,10 +93,15 @@
   // configuration or lock state updates.
   void RefreshState();
 
+  // Updates, shows/hides preview indicators according to changes reported by
+  // DisplayDragged().
+  void ComputePreviewIndicators();
+
   // Stores all DisplayAlignmentIndicators currently being shown. All indicators
   // should either belong to or be a shared edge of display with
   // |triggered_display_id_|. Indicators are created upon activation in
-  // ShowIndicators() and cleared in ResetState().
+  // ShowIndicators() or upon adjusting display layout in
+  // ComputePreviewIndicators() and cleared in ResetState().
   std::vector<std::unique_ptr<DisplayAlignmentIndicator>> active_indicators_;
 
   // Timer used for both edge trigger timeouts and hiding indicators.
@@ -104,6 +121,16 @@
   // Number of times the mouse was on an edge of some display specified by
   // |triggered_display_id_| recently.
   int trigger_count_ = 0;
+
+  // ID of display currently beign dragged. Cannot change from one valid
+  // ID to another as dropping the dragged display causes changes to display
+  // configuration, resetting this ID.
+  int64_t dragged_display_id_;
+
+  // The difference between dragged display's actual position and preview
+  // position. Is an accumulation of |delta_x| and |delta_y| from
+  // DisplayDragged(). The offset is reset when a configuration event occurs.
+  gfx::Vector2d dragged_offset_;
 };
 
 }  // namespace ash
diff --git a/ash/display/display_alignment_controller_unittest.cc b/ash/display/display_alignment_controller_unittest.cc
index ed54ccb..abf7a34 100644
--- a/ash/display/display_alignment_controller_unittest.cc
+++ b/ash/display/display_alignment_controller_unittest.cc
@@ -76,6 +76,10 @@
     display_alignment_controller()->SetTimerForTesting(std::move(mock_timer));
   }
 
+  void DragDisplay(int64_t id, int32_t delta_x, int32_t delta_y) {
+    display_alignment_controller()->DisplayDragged(id, delta_x, delta_y);
+  }
+
   bool NoIndicatorsExist() {
     return display_alignment_controller()
         ->GetActiveIndicatorsForTesting()
@@ -110,6 +114,41 @@
     }
   }
 
+  void CheckPreviewIndicatorShown(int64_t dragged_display_id,
+                                  int64_t target_display_id,
+                                  bool is_visible) {
+    ASSERT_EQ(dragged_display_id,
+              display_alignment_controller()->GetDraggedDisplayIdForTesting());
+
+    const auto& active_indicators_ =
+        display_alignment_controller()->GetActiveIndicatorsForTesting();
+
+    const auto& iter =
+        std::find_if(active_indicators_.begin(), active_indicators_.end(),
+                     [target_display_id](const auto& indicator) {
+                       return target_display_id == indicator->display_id();
+                     });
+
+    if (iter == active_indicators_.end()) {
+      EXPECT_FALSE(is_visible);
+      return;
+    }
+
+    DisplayAlignmentIndicator* indicator = iter->get();
+    ASSERT_TRUE(indicator);
+
+    const views::Widget& indicator_widget = indicator->indicator_widget_;
+
+    if (!is_visible) {
+      EXPECT_FALSE(indicator_widget.IsVisible());
+      return;
+    }
+
+    EXPECT_EQ(Shell::GetRootWindowForDisplayId(target_display_id),
+              indicator_widget.GetNativeWindow()->GetRootWindow());
+    EXPECT_TRUE(indicator_widget.IsVisible());
+  }
+
   base::MockOneShotTimer* mock_timer_ptr_ = nullptr;
 
  private:
@@ -359,4 +398,66 @@
   CheckIndicatorShown(2, primary_display);
 }
 
+TEST_F(DisplayAlignmentControllerTest, DragDisplayBasic) {
+  UpdateDisplay("1920x1080,1366x768");
+  const auto& primary_display = display_manager()->GetDisplayAt(0);
+  const auto& secondary_display = display_manager()->GetDisplayAt(1);
+
+  DragDisplay(primary_display.id(), 0, 0);
+
+  CheckPreviewIndicatorShown(primary_display.id(), secondary_display.id(),
+                             /*is_visible=*/true);
+
+  UpdateDisplay("1920x1080,1366x768");
+  EXPECT_TRUE(NoIndicatorsExist());
+}
+
+// When drag display starts, all existing indicators should be replaced.
+TEST_F(DisplayAlignmentControllerTest, DragDisplayReplaceExistingIndicators) {
+  UpdateDisplay("1920x1080,1366x768");
+
+  const auto& primary_display = display_manager()->GetDisplayAt(0);
+  const auto& secondary_display = display_manager()->GetDisplayAt(1);
+
+  TriggerIndicator(primary_display, EdgeType::kLeft);
+
+  CheckIndicatorShown(2, primary_display);
+
+  DragDisplay(primary_display.id(), 0, 10);
+
+  CheckPreviewIndicatorShown(primary_display.id(), secondary_display.id(),
+                             /*is_visible=*/true);
+}
+
+TEST_F(DisplayAlignmentControllerTest, DragDisplayHideOldNeighbors) {
+  UpdateDisplay("1920x1080,1366x768");
+  const auto& primary_display = display_manager()->GetDisplayAt(0);
+  const auto& secondary_display = display_manager()->GetDisplayAt(1);
+
+  DragDisplay(primary_display.id(), 0, 0);
+
+  // Move the primary display so that the two are no longer neighbors
+  DragDisplay(primary_display.id(), -2000, -2000);
+
+  CheckPreviewIndicatorShown(primary_display.id(), secondary_display.id(),
+                             /*is_visible=*/false);
+}
+
+TEST_F(DisplayAlignmentControllerTest, DragDisplayNewNeighbor) {
+  UpdateDisplay("1000x1000,1000x1000,1000x100");
+  const auto& display_1 = display_manager()->GetDisplayAt(0);
+  const auto& display_2 = display_manager()->GetDisplayAt(1);
+  const auto& display_3 = display_manager()->GetDisplayAt(2);
+
+  DragDisplay(display_1.id(), 0, 0);
+
+  // Move the primary display so that the other two are no longer neighbors
+  DragDisplay(display_1.id(), 3000, 0);
+
+  CheckPreviewIndicatorShown(display_1.id(), display_2.id(),
+                             /*is_visible=*/false);
+
+  CheckPreviewIndicatorShown(display_1.id(), display_3.id(),
+                             /*is_visible=*/true);
+}
 }  // namespace ash
diff --git a/ash/display/display_alignment_indicator.cc b/ash/display/display_alignment_indicator.cc
index ba292406..5e7d43f 100644
--- a/ash/display/display_alignment_indicator.cc
+++ b/ash/display/display_alignment_indicator.cc
@@ -354,7 +354,8 @@
     const display::Display& src_display,
     const gfx::Rect& bounds,
     const std::string& target_name)
-    : indicator_view_(new IndicatorHighlightView(src_display)) {
+    : display_id_(src_display.id()),
+      indicator_view_(new IndicatorHighlightView(src_display)) {
   gfx::Rect thickened_bounds = bounds;
   AdjustIndicatorBounds(src_display, &thickened_bounds);
 
@@ -409,4 +410,14 @@
     pill_widget_->Hide();
 }
 
+void DisplayAlignmentIndicator::Update(const display::Display& display,
+                                       gfx::Rect bounds) {
+  DCHECK(!pill_widget_);
+
+  AdjustIndicatorBounds(display, &bounds);
+  const IndicatorPosition src_direction = GetIndicatorPosition(display, bounds);
+  indicator_view_->SetPosition(src_direction);
+  indicator_widget_.SetBounds(bounds);
+}
+
 }  // namespace ash
diff --git a/ash/display/display_alignment_indicator.h b/ash/display/display_alignment_indicator.h
index 8e985ed..2065211 100644
--- a/ash/display/display_alignment_indicator.h
+++ b/ash/display/display_alignment_indicator.h
@@ -48,10 +48,17 @@
       delete;
   ~DisplayAlignmentIndicator();
 
+  int64_t display_id() const { return display_id_; }
+
   // Shows/Hides the indicator.
   void Show();
   void Hide();
 
+  // Updates the position of the indicator according to |bounds|. Used to move
+  // around preview indicators during dragging. The indicator must NOT have a
+  // pill.
+  void Update(const display::Display& display, gfx::Rect bounds);
+
  private:
   friend class DisplayAlignmentIndicatorTest;
   friend class DisplayAlignmentControllerTest;
@@ -61,6 +68,9 @@
                             const gfx::Rect& bounds,
                             const std::string& target_name);
 
+  // The ID of the display that the indicator is shown on.
+  const int64_t display_id_;
+
   // View and Widget for showing the blue indicator highlights on the edge of
   // the display.
   IndicatorHighlightView* indicator_view_ = nullptr;  // NOT OWNED
diff --git a/ash/display/privacy_screen_controller.cc b/ash/display/privacy_screen_controller.cc
index d07d223..f71f28f 100644
--- a/ash/display/privacy_screen_controller.cc
+++ b/ash/display/privacy_screen_controller.cc
@@ -11,11 +11,13 @@
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 #include "ui/display/manager/display_configurator.h"
+#include "ui/display/types/display_constants.h"
 #include "ui/display/types/display_snapshot.h"
 
 namespace ash {
 
-PrivacyScreenController::PrivacyScreenController() {
+PrivacyScreenController::PrivacyScreenController()
+    : display_id_(display::kInvalidDisplayId) {
   Shell::Get()->session_controller()->AddObserver(this);
   Shell::Get()->display_configurator()->AddObserver(this);
 }
@@ -32,9 +34,7 @@
 }
 
 bool PrivacyScreenController::IsSupported() const {
-  return Shell::Get()
-      ->display_configurator()
-      ->IsPrivacyScreenSupportedOnInternalDisplay();
+  return display_id_ != display::kInvalidDisplayId;
 }
 
 bool PrivacyScreenController::IsManaged() const {
@@ -95,8 +95,10 @@
 
 void PrivacyScreenController::OnDisplayModeChanged(
     const std::vector<display::DisplaySnapshot*>& displays) {
+  UpdateSupport();
+
   // OnDisplayModeChanged() may fire many times during Chrome's lifetime. We
-  // limit it to when applying login screen prefs.
+  // limit automatic user pref initialization to login screen only.
   if (applying_login_screen_prefs_) {
     InitFromUserPrefs();
     applying_login_screen_prefs_ = false;
@@ -106,8 +108,8 @@
 void PrivacyScreenController::OnEnabledPrefChanged(bool notify_observers) {
   if (IsSupported()) {
     const bool is_enabled = GetEnabled();
-    Shell::Get()->display_configurator()->SetPrivacyScreenOnInternalDisplay(
-        is_enabled);
+    Shell::Get()->display_configurator()->SetPrivacyScreen(display_id_,
+                                                           is_enabled);
 
     if (!notify_observers)
       return;
@@ -133,4 +135,20 @@
   OnEnabledPrefChanged(/*notify_observers=*/false);
 }
 
+void PrivacyScreenController::UpdateSupport() {
+  const auto& cached_displays =
+      Shell::Get()->display_configurator()->cached_displays();
+
+  for (auto* display : cached_displays) {
+    if (display->type() == display::DISPLAY_CONNECTION_TYPE_INTERNAL &&
+        display->privacy_screen_state() != display::kNotSupported &&
+        display->current_mode()) {
+      display_id_ = display->display_id();
+      return;
+    }
+  }
+
+  display_id_ = display::kInvalidDisplayId;
+}
+
 }  // namespace ash
diff --git a/ash/display/privacy_screen_controller.h b/ash/display/privacy_screen_controller.h
index 105922d1..821f3a8 100644
--- a/ash/display/privacy_screen_controller.h
+++ b/ash/display/privacy_screen_controller.h
@@ -68,6 +68,10 @@
   // OnActiveUserPrefServiceChanged() is called.
   void InitFromUserPrefs();
 
+  // Updates the internal state of the controller to whether or not the
+  // privacy screen feature is currently supported by the device.
+  void UpdateSupport();
+
   // The pref service of the currently active user. Can be null in
   // ash_unittests.
   PrefService* active_user_pref_service_ = nullptr;
@@ -76,6 +80,12 @@
   // Chrome restart.
   bool applying_login_screen_prefs_ = false;
 
+  // Cache the display id of the internal display if it supports the privacy
+  // screen feature. If |display_id_| is kInvalidDisplayId then it's not
+  // supported, otherwise, it is. Updates every time there's a reconfiguration
+  // of displays.
+  int64_t display_id_;
+
   base::ObserverList<Observer> observers_;
 
   // The registrar used to watch privacy screen prefs changes in the above
diff --git a/ash/display/privacy_screen_controller_unittest.cc b/ash/display/privacy_screen_controller_unittest.cc
index 43b8a43..6545d37b6 100644
--- a/ash/display/privacy_screen_controller_unittest.cc
+++ b/ash/display/privacy_screen_controller_unittest.cc
@@ -181,6 +181,19 @@
   EXPECT_FALSE(controller()->GetEnabled());
 }
 
+TEST_F(PrivacyScreenControllerTest, SupportedOnSingleInternalDisplay) {
+  BuildAndUpdateDisplaySnapshots({{
+      /*id=*/123u,
+      /*is_internal_display=*/true,
+      /*supports_privacy_screen=*/true,
+  }});
+  EXPECT_EQ(1u, display_manager()->GetNumDisplays());
+  ASSERT_TRUE(controller()->IsSupported());
+
+  controller()->SetEnabled(true);
+  EXPECT_TRUE(controller()->GetEnabled());
+}
+
 TEST_F(PrivacyScreenControllerTest, NotSupportedOnSingleInternalDisplay) {
   BuildAndUpdateDisplaySnapshots({{
       /*id=*/123u,
@@ -193,6 +206,30 @@
   EXPECT_FALSE(controller()->GetEnabled());
 }
 
+// Test that the privacy screen is not supported when the device is connected
+// to an external display and the lid is closed (a.k.a. docked mode).
+TEST_F(PrivacyScreenControllerTest, NotSupportedOnInternalDisplayWhenDocked) {
+  BuildAndUpdateDisplaySnapshots({{
+                                      /*id=*/123u,
+                                      /*is_internal_display=*/true,
+                                      /*supports_privacy_screen=*/true,
+                                  },
+                                  {
+                                      /*id=*/234u,
+                                      /*is_internal_display=*/false,
+                                      /*supports_privacy_screen=*/false,
+                                  }});
+  EXPECT_EQ(2u, display_manager()->GetNumDisplays());
+
+  // Turn off the internal display
+  display_manager()->configurator()->SetDisplayPower(
+      chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON,
+      display::DisplayConfigurator::kSetDisplayPowerNoFlags, base::DoNothing());
+
+  ASSERT_FALSE(controller()->IsSupported());
+  EXPECT_FALSE(controller()->GetEnabled());
+}
+
 TEST_F(PrivacyScreenControllerTest,
        SupportedOnInternalDisplayWithMultipleExternalDisplays) {
   BuildAndUpdateDisplaySnapshots({{
@@ -209,13 +246,8 @@
                                       /*id=*/3412u,
                                       /*is_internal_display=*/false,
                                       /*supports_privacy_screen=*/false,
-                                  },
-                                  {
-                                      /*id=*/4123u,
-                                      /*is_internal_display=*/false,
-                                      /*supports_privacy_screen=*/false,
                                   }});
-  EXPECT_EQ(4u, display_manager()->GetNumDisplays());
+  EXPECT_EQ(3u, display_manager()->GetNumDisplays());
   ASSERT_TRUE(controller()->IsSupported());
 
   controller()->SetEnabled(true);
@@ -238,41 +270,8 @@
                                       /*id=*/3412u,
                                       /*is_internal_display=*/false,
                                       /*supports_privacy_screen=*/false,
-                                  },
-                                  {
-                                      /*id=*/4123u,
-                                      /*is_internal_display=*/false,
-                                      /*supports_privacy_screen=*/false,
                                   }});
-  EXPECT_EQ(4u, display_manager()->GetNumDisplays());
-  ASSERT_FALSE(controller()->IsSupported());
-
-  EXPECT_FALSE(controller()->GetEnabled());
-}
-
-TEST_F(PrivacyScreenControllerTest,
-       NotSupportedOnMultipleSupportingExternalDisplays) {
-  BuildAndUpdateDisplaySnapshots({{
-                                      /*id=*/1234u,
-                                      /*is_internal_display=*/false,
-                                      /*supports_privacy_screen=*/false,
-                                  },
-                                  {
-                                      /*id=*/2341u,
-                                      /*is_internal_display=*/false,
-                                      /*supports_privacy_screen=*/true,
-                                  },
-                                  {
-                                      /*id=*/3412u,
-                                      /*is_internal_display=*/false,
-                                      /*supports_privacy_screen=*/true,
-                                  },
-                                  {
-                                      /*id=*/4123u,
-                                      /*is_internal_display=*/false,
-                                      /*supports_privacy_screen=*/true,
-                                  }});
-  EXPECT_EQ(4u, display_manager()->GetNumDisplays());
+  EXPECT_EQ(3u, display_manager()->GetNumDisplays());
   ASSERT_FALSE(controller()->IsSupported());
 
   EXPECT_FALSE(controller()->GetEnabled());
diff --git a/ash/drag_drop/drag_drop_controller.cc b/ash/drag_drop/drag_drop_controller.cc
index e2522a5..cce8c79f 100644
--- a/ash/drag_drop/drag_drop_controller.cc
+++ b/ash/drag_drop/drag_drop_controller.cc
@@ -53,8 +53,7 @@
 static const int kTouchDragImageVerticalOffset = -25;
 
 // Adjusts the drag image bounds such that the new bounds are scaled by |scale|
-// and translated by the |drag_image_offset| and and additional
-// |vertical_offset|.
+// and translated by the |drag_image_offset| and additional |vertical_offset|.
 gfx::Rect AdjustDragImageBoundsForScaleAndOffset(
     const gfx::Rect& drag_image_bounds,
     int vertical_offset,
diff --git a/ash/login/login_screen_test_api.cc b/ash/login/login_screen_test_api.cc
index d104fae..0858237 100644
--- a/ash/login/login_screen_test_api.cc
+++ b/ash/login/login_screen_test_api.cc
@@ -154,6 +154,15 @@
 }
 
 // static
+void LoginScreenTestApi::AddOnLockScreenShownCallback(
+    base::OnceClosure on_lock_screen_shown) {
+  if (!LockScreen::HasInstance())
+    FAIL() << "No lock screen";
+  LockScreen::TestApi lock_screen_test(LockScreen::Get());
+  lock_screen_test.AddOnShownCallback(std::move(on_lock_screen_shown));
+}
+
+// static
 bool LoginScreenTestApi::IsLoginShelfShown() {
   LoginShelfView* view = GetLoginShelfView();
   return view && view->GetVisible();
diff --git a/ash/login/ui/lock_screen.cc b/ash/login/ui/lock_screen.cc
index 51981bd..738c611 100644
--- a/ash/login/ui/lock_screen.cc
+++ b/ash/login/ui/lock_screen.cc
@@ -4,6 +4,7 @@
 
 #include "ash/login/ui/lock_screen.h"
 
+#include <algorithm>
 #include <memory>
 #include <utility>
 
@@ -45,6 +46,14 @@
   return lock_screen_->contents_view_;
 }
 
+void LockScreen::TestApi::AddOnShownCallback(base::OnceClosure on_shown) {
+  if (lock_screen_->is_shown_) {
+    std::move(on_shown).Run();
+    return;
+  }
+  lock_screen_->on_shown_callbacks_.push_back(std::move(on_shown));
+}
+
 LockScreen::LockScreen(ScreenType type) : type_(type) {
   tray_action_observer_.Add(Shell::Get()->tray_action());
   saved_clipboard_ = ui::Clipboard::TakeForCurrentThread();
@@ -103,13 +112,7 @@
   // completes, to make the transition smooth. The callback will be dispatched
   // immediately if the animation is already complete (e.g. kLock).
   Shell::Get()->wallpaper_controller()->AddFirstWallpaperAnimationEndCallback(
-      base::BindOnce([]() {
-        // |instance_| may already be destroyed in tests.
-        if (!instance_ || instance_->is_shown_)
-          return;
-        instance_->is_shown_ = true;
-        instance_->widget_->Show();
-      }),
+      base::BindOnce(&LockScreen::ShowWidgetUponWallpaperReady),
       instance_->widget_->GetNativeView());
 }
 
@@ -176,4 +179,18 @@
   Destroy();
 }
 
+// static
+void LockScreen::ShowWidgetUponWallpaperReady() {
+  // |instance_| may already be destroyed in tests.
+  if (!instance_ || instance_->is_shown_)
+    return;
+  instance_->is_shown_ = true;
+  instance_->widget_->Show();
+
+  std::vector<base::OnceClosure> on_shown_callbacks;
+  swap(instance_->on_shown_callbacks_, on_shown_callbacks);
+  for (auto& callback : on_shown_callbacks)
+    std::move(callback).Run();
+}
+
 }  // namespace ash
diff --git a/ash/login/ui/lock_screen.h b/ash/login/ui/lock_screen.h
index ce4f1997..7ec0be2 100644
--- a/ash/login/ui/lock_screen.h
+++ b/ash/login/ui/lock_screen.h
@@ -5,11 +5,14 @@
 #ifndef ASH_LOGIN_UI_LOCK_SCREEN_H_
 #define ASH_LOGIN_UI_LOCK_SCREEN_H_
 
+#include <vector>
+
 #include "ash/ash_export.h"
 #include "ash/public/cpp/login_types.h"
 #include "ash/public/cpp/session/session_observer.h"
 #include "ash/tray_action/tray_action.h"
 #include "ash/tray_action/tray_action_observer.h"
+#include "base/callback.h"
 #include "base/macros.h"
 #include "base/scoped_observer.h"
 #include "ui/base/clipboard/clipboard.h"
@@ -32,6 +35,7 @@
     ~TestApi();
 
     LockContentsView* contents_view() const;
+    void AddOnShownCallback(base::OnceClosure on_shown);
 
    private:
     LockScreen* const lock_screen_;
@@ -77,6 +81,10 @@
   explicit LockScreen(ScreenType type);
   ~LockScreen() override;
 
+  // Shows the lock screen widget, unless the global instance was already
+  // destroyed. Called after the first wallpaper becomes ready.
+  static void ShowWidgetUponWallpaperReady();
+
   // The type of screen shown. Controls how the screen is dismissed.
   const ScreenType type_;
 
@@ -93,6 +101,8 @@
   ScopedObserver<TrayAction, TrayActionObserver> tray_action_observer_{this};
   ScopedSessionObserver session_observer_{this};
 
+  std::vector<base::OnceClosure> on_shown_callbacks_;
+
   DISALLOW_COPY_AND_ASSIGN(LockScreen);
 };
 
diff --git a/ash/power/gatt_battery_percentage_fetcher_unittest.cc b/ash/power/gatt_battery_percentage_fetcher_unittest.cc
index 61b6aaa..4550507 100644
--- a/ash/power/gatt_battery_percentage_fetcher_unittest.cc
+++ b/ash/power/gatt_battery_percentage_fetcher_unittest.cc
@@ -73,7 +73,7 @@
     mock_service_ =
         std::make_unique<NiceMock<device::MockBluetoothGattService>>(
             mock_device_.get(), kServiceID, GetBatteryServiceUUID(),
-            true /* is_primary */, false /* is_local */);
+            /*is_primary=*/true);
     std::vector<device::BluetoothRemoteGattService*> services = {
         mock_service_.get()};
     ON_CALL(*mock_device_, GetGattServices()).WillByDefault(Return(services));
@@ -81,7 +81,6 @@
     mock_characteristic_ =
         std::make_unique<NiceMock<device::MockBluetoothGattCharacteristic>>(
             mock_service_.get(), kCharacteristicID, GetBatteryLevelUUID(),
-            false /* is_local */,
             BluetoothRemoteGattCharacteristic::PROPERTY_READ,
             BluetoothRemoteGattCharacteristic::PERMISSION_READ);
     std::vector<BluetoothRemoteGattCharacteristic*> characteristics = {
diff --git a/ash/public/cpp/default_scale_factor_retriever_unittest.cc b/ash/public/cpp/default_scale_factor_retriever_unittest.cc
index 1970aa0..341fc99 100644
--- a/ash/public/cpp/default_scale_factor_retriever_unittest.cc
+++ b/ash/public/cpp/default_scale_factor_retriever_unittest.cc
@@ -63,6 +63,8 @@
                         ash::mojom::TouchCalibrationPtr calibration,
                         TouchCalibrationCallback callback) override {}
   void HighlightDisplay(int64_t id) override {}
+  void DragDisplayDelta(int64_t id, int32_t delta_x, int32_t delta_y) override {
+  }
 
  private:
   mojo::Receiver<ash::mojom::CrosDisplayConfigController> receiver_{this};
diff --git a/ash/public/cpp/login_screen_test_api.h b/ash/public/cpp/login_screen_test_api.h
index a1616e5..7b51950 100644
--- a/ash/public/cpp/login_screen_test_api.h
+++ b/ash/public/cpp/login_screen_test_api.h
@@ -21,6 +21,11 @@
 class ASH_PUBLIC_EXPORT LoginScreenTestApi {
  public:
   static bool IsLockShown();
+  // Schedules the callback to be run when the LockScreen is shown. Note that
+  // the LockScreen class is used for both the Lock and the Login screens.
+  static void AddOnLockScreenShownCallback(
+      base::OnceClosure on_lock_screen_shown);
+
   static bool IsLoginShelfShown();
   static bool IsRestartButtonShown();
   static bool IsShutdownButtonShown();
diff --git a/ash/public/mojom/cros_display_config.mojom b/ash/public/mojom/cros_display_config.mojom
index b3096ad6..61b5120 100644
--- a/ash/public/mojom/cros_display_config.mojom
+++ b/ash/public/mojom/cros_display_config.mojom
@@ -302,6 +302,15 @@
   // Sets |id| of display to render identification highlight on. Invalid |id|
   // turns identification highlight off.
   HighlightDisplay(int64 id);
+
+  // Updates preview indicators with change in position of display being dragged
+  // in display layouts section of the display settings page. |display_id| is
+  // the ID of the display being dragged. |delta_x| and |delta_y| are the change
+  // in position of the dragged display since DragDisplayDelta() was last
+  // called. |display_id| remains the same while the drag is in progress, once
+  // the display is dropped, the new layout is applied, updating the display
+  // configuration.
+  DragDisplayDelta(int64 display_id, int32 delta_x, int32 delta_y);
 };
 
 // Interface for clients needing to be informed when the display configuration
diff --git a/ash/system/message_center/ash_message_popup_collection.cc b/ash/system/message_center/ash_message_popup_collection.cc
index 318d01b..82ad696 100644
--- a/ash/system/message_center/ash_message_popup_collection.cc
+++ b/ash/system/message_center/ash_message_popup_collection.cc
@@ -9,6 +9,7 @@
 #include "ash/public/cpp/shelf_types.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/root_window_controller.h"
+#include "ash/shelf/hotseat_widget.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
 #include "ash/system/message_center/fullscreen_notification_blocker.h"
@@ -97,8 +98,12 @@
 
 int AshMessagePopupCollection::GetBaseline() const {
   gfx::Insets tray_bubble_insets = GetTrayBubbleInsets();
+  int hotseat_height =
+      shelf_->hotseat_widget()->state() == HotseatState::kExtended
+          ? shelf_->hotseat_widget()->GetHotseatSize()
+          : 0;
   return work_area_.bottom() - tray_bubble_insets.bottom() -
-         tray_bubble_height_;
+         tray_bubble_height_ - hotseat_height;
 }
 
 gfx::Rect AshMessagePopupCollection::GetWorkArea() const {
@@ -215,6 +220,11 @@
   UpdateWorkArea();
 }
 
+void AshMessagePopupCollection::OnHotseatStateChanged(HotseatState old_state,
+                                                      HotseatState new_state) {
+  ResetBounds();
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // display::DisplayObserver:
 
diff --git a/ash/system/message_center/ash_message_popup_collection.h b/ash/system/message_center/ash_message_popup_collection.h
index 2612d162..6a838d5 100644
--- a/ash/system/message_center/ash_message_popup_collection.h
+++ b/ash/system/message_center/ash_message_popup_collection.h
@@ -90,6 +90,8 @@
 
   // ShelfObserver:
   void OnShelfWorkAreaInsetsChanged() override;
+  void OnHotseatStateChanged(HotseatState old_state,
+                             HotseatState new_state) override;
 
   // display::DisplayObserver:
   void OnDisplayMetricsChanged(const display::Display& display,
diff --git a/ash/system/message_center/session_state_notification_blocker.cc b/ash/system/message_center/session_state_notification_blocker.cc
index f904f12..a1ee3ab 100644
--- a/ash/system/message_center/session_state_notification_blocker.cc
+++ b/ash/system/message_center/session_state_notification_blocker.cc
@@ -26,8 +26,10 @@
   SessionControllerImpl* const session_controller =
       Shell::Get()->session_controller();
 
-  // Enable popup in OOBE to display system notifications (wifi, etc.).
-  if (session_controller->GetSessionState() == SessionState::OOBE)
+  // Enable popup in OOBE and login screen to display system notifications
+  // (wifi, etc.).
+  if (session_controller->GetSessionState() == SessionState::OOBE ||
+      session_controller->GetSessionState() == SessionState::LOGIN_PRIMARY)
     return true;
 
   if (session_controller->IsRunningInAppMode() ||
diff --git a/ash/system/message_center/session_state_notification_blocker_unittest.cc b/ash/system/message_center/session_state_notification_blocker_unittest.cc
index cb1bdd6..d08d40b 100644
--- a/ash/system/message_center/session_state_notification_blocker_unittest.cc
+++ b/ash/system/message_center/session_state_notification_blocker_unittest.cc
@@ -83,19 +83,19 @@
 TEST_F(SessionStateNotificationBlockerTest, BaseTest) {
   // OOBE.
   GetSessionControllerClient()->SetSessionState(SessionState::OOBE);
-  EXPECT_EQ(1, GetStateChangedCountAndReset());
+  EXPECT_EQ(0, GetStateChangedCountAndReset());
   message_center::NotifierId notifier_id(
       message_center::NotifierType::APPLICATION, "test-notifier");
   EXPECT_TRUE(ShouldShowNotificationAsPopup(notifier_id));
 
   // Login screen.
   GetSessionControllerClient()->SetSessionState(SessionState::LOGIN_PRIMARY);
-  EXPECT_EQ(1, GetStateChangedCountAndReset());
-  EXPECT_FALSE(ShouldShowNotificationAsPopup(notifier_id));
+  EXPECT_EQ(0, GetStateChangedCountAndReset());
+  EXPECT_TRUE(ShouldShowNotificationAsPopup(notifier_id));
 
   // Logged in as a normal user.
   SimulateUserLogin("user@test.com");
-  EXPECT_EQ(1, GetStateChangedCountAndReset());
+  EXPECT_EQ(0, GetStateChangedCountAndReset());
   EXPECT_TRUE(ShouldShowNotificationAsPopup(notifier_id));
 
   // Lock.
@@ -116,17 +116,17 @@
 
   // OOBE.
   GetSessionControllerClient()->SetSessionState(SessionState::OOBE);
-  EXPECT_EQ(1, GetStateChangedCountAndReset());
+  EXPECT_EQ(0, GetStateChangedCountAndReset());
   EXPECT_TRUE(ShouldShowNotificationAsPopup(notifier_id));
 
   // Login screen.
   GetSessionControllerClient()->SetSessionState(SessionState::LOGIN_PRIMARY);
-  EXPECT_EQ(1, GetStateChangedCountAndReset());
+  EXPECT_EQ(0, GetStateChangedCountAndReset());
   EXPECT_TRUE(ShouldShowNotificationAsPopup(notifier_id));
 
   // Logged in as a normal user.
   SimulateUserLogin("user@test.com");
-  EXPECT_EQ(1, GetStateChangedCountAndReset());
+  EXPECT_EQ(0, GetStateChangedCountAndReset());
   EXPECT_TRUE(ShouldShowNotificationAsPopup(notifier_id));
 
   // Lock.
@@ -143,15 +143,15 @@
 TEST_F(SessionStateNotificationBlockerTest, BlockOnPrefService) {
   // OOBE.
   GetSessionControllerClient()->SetSessionState(SessionState::OOBE);
-  EXPECT_EQ(1, GetStateChangedCountAndReset());
+  EXPECT_EQ(0, GetStateChangedCountAndReset());
   message_center::NotifierId notifier_id(
       message_center::NotifierType::APPLICATION, "test-notifier");
   EXPECT_TRUE(ShouldShowNotificationAsPopup(notifier_id));
 
   // Login screen.
   GetSessionControllerClient()->SetSessionState(SessionState::LOGIN_PRIMARY);
-  EXPECT_EQ(1, GetStateChangedCountAndReset());
-  EXPECT_FALSE(ShouldShowNotificationAsPopup(notifier_id));
+  EXPECT_EQ(0, GetStateChangedCountAndReset());
+  EXPECT_TRUE(ShouldShowNotificationAsPopup(notifier_id));
 
   // Simulates login event sequence in production code:
   // - Add a user session;
@@ -166,14 +166,14 @@
                                             true, /* enable_settings */
                                             false /* provide_pref_service */);
   EXPECT_EQ(0, GetStateChangedCountAndReset());
-  EXPECT_FALSE(ShouldShowNotificationAsPopup(notifier_id));
+  EXPECT_TRUE(ShouldShowNotificationAsPopup(notifier_id));
 
   session_controller_client->SwitchActiveUser(kUserAccountId);
   EXPECT_EQ(0, GetStateChangedCountAndReset());
-  EXPECT_FALSE(ShouldShowNotificationAsPopup(notifier_id));
+  EXPECT_TRUE(ShouldShowNotificationAsPopup(notifier_id));
 
   session_controller_client->SetSessionState(SessionState::ACTIVE);
-  EXPECT_EQ(0, GetStateChangedCountAndReset());
+  EXPECT_EQ(1, GetStateChangedCountAndReset());
   EXPECT_FALSE(ShouldShowNotificationAsPopup(notifier_id));
 
   session_controller_client->ProvidePrefServiceForUser(kUserAccountId);
diff --git a/ash/wm/desks/desk_mini_view.cc b/ash/wm/desks/desk_mini_view.cc
index c24383fb..4201cb4 100644
--- a/ash/wm/desks/desk_mini_view.cc
+++ b/ash/wm/desks/desk_mini_view.cc
@@ -7,6 +7,7 @@
 #include <algorithm>
 
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_provider.h"
 #include "ash/wm/desks/close_desk_button.h"
 #include "ash/wm/desks/desk.h"
 #include "ash/wm/desks/desk_name_view.h"
@@ -31,7 +32,8 @@
 
 constexpr int kCloseButtonMargin = 8;
 
-constexpr SkColor kActiveColor = SK_ColorWHITE;
+constexpr SkColor kDarkModeActiveColor = SK_ColorWHITE;
+constexpr SkColor kLightModeActiveColor = SK_ColorBLACK;
 constexpr SkColor kInactiveColor = SK_ColorTRANSPARENT;
 
 constexpr SkColor kDraggedOverColor = SkColorSetARGB(0xFF, 0x5B, 0xBC, 0xFF);
@@ -125,14 +127,22 @@
 
 void DeskMiniView::UpdateBorderColor() {
   DCHECK(desk_);
+  auto* color_provider = AshColorProvider::Get();
   if (owner_bar_->dragged_item_over_bar() &&
       IsPointOnMiniView(owner_bar_->last_dragged_item_screen_location())) {
     desk_preview_->SetBorderColor(kDraggedOverColor);
   } else if (IsViewHighlighted()) {
-    desk_preview_->SetBorderColor(gfx::kGoogleBlue300);
+    desk_preview_->SetBorderColor(color_provider->GetControlsLayerColor(
+        AshColorProvider::ControlsLayerType::kFocusRingColor,
+        AshColorProvider::AshColorMode::kDark));
+  } else if (!desk_->is_active()) {
+    desk_preview_->SetBorderColor(kInactiveColor);
   } else {
-    desk_preview_->SetBorderColor(desk_->is_active() ? kActiveColor
-                                                     : kInactiveColor);
+    // Default theme for desks is dark mode.
+    desk_preview_->SetBorderColor(color_provider->color_mode() ==
+                                          AshColorProvider::AshColorMode::kLight
+                                      ? kLightModeActiveColor
+                                      : kDarkModeActiveColor);
   }
 }
 
diff --git a/ash/wm/desks/desk_name_view.cc b/ash/wm/desks/desk_name_view.cc
index 80c5ba9..93cbd0f 100644
--- a/ash/wm/desks/desk_name_view.cc
+++ b/ash/wm/desks/desk_name_view.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "ash/shell.h"
+#include "ash/style/ash_color_provider.h"
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/overview/overview_grid.h"
 #include "ui/gfx/canvas.h"
@@ -44,7 +45,9 @@
   SetBorder(std::move(border));
 
   SetBackgroundColor(SK_ColorTRANSPARENT);
-  SetTextColor(SK_ColorWHITE);
+  SetTextColor(AshColorProvider::Get()->GetContentLayerColor(
+      AshColorProvider::ContentLayerType::kTextColorPrimary,
+      AshColorProvider::AshColorMode::kDark));
   SetCursorEnabled(true);
   SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_CENTER);
 }
@@ -116,8 +119,7 @@
 }
 
 void DeskNameView::UpdateBorderState() {
-  border_ptr_->set_color(IsViewHighlighted() ? gfx::kGoogleBlue300
-                                             : SK_ColorTRANSPARENT);
+  border_ptr_->SetFocused(IsViewHighlighted());
   SchedulePaint();
 }
 
diff --git a/ash/wm/desks/desks_bar_view.cc b/ash/wm/desks/desks_bar_view.cc
index 5ac3a41..644c5a4 100644
--- a/ash/wm/desks/desks_bar_view.cc
+++ b/ash/wm/desks/desks_bar_view.cc
@@ -138,8 +138,8 @@
   background_view_->SetPaintToLayer(ui::LAYER_SOLID_COLOR);
   background_view_->layer()->SetFillsBoundsOpaquely(false);
   background_view_->layer()->SetColor(
-      AshColorProvider::Get()->GetBaseLayerColor(
-          AshColorProvider::BaseLayerType::kTransparent80,
+      AshColorProvider::Get()->GetShieldLayerColor(
+          AshColorProvider::ShieldLayerType::kShield80,
           AshColorProvider::AshColorMode::kDark));
 
   AddChildView(background_view_);
diff --git a/ash/wm/desks/new_desk_button.cc b/ash/wm/desks/new_desk_button.cc
index b797461..fe2ee37 100644
--- a/ash/wm/desks/new_desk_button.cc
+++ b/ash/wm/desks/new_desk_button.cc
@@ -21,7 +21,6 @@
 #include "base/strings/utf_string_conversions.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/canvas.h"
-#include "ui/gfx/color_palette.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/views/animation/ink_drop_impl.h"
 #include "ui/views/background.h"
@@ -203,10 +202,8 @@
 }
 
 void NewDeskButton::UpdateBorderState() {
-  border_ptr_->set_color(
-      (IsViewHighlighted() && DesksController::Get()->CanCreateDesks())
-          ? gfx::kGoogleBlue300
-          : SK_ColorTRANSPARENT);
+  border_ptr_->SetFocused(IsViewHighlighted() &&
+                          DesksController::Get()->CanCreateDesks());
   SchedulePaint();
 }
 
diff --git a/ash/wm/window_mini_view.cc b/ash/wm/window_mini_view.cc
index 1847620..46ef1c2 100644
--- a/ash/wm/window_mini_view.cc
+++ b/ash/wm/window_mini_view.cc
@@ -14,7 +14,6 @@
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/window.h"
-#include "ui/gfx/color_palette.h"
 #include "ui/gfx/image/image_skia_operations.h"
 #include "ui/views/controls/image_view.h"
 #include "ui/views/controls/label.h"
@@ -108,7 +107,7 @@
 }
 
 void WindowMiniView::UpdateBorderState(bool show) {
-  border_ptr_->set_color(show ? gfx::kGoogleBlue300 : SK_ColorTRANSPARENT);
+  border_ptr_->SetFocused(show);
   SchedulePaint();
 }
 
diff --git a/ash/wm/wm_highlight_item_border.cc b/ash/wm/wm_highlight_item_border.cc
index a567540..c9a30ab 100644
--- a/ash/wm/wm_highlight_item_border.cc
+++ b/ash/wm/wm_highlight_item_border.cc
@@ -4,6 +4,7 @@
 
 #include "ash/wm/wm_highlight_item_border.h"
 
+#include "ash/style/ash_color_provider.h"
 #include "cc/paint/paint_flags.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/geometry/rect_f.h"
@@ -22,6 +23,15 @@
 WmHighlightItemBorder::WmHighlightItemBorder(int corner_radius)
     : views::Border(SK_ColorTRANSPARENT), corner_radius_(corner_radius) {}
 
+void WmHighlightItemBorder::SetFocused(bool focused) {
+  // Note that all WM features that use this custom border currently have dark
+  // mode as the default color mode.
+  set_color(focused ? AshColorProvider::Get()->GetControlsLayerColor(
+                          AshColorProvider::ControlsLayerType::kFocusRingColor,
+                          AshColorProvider::AshColorMode::kDark)
+                    : SK_ColorTRANSPARENT);
+}
+
 void WmHighlightItemBorder::Paint(const views::View& view,
                                   gfx::Canvas* canvas) {
   if (color() == SK_ColorTRANSPARENT)
diff --git a/ash/wm/wm_highlight_item_border.h b/ash/wm/wm_highlight_item_border.h
index effb855..72c6bad 100644
--- a/ash/wm/wm_highlight_item_border.h
+++ b/ash/wm/wm_highlight_item_border.h
@@ -24,6 +24,10 @@
 
   void set_extra_margin(int extra_margin) { extra_margin_ = extra_margin; }
 
+  // This highlight meant to indicate focus. No border will be painted if
+  // |focused| is false.
+  void SetFocused(bool focused);
+
   // views::Border:
   void Paint(const views::View& view, gfx::Canvas* canvas) override;
   gfx::Insets GetInsets() const override;
diff --git a/ash/wm/workspace/workspace_window_resizer.cc b/ash/wm/workspace/workspace_window_resizer.cc
index ca8c0c4..5390064 100644
--- a/ash/wm/workspace/workspace_window_resizer.cc
+++ b/ash/wm/workspace/workspace_window_resizer.cc
@@ -12,6 +12,7 @@
 #include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/root_window_controller.h"
+#include "ash/scoped_animation_disabler.h"
 #include "ash/screen_util.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
@@ -701,6 +702,11 @@
       // window at the bounds that the user has moved/resized the
       // window to.
       window_state()->SaveCurrentBoundsForRestore();
+
+      // Since we saved the current bounds to the restore bounds, the restore
+      // animation will use the current bounds as the target bounds, so we can
+      // disable the animation here.
+      ScopedAnimationDisabler disabler(window_state()->window());
       window_state()->Restore();
     }
     return;
@@ -710,7 +716,17 @@
   // window here.
   if (window_state()->IsMaximized()) {
     DCHECK_EQ(HTCAPTION, details().window_component);
+    // Reaching here the only running animation should be the drag to
+    // unmaximize animation. Stop animating so that animations that might come
+    // after because of a gesture swipe or fling look smoother.
+    window_state()->window()->layer()->GetAnimator()->StopAnimating();
+
     window_state()->SaveCurrentBoundsForRestore();
+
+    // Since we saved the current bounds to the restore bounds, the restore
+    // animation will use the current bounds as the target bounds, so we can
+    // disable the animation here.
+    ScopedAnimationDisabler disabler(window_state()->window());
     window_state()->Restore();
     return;
   }
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 58bb863..fde5afb0 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -1772,6 +1772,7 @@
         "allocator/partition_allocator/partition_page.cc",
         "allocator/partition_allocator/partition_page.h",
         "allocator/partition_allocator/partition_tag.h",
+        "allocator/partition_allocator/partition_tag_bitmap.h",
         "allocator/partition_allocator/random.cc",
         "allocator/partition_allocator/random.h",
         "allocator/partition_allocator/spin_lock.cc",
@@ -3458,6 +3459,7 @@
       "android/java/src/org/chromium/base/BuildInfo.java",
       "android/java/src/org/chromium/base/BundleUtils.java",
       "android/java/src/org/chromium/base/Callback.java",
+      "android/java/src/org/chromium/base/CallbackController.java",
       "android/java/src/org/chromium/base/CollectionUtil.java",
       "android/java/src/org/chromium/base/CommandLine.java",
       "android/java/src/org/chromium/base/CommandLineInitUtil.java",
@@ -3793,6 +3795,7 @@
   junit_binary("base_junit_tests") {
     sources = [
       "android/junit/src/org/chromium/base/ApplicationStatusTest.java",
+      "android/junit/src/org/chromium/base/CallbackControllerTest.java",
       "android/junit/src/org/chromium/base/DiscardableReferencePoolTest.java",
       "android/junit/src/org/chromium/base/FileUtilsTest.java",
       "android/junit/src/org/chromium/base/LifetimeAssertTest.java",
diff --git a/base/allocator/partition_allocator/partition_alloc.h b/base/allocator/partition_allocator/partition_alloc.h
index c8b01045..6bfd8d74 100644
--- a/base/allocator/partition_allocator/partition_alloc.h
+++ b/base/allocator/partition_allocator/partition_alloc.h
@@ -587,7 +587,7 @@
     memset(ret, 0, size_with_no_extras);
   }
 
-  if (allow_extras) {
+  if (allow_extras && !bucket->is_direct_mapped()) {
     // TODO(tasak): initialize tag randomly. Temporarily use
     // kTagTemporaryInitialValue to initialize the tag.
     internal::PartitionTagSetValue(ret, internal::kTagTemporaryInitialValue);
@@ -615,9 +615,9 @@
   // TODO(palmer): See if we can afford to make this a CHECK.
   PA_DCHECK(IsValidPage(page));
   auto* root = PartitionRoot<thread_safe>::FromPage(page);
-  if (root->allow_extras) {
+  if (root->allow_extras && !page->bucket->is_direct_mapped()) {
     // TODO(tasak): clear partition tag. Temporarily set the tag to be 0.
-    internal::PartitionTagSetValue(ptr, 0);
+    internal::PartitionTagClearValue(ptr);
   }
   ptr = internal::PartitionPointerAdjustSubtract(root->allow_extras, ptr);
   internal::DeferredUnmap deferred_unmap;
diff --git a/base/allocator/partition_allocator/partition_alloc_unittest.cc b/base/allocator/partition_allocator/partition_alloc_unittest.cc
index 602ce3b..2263d97 100644
--- a/base/allocator/partition_allocator/partition_alloc_unittest.cc
+++ b/base/allocator/partition_allocator/partition_alloc_unittest.cc
@@ -17,6 +17,8 @@
 #include "base/allocator/partition_allocator/page_allocator_constants.h"
 #include "base/allocator/partition_allocator/partition_alloc_constants.h"
 #include "base/allocator/partition_allocator/partition_alloc_features.h"
+#include "base/allocator/partition_allocator/partition_tag.h"
+#include "base/allocator/partition_allocator/partition_tag_bitmap.h"
 #include "base/logging.h"
 #include "base/rand_util.h"
 #include "base/stl_util.h"
@@ -131,11 +133,11 @@
 
 const size_t kTestAllocSize = 16;
 #if !DCHECK_IS_ON()
-const size_t kPointerOffset = kPartitionTagSize;
-const size_t kExtraAllocSize = kPartitionTagSize;
+const size_t kPointerOffset = kInSlotTagBufferSize;
+const size_t kExtraAllocSize = kInSlotTagBufferSize;
 #else
-const size_t kPointerOffset = kCookieSize + kPartitionTagSize;
-const size_t kExtraAllocSize = kCookieSize * 2 + kPartitionTagSize;
+const size_t kPointerOffset = kCookieSize + kInSlotTagBufferSize;
+const size_t kExtraAllocSize = kCookieSize * 2 + kInSlotTagBufferSize;
 #endif
 const size_t kRealAllocSize = kTestAllocSize + kExtraAllocSize;
 
@@ -165,6 +167,19 @@
     PartitionAllocGlobalUninitForTesting();
   }
 
+  size_t GetNumPagesPerSlotSpan(size_t size) {
+    size_t real_size = size + kExtraAllocSize;
+    size_t bucket_index = SizeToIndex(real_size);
+    PartitionRoot<ThreadSafe>::Bucket* bucket =
+        &allocator.root()->buckets[bucket_index];
+    // TODO(tasak): make get_pages_per_slot_span() available at
+    // partition_alloc_unittest.cc. Is it allowable to make the code from
+    // partition_bucet.cc to partition_bucket.h?
+    return (bucket->num_system_pages_per_slot_span +
+            (kNumSystemPagesPerPartitionPage - 1)) /
+           kNumSystemPagesPerPartitionPage;
+  }
+
   PartitionRoot<ThreadSafe>::Page* GetFullPage(size_t size) {
     size_t real_size = size + kExtraAllocSize;
     size_t bucket_index = SizeToIndex(real_size);
@@ -397,7 +412,7 @@
   EXPECT_EQ(kPointerOffset,
             reinterpret_cast<size_t>(ptr) & kPartitionPageOffsetMask);
   // Check that the offset appears to include a guard page.
-  EXPECT_EQ(kPartitionPageSize + kPointerOffset,
+  EXPECT_EQ(kPartitionPageSize + kPointerOffset + kReservedTagBitmapSize,
             reinterpret_cast<size_t>(ptr) & kSuperPageOffsetMask);
 
   allocator.root()->Free(ptr);
@@ -603,12 +618,23 @@
 // Test a large series of allocations that cross more than one underlying
 // 64KB super page allocation.
 TEST_F(PartitionAllocTest, MultiPageAllocs) {
+  // Need to consider the number of slot span per 1 Super Page.
+  // The number of pages needed should be calculated by using the number.
+  // Because if the number of left partition pages is smaller than
+  // the number of pages per slot span, a new super page will be allocated.
+  size_t num_pages_per_slot_span = GetNumPagesPerSlotSpan(kTestAllocSize);
+  // 1 SuperPage has 2 guard PartitionPages.
+  size_t num_slot_span_needed =
+      (kNumPartitionPagesPerSuperPage - kNumPartitionPagesPerTagBitmap - 2) /
+      num_pages_per_slot_span;
+
   // This is guaranteed to cross a super page boundary because the first
   // partition page "slot" will be taken up by a guard page.
-  size_t num_pages_needed = kNumPartitionPagesPerSuperPage;
+  size_t num_pages_needed = num_slot_span_needed * num_pages_per_slot_span;
+
   // The super page should begin and end in a guard so we one less page in
   // order to allocate a single page in the new super page.
-  --num_pages_needed;
+  ++num_pages_needed;
 
   EXPECT_GT(num_pages_needed, 1u);
   auto pages =
@@ -628,7 +654,8 @@
           reinterpret_cast<uintptr_t>(storage_ptr) & kSuperPageOffsetMask;
       EXPECT_FALSE(second_super_page_base == first_super_page_base);
       // Check that we allocated a guard page for the second page.
-      EXPECT_EQ(kPartitionPageSize, second_super_page_offset);
+      EXPECT_EQ(kPartitionPageSize + kReservedTagBitmapSize,
+                second_super_page_offset);
     }
   }
   for (i = 0; i < num_pages_needed; ++i)
@@ -1237,9 +1264,15 @@
 
 // Test correct handling if our mapping collides with another.
 TEST_F(PartitionAllocTest, MappingCollision) {
+  size_t num_pages_per_slot_span = GetNumPagesPerSlotSpan(kTestAllocSize);
   // The -2 is because the first and last partition pages in a super page are
   // guard pages.
-  size_t num_partition_pages_needed = kNumPartitionPagesPerSuperPage - 2;
+  size_t num_slot_span_needed =
+      (kNumPartitionPagesPerSuperPage - kNumPartitionPagesPerTagBitmap - 2) /
+      num_pages_per_slot_span;
+  size_t num_partition_pages_needed =
+      num_slot_span_needed * num_pages_per_slot_span;
+
   auto first_super_page_pages =
       std::make_unique<PartitionRoot<ThreadSafe>::Page*[]>(
           num_partition_pages_needed);
@@ -1253,9 +1286,9 @@
 
   char* page_base = reinterpret_cast<char*>(
       PartitionRoot<ThreadSafe>::Page::ToPointer(first_super_page_pages[0]));
-  EXPECT_EQ(kPartitionPageSize,
+  EXPECT_EQ(kPartitionPageSize + kReservedTagBitmapSize,
             reinterpret_cast<uintptr_t>(page_base) & kSuperPageOffsetMask);
-  page_base -= kPartitionPageSize;
+  page_base -= kPartitionPageSize - kReservedTagBitmapSize;
   // Map a single system page either side of the mapping for our allocations,
   // with the goal of tripping up alignment of the next mapping.
   void* map1 = AllocPages(
@@ -1275,9 +1308,9 @@
 
   page_base = reinterpret_cast<char*>(
       PartitionRoot<ThreadSafe>::Page::ToPointer(second_super_page_pages[0]));
-  EXPECT_EQ(kPartitionPageSize,
+  EXPECT_EQ(kPartitionPageSize + kReservedTagBitmapSize,
             reinterpret_cast<uintptr_t>(page_base) & kSuperPageOffsetMask);
-  page_base -= kPartitionPageSize;
+  page_base -= kPartitionPageSize - kReservedTagBitmapSize;
   // Map a single system page either side of the mapping for our allocations,
   // with the goal of tripping up alignment of the next mapping.
   map1 = AllocPages(page_base - kPageAllocationGranularity,
@@ -2397,10 +2430,10 @@
     expected_alignment = std::min(expected_alignment, kCookieSize);
 #endif
 #if ENABLE_TAG_FOR_CHECKED_PTR2
-    // When ENABLE_TAG_FOR_CHECKED_PTR2, a kPartitionTagSize is added before
+    // When ENABLE_TAG_FOR_CHECKED_PTR2, a kInSlotTagBufferSize is added before
     // rounding up the allocation size. The returned pointer points after the
     // partition tag.
-    expected_alignment = std::min({expected_alignment, kPartitionTagSize});
+    expected_alignment = std::min({expected_alignment, kInSlotTagBufferSize});
 #endif
     for (int index = 0; index < 3; index++) {
       void* ptr = allocator.root()->Alloc(size, "");
@@ -2456,7 +2489,7 @@
   }
 }
 
-#if ENABLE_TAG_FOR_CHECKED_PTR2
+#if ENABLE_TAG_FOR_CHECKED_PTR2 || ENABLE_TAG_FOR_MTE_CHECKED_PTR
 
 TEST_F(PartitionAllocTest, TagBasic) {
   size_t alloc_size = 64 - kExtraAllocSize;
@@ -2479,9 +2512,9 @@
   EXPECT_EQ(char_ptr1 + page->bucket->slot_size, char_ptr2);
   EXPECT_EQ(char_ptr2 + page->bucket->slot_size, char_ptr3);
 
-  constexpr PartitionTag kTag1 = 0xBADA;
-  constexpr PartitionTag kTag2 = 0xDB8A;
-  constexpr PartitionTag kTag3 = 0xA3C4;
+  constexpr PartitionTag kTag1 = static_cast<PartitionTag>(0xBADA);
+  constexpr PartitionTag kTag2 = static_cast<PartitionTag>(0xDB8A);
+  constexpr PartitionTag kTag3 = static_cast<PartitionTag>(0xA3C4);
   PartitionTagSetValue(ptr1, kTag1);
   PartitionTagSetValue(ptr2, kTag2);
   PartitionTagSetValue(ptr3, kTag3);
@@ -2494,10 +2527,14 @@
   EXPECT_EQ(kTag2, PartitionTagGetValue(ptr2));
   EXPECT_EQ(kTag3, PartitionTagGetValue(ptr3));
 
-  EXPECT_TRUE(!memchr(ptr1, static_cast<uint8_t>(kTag1 >> 8), kTestAllocSize));
   EXPECT_TRUE(!memchr(ptr1, static_cast<uint8_t>(kTag1), kTestAllocSize));
-  EXPECT_TRUE(!memchr(ptr2, static_cast<uint8_t>(kTag2 >> 8), kTestAllocSize));
   EXPECT_TRUE(!memchr(ptr2, static_cast<uint8_t>(kTag2), kTestAllocSize));
+  if (sizeof(PartitionTag) > 1) {
+    EXPECT_TRUE(
+        !memchr(ptr1, static_cast<uint8_t>(kTag1 >> 8), kTestAllocSize));
+    EXPECT_TRUE(
+        !memchr(ptr2, static_cast<uint8_t>(kTag2 >> 8), kTestAllocSize));
+  }
 
   allocator.root()->Free(ptr1);
   EXPECT_EQ(kTag2, PartitionTagGetValue(ptr2));
@@ -2507,7 +2544,11 @@
   EXPECT_EQ(ptr2, new_ptr2);
   EXPECT_EQ(kTag3, PartitionTagGetValue(ptr3));
 
-  request_size = page->bucket->slot_size - kExtraAllocSize + kPartitionTagSize;
+  request_size =
+      page->bucket->slot_size - kExtraAllocSize + kInSlotTagBufferSize;
+#if ENABLE_TAG_FOR_MTE_CHECKED_PTR
+  request_size += kGenericSmallestBucket;
+#endif
   new_ptr2 = allocator.root()->Realloc(ptr2, request_size, type_name);
   EXPECT_TRUE(new_ptr2);
   EXPECT_NE(ptr2, new_ptr2);
@@ -2518,6 +2559,10 @@
   allocator.root()->Free(ptr3);
 }
 
+#endif
+
+#if ENABLE_TAG_FOR_CHECKED_PTR2
+
 TEST_F(PartitionAllocTest, TagForDirectMap) {
   size_t request_size = kGenericMinDirectMappedDownsize + kExtraAllocSize;
   void* ptr1 = allocator.root()->Alloc(request_size, type_name);
diff --git a/base/allocator/partition_allocator/partition_bucket.cc b/base/allocator/partition_allocator/partition_bucket.cc
index 22699ea..b061a96 100644
--- a/base/allocator/partition_allocator/partition_bucket.cc
+++ b/base/allocator/partition_allocator/partition_bucket.cc
@@ -15,7 +15,7 @@
 #include "base/allocator/partition_allocator/partition_direct_map_extent.h"
 #include "base/allocator/partition_allocator/partition_oom.h"
 #include "base/allocator/partition_allocator/partition_page.h"
-
+#include "base/allocator/partition_allocator/partition_tag_bitmap.h"
 #include "base/check.h"
 #include "build/build_config.h"
 
@@ -275,7 +275,10 @@
   //
   // TODO(ajwong): Introduce a DCHECK.
   root->next_super_page = super_page + kSuperPageSize;
-  char* ret = super_page + kPartitionPageSize;
+  // TODO(tasak): Consider starting the bitmap right after metadata to save
+  // space.
+  char* tag_bitmap = super_page + kPartitionPageSize;
+  char* ret = tag_bitmap + kReservedTagBitmapSize;
   root->next_partition_page = ret + total_size;
   root->next_partition_page_end = root->next_super_page - kPartitionPageSize;
   // Make the first partition page in the super page a guard page, but leave a
@@ -286,6 +289,12 @@
   SetSystemPagesAccess(super_page + (kSystemPageSize * 2),
                        kPartitionPageSize - (kSystemPageSize * 2),
                        PageInaccessible);
+  if (kActualTagBitmapSize < kReservedTagBitmapSize) {
+    // Make guard pages between tag bitmap and the first slotspan if possible.
+    SetSystemPagesAccess(tag_bitmap + kActualTagBitmapSize,
+                         kReservedTagBitmapSize - kActualTagBitmapSize,
+                         PageInaccessible);
+  }
   //  SetSystemPagesAccess(super_page + (kSuperPageSize -
   //  kPartitionPageSize),
   //                             kPartitionPageSize, PageInaccessible);
@@ -295,9 +304,11 @@
   //
   // TODO(ajwong): Refactor Page Allocator API so the SuperPage comes in
   // decommited initially.
-  SetSystemPagesAccess(super_page + kPartitionPageSize + total_size,
-                       (kSuperPageSize - kPartitionPageSize - total_size),
-                       PageInaccessible);
+  SetSystemPagesAccess(
+      super_page + kPartitionPageSize + kReservedTagBitmapSize + total_size,
+      (kSuperPageSize - kPartitionPageSize - kReservedTagBitmapSize -
+       total_size),
+      PageInaccessible);
 
   // If we were after a specific address, but didn't get it, assume that
   // the system chose a lousy address. Here most OS'es have a default
diff --git a/base/allocator/partition_allocator/partition_page.h b/base/allocator/partition_allocator/partition_page.h
index 864b164..b5e7f0c 100644
--- a/base/allocator/partition_allocator/partition_page.h
+++ b/base/allocator/partition_allocator/partition_page.h
@@ -251,7 +251,8 @@
 
   // If these asserts fire, you probably corrupted memory.
   if (root->allow_extras) {
-    PartitionCookieCheckValue(reinterpret_cast<char*>(ptr) + kPartitionTagSize);
+    PartitionCookieCheckValue(reinterpret_cast<char*>(ptr) +
+                              kInSlotTagBufferSize);
     PartitionCookieCheckValue(reinterpret_cast<char*>(ptr) + slot_size -
                               kCookieSize);
   }
diff --git a/base/allocator/partition_allocator/partition_tag.h b/base/allocator/partition_allocator/partition_tag.h
index 416c81d9..11bf9b5 100644
--- a/base/allocator/partition_allocator/partition_tag.h
+++ b/base/allocator/partition_allocator/partition_tag.h
@@ -7,42 +7,43 @@
 
 #include "base/allocator/partition_allocator/partition_alloc_constants.h"
 #include "base/allocator/partition_allocator/partition_cookie.h"
+#include "base/allocator/partition_allocator/partition_tag_bitmap.h"
 #include "base/notreached.h"
 #include "build/build_config.h"
 
-#define ENABLE_TAG_FOR_CHECKED_PTR2 0
-
 namespace base {
+
 namespace internal {
 
+#if ENABLE_TAG_FOR_CHECKED_PTR2
+
 // Use 16 bits for the partition tag.
 // TODO(tasak): add a description about the partition tag.
 using PartitionTag = uint16_t;
 
 static constexpr PartitionTag kTagTemporaryInitialValue = 0x0BAD;
 
-#if ENABLE_TAG_FOR_CHECKED_PTR2
-
 // Allocate extra 16 bytes for the partition tag. 14 bytes are unused
 // (reserved).
-static constexpr size_t kPartitionTagSize = 16;
+static constexpr size_t kInSlotTagBufferSize = 16;
 
 #if DCHECK_IS_ON()
 // The layout inside the slot is |tag|cookie|object|(empty)|cookie|.
-static constexpr size_t kPartitionTagOffset = kPartitionTagSize + kCookieSize;
+static constexpr size_t kPartitionTagOffset =
+    kInSlotTagBufferSize + kCookieSize;
 #else
 // The layout inside the slot is |tag|object|(empty)|.
-static constexpr size_t kPartitionTagOffset = kPartitionTagSize;
+static constexpr size_t kPartitionTagOffset = kInSlotTagBufferSize;
 #endif
 
 ALWAYS_INLINE size_t PartitionTagSizeAdjustAdd(size_t size) {
-  PA_DCHECK(size + kPartitionTagSize > size);
-  return size + kPartitionTagSize;
+  PA_DCHECK(size + kInSlotTagBufferSize > size);
+  return size + kInSlotTagBufferSize;
 }
 
 ALWAYS_INLINE size_t PartitionTagSizeAdjustSubtract(size_t size) {
-  PA_DCHECK(size >= kPartitionTagSize);
-  return size - kPartitionTagSize;
+  PA_DCHECK(size >= kInSlotTagBufferSize);
+  return size - kInSlotTagBufferSize;
 }
 
 ALWAYS_INLINE PartitionTag* PartitionTagPointer(void* ptr) {
@@ -52,15 +53,15 @@
 
 ALWAYS_INLINE void* PartitionTagPointerAdjustSubtract(void* ptr) {
   return reinterpret_cast<void*>(reinterpret_cast<char*>(ptr) -
-                                 kPartitionTagSize);
+                                 kInSlotTagBufferSize);
 }
 
 ALWAYS_INLINE void* PartitionTagPointerAdjustAdd(void* ptr) {
   return reinterpret_cast<void*>(reinterpret_cast<char*>(ptr) +
-                                 kPartitionTagSize);
+                                 kInSlotTagBufferSize);
 }
 
-ALWAYS_INLINE void PartitionTagSetValue(void* ptr, uint16_t value) {
+ALWAYS_INLINE void PartitionTagSetValue(void* ptr, PartitionTag value) {
   *PartitionTagPointer(ptr) = value;
 }
 
@@ -68,10 +69,78 @@
   return *PartitionTagPointer(ptr);
 }
 
+ALWAYS_INLINE void PartitionTagClearValue(void* ptr) {
+  PA_DCHECK(PartitionTagGetValue(ptr));
+  *PartitionTagPointer(ptr) = 0;
+}
+
+#elif ENABLE_TAG_FOR_MTE_CHECKED_PTR
+
+// Use 8 bits for the partition tag.
+// TODO(tasak): add a description about the partition tag.
+using PartitionTag = uint8_t;
+
+static_assert(
+    sizeof(PartitionTag) == tag_bitmap::kPartitionTagSize,
+    "sizeof(PartitionTag) must be equal to bitmap::kPartitionTagSize.");
+
+static constexpr PartitionTag kTagTemporaryInitialValue = 0xAD;
+
+static constexpr size_t kInSlotTagBufferSize = 0;
+
+ALWAYS_INLINE size_t PartitionTagSizeAdjustAdd(size_t size) {
+  return size;
+}
+
+ALWAYS_INLINE size_t PartitionTagSizeAdjustSubtract(size_t size) {
+  return size;
+}
+
+ALWAYS_INLINE PartitionTag* PartitionTagPointer(void* ptr) {
+  // See the comment explaining the layout in partition_tag_bitmap.h.
+  uintptr_t pointer_as_uintptr = reinterpret_cast<uintptr_t>(ptr);
+  uintptr_t bitmap_base =
+      (pointer_as_uintptr & kSuperPageBaseMask) + kPartitionPageSize;
+  uintptr_t offset =
+      (pointer_as_uintptr & kSuperPageOffsetMask) - kPartitionPageSize;
+  // Not to depend on partition_address_space.h and PartitionAllocGigaCage
+  // feature, use "offset" to see whether the given ptr is_direct_mapped or not.
+  // DirectMap object should cause this PA_DCHECK's failure, as tags aren't
+  // currently supported there.
+  PA_DCHECK(offset >= kReservedTagBitmapSize);
+  size_t bitmap_offset = (offset - kReservedTagBitmapSize) >>
+                         tag_bitmap::kBytesPerPartitionTagShift
+                             << tag_bitmap::kPartitionTagSizeShift;
+  return reinterpret_cast<PartitionTag* const>(bitmap_base + bitmap_offset);
+}
+
+ALWAYS_INLINE void* PartitionTagPointerAdjustSubtract(void* ptr) {
+  return ptr;
+}
+
+ALWAYS_INLINE void* PartitionTagPointerAdjustAdd(void* ptr) {
+  return ptr;
+}
+
+ALWAYS_INLINE void PartitionTagSetValue(void* ptr, PartitionTag value) {
+  *PartitionTagPointer(ptr) = value;
+}
+
+ALWAYS_INLINE PartitionTag PartitionTagGetValue(void* ptr) {
+  return *PartitionTagPointer(ptr);
+}
+
+ALWAYS_INLINE void PartitionTagClearValue(void* ptr) {
+  PA_DCHECK(PartitionTagGetValue(ptr));
+  *PartitionTagPointer(ptr) = 0;
+}
+
 #else  // !ENABLE_TAG_FOR_CHECKED_PTR2
 
-// No tag added.
-static constexpr size_t kPartitionTagSize = 0;
+using PartitionTag = uint16_t;
+
+static constexpr PartitionTag kTagTemporaryInitialValue = 0;
+static constexpr size_t kInSlotTagBufferSize = 0;
 
 ALWAYS_INLINE size_t PartitionTagSizeAdjustAdd(size_t size) {
   return size;
@@ -94,12 +163,14 @@
   return ptr;
 }
 
-ALWAYS_INLINE void PartitionTagSetValue(void*, uint16_t) {}
+ALWAYS_INLINE void PartitionTagSetValue(void*, PartitionTag) {}
 
 ALWAYS_INLINE PartitionTag PartitionTagGetValue(void*) {
   return 0;
 }
 
+ALWAYS_INLINE void PartitionTagClearValue(void* ptr) {}
+
 #endif  // !ENABLE_TAG_FOR_CHECKED_PTR2
 
 }  // namespace internal
diff --git a/base/allocator/partition_allocator/partition_tag_bitmap.h b/base/allocator/partition_allocator/partition_tag_bitmap.h
new file mode 100644
index 0000000..69740b4a
--- /dev/null
+++ b/base/allocator/partition_allocator/partition_tag_bitmap.h
@@ -0,0 +1,147 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_TAG_BITMAP_H_
+#define BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_TAG_BITMAP_H_
+
+#include "base/allocator/partition_allocator/partition_alloc_constants.h"
+
+#define ENABLE_TAG_FOR_CHECKED_PTR2 0
+#define ENABLE_TAG_FOR_MTE_CHECKED_PTR 0
+
+namespace base {
+
+namespace internal {
+
+#if ENABLE_TAG_FOR_MTE_CHECKED_PTR
+#undef ENABLE_TAG_FOR_CHECKED_PTR2
+
+// Normal bucket layout
+// +----------------+ super_page_base
+// | PartitionPage  |
+// | (Meta+Guard)   |
+// +----------------+ super_page_base + kPartitionPageSize (=bitmap_base)
+// |  TagBitmap     |
+// ....
+// +- - - - - - - - + bitmap_base + kActualTagBitmapSize
+// | guard pages(*) | (kActualTagBitmapSize is kSystemPageSize-aligned.)
+// +----------------+ bitmap_base + kReservedTagBitmapSize
+// |   Slot Span    | (kReservedTagBitmapSize is kPartitionPageSize-aligned.)
+// ....
+// ....
+// +----------------+
+// |   Slot Span    |
+// ....
+// ....
+// +----------------+
+// | PartitionPage  |
+// |  (GuardPage)   |
+// +----------------+ super_page_base + kSuperPageSize
+// (*) If kActualTagBitmapSize < kReservedTagBitmapSize, the
+// unused pages are guard pages. This depends on sizeof(PartitionTag).
+// TODO(tasak): Consider guaranteeing guard pages after the tag bitmap, if
+// needed.
+
+namespace tag_bitmap {
+// kPartitionTagSize should be equal to sizeof(PartitionTag).
+// PartitionTag is defined in partition_tag.h and static_assert there
+// checks the condition.
+static constexpr size_t kPartitionTagSizeShift = 0;
+static constexpr size_t kPartitionTagSize = 1U << kPartitionTagSizeShift;
+
+static constexpr size_t kBytesPerPartitionTagShift = 4;
+// One partition tag is assigned per |kBytesPerPartitionTag| bytes in the slot
+// spans.
+//  +-----------+ 0
+//  |           |  ====> 1 partition tag
+//  +-----------+ kBytesPerPartitionTag
+//  |           |  ====> 1 partition tag
+//  +-----------+ 2*kBytesPerPartitionTag
+// ...
+//  +-----------+ slot_size
+static constexpr size_t kBytesPerPartitionTag = 1U
+                                                << kBytesPerPartitionTagShift;
+
+static constexpr size_t kBytesPerPartitionTagRatio =
+    kBytesPerPartitionTag / kPartitionTagSize;
+
+static_assert(kBytesPerPartitionTag > 0,
+              "kBytesPerPartitionTag should be larger than 0");
+static_assert(
+    kBytesPerPartitionTag % kPartitionTagSize == 0,
+    "kBytesPerPartitionTag should be multiples of sizeof(PartitionTag).");
+
+constexpr size_t CeilCountOfUnits(size_t size, size_t unit_size) {
+  return (size + unit_size - 1) / unit_size;
+}
+
+}  // namespace tag_bitmap
+
+// kTagBitmapSize is calculated in the following way:
+// (1) kSuperPageSize - 2 * kPartitionPageSize = kTagBitmapSize + kSlotSpanSize
+// (2) kTagBitmapSize >= kSlotSpanSize / kBytesPerPartitionTag *
+// sizeof(PartitionTag)
+//--
+// (1)' kSlotSpanSize = kSuperPageSize - 2 * kPartitionPageSize - kTagBitmapSize
+// (2)' kSlotSpanSize <= kTagBitmapSize * Y
+// (3)' Y = kBytesPerPartitionTag / sizeof(PartitionTag) =
+// kBytesPerPartitionTagRatio
+//
+//   kTagBitmapSize * Y >= kSuperPageSize - 2 * kPartitionPageSize -
+//   kTagBitmapSize (1 + Y) * kTagBimapSize >= kSuperPageSize - 2 *
+//   kPartitionPageSize
+// Finally,
+//   kTagBitmapSize >= (kSuperPageSize - 2 * kPartitionPageSize) / (1 + Y)
+static constexpr size_t kNumPartitionPagesPerTagBitmap =
+    tag_bitmap::CeilCountOfUnits(kSuperPageSize / kPartitionPageSize - 2,
+                                 tag_bitmap::kBytesPerPartitionTagRatio + 1);
+
+// To make guard pages between the tag bitmap and the slot span, calculate the
+// number of SystemPages of TagBitmap. If kNumSystemPagesPerTagBitmap *
+// kSystemPageSize < kTagBitmapSize, guard pages will be created. (c.f. no guard
+// pages if sizeof(PartitionTag) == 2.)
+static constexpr size_t kNumSystemPagesPerTagBitmap =
+    tag_bitmap::CeilCountOfUnits(kSuperPageSize / kSystemPageSize -
+                                     2 * kPartitionPageSize / kSystemPageSize,
+                                 tag_bitmap::kBytesPerPartitionTagRatio + 1);
+
+static constexpr size_t kActualTagBitmapSize =
+    kNumSystemPagesPerTagBitmap * kSystemPageSize;
+
+// PartitionPageSize-aligned tag bitmap size.
+static constexpr size_t kReservedTagBitmapSize =
+    kPartitionPageSize * kNumPartitionPagesPerTagBitmap;
+
+static_assert(kActualTagBitmapSize <= kReservedTagBitmapSize,
+              "kActualTagBitmapSize should be smaller than or equal to "
+              "kReservedTagBitmapSize.");
+static_assert(
+    kReservedTagBitmapSize - kActualTagBitmapSize < kPartitionPageSize,
+    "Unused space in the tag bitmap should be smaller than kPartitionPageSize");
+
+// The region available for slot spans is the reminder of the super page, after
+// taking away the first and last partition page (for metadata and guard pages)
+// and partition pages reserved for the tag bitmap.
+static constexpr size_t kSlotSpansSize =
+    kSuperPageSize - 2 * kPartitionPageSize - kReservedTagBitmapSize;
+static_assert(kActualTagBitmapSize * tag_bitmap::kBytesPerPartitionTagRatio >=
+                  kSlotSpansSize,
+              "bitmap is large enough to cover slot spans");
+static_assert((kActualTagBitmapSize - kPartitionPageSize) *
+                      tag_bitmap::kBytesPerPartitionTagRatio <
+                  kSlotSpansSize,
+              "any smaller bitmap wouldn't suffice to cover slots spans");
+
+#else  // !ENABLE_TAG_FOR_MTE_CHECKED_PTR
+
+static constexpr size_t kNumPartitionPagesPerTagBitmap = 0;
+static constexpr size_t kActualTagBitmapSize = 0;
+static constexpr size_t kReservedTagBitmapSize = 0;
+
+#endif
+
+}  // namespace internal
+}  // namespace base
+
+#endif  // BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_TAG_CONSTANTS_H_
diff --git a/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java b/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java
index 9f4ff593..76186054 100644
--- a/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java
+++ b/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java
@@ -23,8 +23,6 @@
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.graphics.drawable.TransitionDrawable;
 import android.hardware.display.DisplayManager;
 import android.net.Uri;
 import android.os.Build;
@@ -68,7 +66,7 @@
 import java.util.List;
 
 /**
- * Utility class to use new APIs that were added after KitKat (API level 19).
+ * Utility class to use APIs not in all supported Android versions.
  *
  * Do not inline because we use many new APIs, and if they are inlined, they could cause dex
  * validation errors on low Android versions.
@@ -670,32 +668,6 @@
     }
 
     /**
-     * Creates regular LayerDrawable on Android L+. On older versions creates a helper class that
-     * fixes issues around {@link LayerDrawable#mutate()}. See https://crbug.com/890317 for details.
-     * See also {@link #createTransitionDrawable}.
-     * @param layers A list of drawables to use as layers in this new drawable.
-     */
-    public static LayerDrawable createLayerDrawable(@NonNull Drawable[] layers) {
-        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
-            return new LayerDrawableCompat(layers);
-        }
-        return new LayerDrawable(layers);
-    }
-
-    /**
-     * Creates regular TransitionDrawable on Android L+. On older versions creates a helper class
-     * that fixes issues around {@link TransitionDrawable#mutate()}. See https://crbug.com/892061
-     * for details. See also {@link #createLayerDrawable}.
-     * @param layers A list of drawables to use as layers in this new drawable.
-     */
-    public static TransitionDrawable createTransitionDrawable(@NonNull Drawable[] layers) {
-        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
-            return new TransitionDrawableCompat(layers);
-        }
-        return new TransitionDrawable(layers);
-    }
-
-    /**
      * Adds a content description to the provided EditText password field on versions of Android
      * where the hint text is not used for accessibility. Does nothing if the EditText field does
      * not have a password input type or the hint text is empty.  See https://crbug.com/911762.
@@ -742,80 +714,6 @@
         return false;
     }
 
-    private static class LayerDrawableCompat extends LayerDrawable {
-        private boolean mMutated;
-
-        LayerDrawableCompat(@NonNull Drawable[] layers) {
-            super(layers);
-        }
-
-        @NonNull
-        @Override
-        public Drawable mutate() {
-            // LayerDrawable in Android K loses bounds of layers, so this method works around that.
-            if (mMutated) {
-                // This object has already been mutated and shouldn't have any shared state.
-                return this;
-            }
-
-            Rect[] oldBounds = getLayersBounds(this);
-            Drawable superResult = super.mutate();
-            // LayerDrawable.mutate() always returns this, bail out if this isn't the case.
-            if (superResult != this) return superResult;
-            restoreLayersBounds(this, oldBounds);
-            mMutated = true;
-            return this;
-        }
-    }
-
-    private static class TransitionDrawableCompat extends TransitionDrawable {
-        private boolean mMutated;
-
-        TransitionDrawableCompat(@NonNull Drawable[] layers) {
-            super(layers);
-        }
-
-        @NonNull
-        @Override
-        public Drawable mutate() {
-            // LayerDrawable in Android K loses bounds of layers, so this method works around that.
-            if (mMutated) {
-                // This object has already been mutated and shouldn't have any shared state.
-                return this;
-            }
-            Rect[] oldBounds = getLayersBounds(this);
-            Drawable superResult = super.mutate();
-            // TransitionDrawable.mutate() always returns this, bail out if this isn't the case.
-            if (superResult != this) return superResult;
-            restoreLayersBounds(this, oldBounds);
-            mMutated = true;
-            return this;
-        }
-    }
-
-    /**
-     * Helper for {@link LayerDrawableCompat#mutate} and {@link TransitionDrawableCompat#mutate}.
-     * Obtains the bounds of layers so they can be restored after a mutation.
-     */
-    private static Rect[] getLayersBounds(LayerDrawable layerDrawable) {
-        Rect[] result = new Rect[layerDrawable.getNumberOfLayers()];
-        for (int i = 0; i < layerDrawable.getNumberOfLayers(); i++) {
-            result[i] = layerDrawable.getDrawable(i).getBounds();
-        }
-        return result;
-    }
-
-    /**
-     * Helper for {@link LayerDrawableCompat#mutate} and {@link TransitionDrawableCompat#mutate}.
-     * Restores the bounds of layers after a mutation.
-     */
-    private static void restoreLayersBounds(LayerDrawable layerDrawable, Rect[] oldBounds) {
-        assert layerDrawable.getNumberOfLayers() == oldBounds.length;
-        for (int i = 0; i < layerDrawable.getNumberOfLayers(); i++) {
-            layerDrawable.getDrawable(i).setBounds(oldBounds[i]);
-        }
-    }
-
     /**
      * Retrieves an image for the given url as a Bitmap.
      */
diff --git a/base/android/java/src/org/chromium/base/CallbackController.java b/base/android/java/src/org/chromium/base/CallbackController.java
new file mode 100644
index 0000000..cb55ce0c
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/CallbackController.java
@@ -0,0 +1,225 @@
+// Copyright 2020 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.base;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * Class allowing to wrap lambdas, such as {@link Callback} or {@link Runnable} with a cancelable
+ * version of the same, and cancel them in bulk when {@link #destroy()} is called. Use an instance
+ * of this class to wrap lambdas passed to other objects, and later use {@link #destroy()} to
+ * prevent future invocations of these lambdas.
+ * <p>
+ * Example usage:
+ * {@code
+ * public class Foo {
+ *    private CallbackController mCallbackController = new CallbackController();
+ *    private SomeDestructibleClass mDestructible = new SomeDestructibleClass();
+ *
+ *    // Classic destroy, with clean up of cancelables.
+ *    public void destroy() {
+ *        // This call makes sure all tracked lambdas are destroyed.
+ *        // It is recommended to be done at the top of the destroy methods, to ensure calls from
+ *        // other threads don't use already destroyed resources.
+ *        if (mCallbackController != null) {
+ *            mCallbackController.destroy();
+ *            mCallbackController = null;
+ *        }
+ *
+ *        if (mDestructible != null) {
+ *            mDestructible.destroy();
+ *            mDestructible = null;
+ *        }
+ *    }
+ *
+ *    // Sets up Bar instance by providing it with a set of dangerous callbacks all of which could
+ *    // cause a NullPointerException if invoked after destroy().
+ *    public void setUpBar(Bar bar) {
+ *        // Notice all callbacks below would fail post destroy, if they were not canceled.
+ *        bar.setDangerousLambda(mCallbackController.makeCancelable(() -> mDestructible.method()));
+ *        bar.setDangerousRunnable(mCallbackController.makeCancelable(this::dangerousRunnable));
+ *        bar.setDangerousOtherCallback(
+ *                mCallbackController.makeCancelable(baz -> mDestructible.setBaz(baz)));
+ *        bar.setDangerousCallback(mCallbackController.makeCancelable(this::setBaz));
+ *    }
+ *
+ *    private void dangerousRunnable() {
+ *        mDestructible.method();
+ *    }
+ *
+ *    private void setBaz(Baz baz) {
+ *        mDestructible.setBaz(baz);
+ *    }
+ * }
+ * }
+ * <p>
+ * It does not matter if the lambda is intended to be invoked once or more times, as it is only
+ * weakly referred from this class. When the lambda is no longer needed, it can be safely garbage
+ * collected. All invocations after {@link #destroy()} will be ignored.
+ * <p>
+ * Each instance of this class in only meant for a single {@link
+ * #destroy()} call. After it is destroyed, the owning class should create a new instance instead:
+ * {@code
+ *    // Somewhere inside Foo.
+ *    mCallbackController.destroy();  // Invalidates all current callbacks.
+ *    mCallbackController = new CallbackController();  // Allows to start handing out new callbacks.
+ * }
+ */
+public final class CallbackController {
+    /** Interface for cancelable objects tracked by this class. */
+    private interface Cancelable {
+        /** Cancels the object, preventing its execution, when triggered. */
+        void cancel();
+    }
+
+    /** Class wrapping a {@link Callback} interface with a {@link Cancelable} interface. */
+    private class CancelableCallback<T> implements Cancelable, Callback<T> {
+        @GuardedBy("mReadWriteLock")
+        private Callback<T> mCallback;
+
+        private CancelableCallback(@NonNull Callback<T> callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void cancel() {
+            mCallback = null;
+        }
+
+        @Override
+        public void onResult(T result) {
+            // Guarantees the cancelation is not going to happen, while callback is executed by
+            // another thread.
+            try (AutoCloseableLock acl = AutoCloseableLock.lock(mReadWriteLock.readLock())) {
+                if (mCallback != null) mCallback.onResult(result);
+            }
+        }
+    }
+
+    /** Class wrapping {@link Runnable} interface with a {@link Cancelable} interface. */
+    private class CancelableRunnable implements Cancelable, Runnable {
+        @GuardedBy("mReadWriteLock")
+        private Runnable mRunnable;
+
+        private CancelableRunnable(@NonNull Runnable runnable) {
+            mRunnable = runnable;
+        }
+
+        @Override
+        public void cancel() {
+            mRunnable = null;
+        }
+
+        @Override
+        public void run() {
+            // Guarantees the cancelation is not going to happen, while runnable is executed by
+            // another thread.
+            try (AutoCloseableLock acl = AutoCloseableLock.lock(mReadWriteLock.readLock())) {
+                if (mRunnable != null) mRunnable.run();
+            }
+        }
+    }
+
+    /** Class wrapping the locking logic to reduce repetitive code. */
+    private static class AutoCloseableLock implements AutoCloseable {
+        private final Lock mLock;
+        private boolean mIsLocked;
+
+        private AutoCloseableLock(Lock lock, boolean isLocked) {
+            mLock = lock;
+            mIsLocked = isLocked;
+        }
+
+        static AutoCloseableLock lock(Lock l) {
+            l.lock();
+            return new AutoCloseableLock(l, true);
+        }
+
+        @Override
+        public void close() {
+            if (!mIsLocked) throw new IllegalStateException("mLock isn't locked.");
+            mIsLocked = false;
+            mLock.unlock();
+        }
+    }
+
+    /** A list of cancelables created and cancelable by this object. */
+    @Nullable
+    @GuardedBy("mReadWriteLock")
+    private ArrayList<WeakReference<Cancelable>> mCancelables = new ArrayList<>();
+
+    /** Ensures thread safety of creating cancelables and canceling them. */
+    private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock(/*fair=*/true);
+
+    /**
+     * Wraps a provided {@link Callback} with a cancelable object that is tracked by this
+     * {@link CallbackController}. To cancel a resulting wrapped instance destroy the host.
+     * <p>
+     * This method must not be called after {@link #destroy()}.
+     *
+     * @param <T> The type of the callback result.
+     * @param callback A callback that will be made cancelable.
+     * @return A cancelable instance of the callback.
+     */
+    public <T> Callback<T> makeCancelable(@NonNull Callback<T> callback) {
+        try (AutoCloseableLock acl = AutoCloseableLock.lock(mReadWriteLock.writeLock())) {
+            checkNotCanceled();
+            CancelableCallback<T> cancelable = new CancelableCallback<>(callback);
+            mCancelables.add(new WeakReference<>(cancelable));
+            return cancelable;
+        }
+    }
+
+    /**
+     * Wraps a provided {@link Runnable} with a cancelable object that is tracked by this
+     * {@link CallbackController}. To cancel a resulting wrapped instance destroy the host.
+     * <p>
+     * This method must not be called after {@link #destroy()}.
+     *
+     * @param runnable A runnable that will be made cancelable.
+     * @return A cancelable instance of the runnable.
+     */
+    public Runnable makeCancelable(@NonNull Runnable runnable) {
+        try (AutoCloseableLock acl = AutoCloseableLock.lock(mReadWriteLock.writeLock())) {
+            checkNotCanceled();
+            CancelableRunnable cancelable = new CancelableRunnable(runnable);
+            mCancelables.add(new WeakReference<>(cancelable));
+            return cancelable;
+        }
+    }
+
+    /**
+     * Cancels all of the cancelables that have not been garbage collected yet.
+     * <p>
+     * This method must only be called once and makes the instance unusable afterwards.
+     */
+    public void destroy() {
+        try (AutoCloseableLock acl = AutoCloseableLock.lock(mReadWriteLock.writeLock())) {
+            checkNotCanceled();
+            for (WeakReference<Cancelable> cancelableWeakReference : mCancelables) {
+                Cancelable cancelable = cancelableWeakReference.get();
+                if (cancelable != null) cancelable.cancel();
+            }
+            mCancelables = null;
+        }
+    }
+
+    /** If the cancelation already happened, throws an {@link IllegalStateException}. */
+    @GuardedBy("mReadWriteLock")
+    private void checkNotCanceled() {
+        if (mCancelables == null) {
+            throw new IllegalStateException("This CallbackController has already been destroyed.");
+        }
+    }
+}
diff --git a/base/android/junit/src/org/chromium/base/CallbackControllerTest.java b/base/android/junit/src/org/chromium/base/CallbackControllerTest.java
new file mode 100644
index 0000000..17c4d51a
--- /dev/null
+++ b/base/android/junit/src/org/chromium/base/CallbackControllerTest.java
@@ -0,0 +1,113 @@
+// Copyright 2020 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.base;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+/**
+ * Test class for {@link CallbackController}, which also describes typical usage.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class CallbackControllerTest {
+    private boolean mExecutionCompleted;
+
+    @Test
+    public void testInstanceCallback() {
+        CallbackController mCallbackController = new CallbackController();
+        Callback<Boolean> wrapped = mCallbackController.makeCancelable(this::setExecutionCompleted);
+        mExecutionCompleted = false;
+        wrapped.onResult(true);
+        assertTrue(mExecutionCompleted);
+
+        // Execution possible multiple times.
+        mExecutionCompleted = false;
+        wrapped.onResult(true);
+        assertTrue(mExecutionCompleted);
+
+        // Won't trigger after CallbackController is destroyed.
+        mExecutionCompleted = false;
+        mCallbackController.destroy();
+        wrapped.onResult(true);
+        assertFalse(mExecutionCompleted);
+    }
+
+    @Test
+    public void testlInstanceRunnable() {
+        CallbackController mCallbackController = new CallbackController();
+        Runnable wrapped = mCallbackController.makeCancelable(this::completeExection);
+        mExecutionCompleted = false;
+        wrapped.run();
+        assertTrue(mExecutionCompleted);
+
+        // Execution possible multiple times.
+        mExecutionCompleted = false;
+        wrapped.run();
+        assertTrue(mExecutionCompleted);
+
+        // Won't trigger after CallbackController is destroyed.
+        mExecutionCompleted = false;
+        mCallbackController.destroy();
+        wrapped.run();
+        assertFalse(mExecutionCompleted);
+    }
+
+    @Test
+    public void testLambdaCallback() {
+        CallbackController mCallbackController = new CallbackController();
+        Callback<Boolean> wrapped =
+                mCallbackController.makeCancelable(value -> setExecutionCompleted(value));
+        mExecutionCompleted = false;
+        wrapped.onResult(true);
+        assertTrue(mExecutionCompleted);
+
+        // Execution possible multiple times.
+        mExecutionCompleted = false;
+        wrapped.onResult(true);
+        assertTrue(mExecutionCompleted);
+
+        // Won't trigger after CallbackController is destroyed.
+        mExecutionCompleted = false;
+        mCallbackController.destroy();
+        wrapped.onResult(true);
+        assertFalse(mExecutionCompleted);
+    }
+
+    @Test
+    public void testLambdaRunnable() {
+        Runnable runnable = () -> setExecutionCompleted(true);
+        CallbackController mCallbackController = new CallbackController();
+        Runnable wrapped = mCallbackController.makeCancelable(() -> completeExection());
+        mExecutionCompleted = false;
+        wrapped.run();
+        assertTrue(mExecutionCompleted);
+
+        // Execution possible multiple times.
+        mExecutionCompleted = false;
+        wrapped.run();
+        assertTrue(mExecutionCompleted);
+
+        // Won't trigger after CallbackController is destroyed.
+        mExecutionCompleted = false;
+        mCallbackController.destroy();
+        wrapped.run();
+        assertFalse(mExecutionCompleted);
+    }
+
+    private void completeExection() {
+        setExecutionCompleted(true);
+    }
+
+    private void setExecutionCompleted(boolean completed) {
+        mExecutionCompleted = completed;
+    }
+}
diff --git a/base/files/file_util_posix.cc b/base/files/file_util_posix.cc
index 837dff8..091d9ef 100644
--- a/base/files/file_util_posix.cc
+++ b/base/files/file_util_posix.cc
@@ -1220,7 +1220,7 @@
 
   ScopedFD fd = CreateAndOpenFdForTemporaryFileInDir(path, &tmp_file_path);
   if (fd.is_valid()) {
-    DeleteFile(tmp_file_path, false);
+    DeleteFile(tmp_file_path);
     long sysconf_result = sysconf(_SC_PAGESIZE);
     CHECK_GE(sysconf_result, 0);
     size_t pagesize = static_cast<size_t>(sysconf_result);
diff --git a/base/metrics/histogram.cc b/base/metrics/histogram.cc
index 4ac5b0f..a938953 100644
--- a/base/metrics/histogram.cc
+++ b/base/metrics/histogram.cc
@@ -1084,19 +1084,12 @@
   CHECK_EQ(static_cast<Sample>(bucket_count), maximum - minimum + 2)
       << " ScaledLinearHistogram requires buckets of size 1";
 
-  // Normally, |histogram_| should have type LINEAR_HISTOGRAM or be
-  // inherited from it. However, if it's expired, it will be DUMMY_HISTOGRAM.
-  if (histogram_->GetHistogramType() == DUMMY_HISTOGRAM)
-    return;
-
   remainders_.resize(histogram_->bucket_count(), 0);
 }
 
 ScaledLinearHistogram::~ScaledLinearHistogram() = default;
 
 void ScaledLinearHistogram::AddScaledCount(Sample value, int count) {
-  if (histogram_->GetHistogramType() == DUMMY_HISTOGRAM)
-    return;
   if (count == 0)
     return;
   if (count < 0) {
diff --git a/base/metrics/histogram_unittest.cc b/base/metrics/histogram_unittest.cc
index 7ab3302..d7addaa 100644
--- a/base/metrics/histogram_unittest.cc
+++ b/base/metrics/histogram_unittest.cc
@@ -872,13 +872,6 @@
   samples = linear_expired->SnapshotDelta();
   EXPECT_EQ(0, samples->TotalCount());
 
-  ScaledLinearHistogram scaled_linear_expired(kExpiredHistogramName, 1, 5, 6,
-                                              100, HistogramBase::kNoFlags);
-  scaled_linear_expired.AddScaledCount(0, 1);
-  scaled_linear_expired.AddScaledCount(1, 49);
-  samples = scaled_linear_expired.histogram()->SnapshotDelta();
-  EXPECT_EQ(0, samples->TotalCount());
-
   std::vector<int> custom_ranges;
   custom_ranges.push_back(1);
   custom_ranges.push_back(5);
diff --git a/base/task/thread_pool/job_task_source.cc b/base/task/thread_pool/job_task_source.cc
index a9427db..1e6b04c 100644
--- a/base/task/thread_pool/job_task_source.cc
+++ b/base/task/thread_pool/job_task_source.cc
@@ -346,7 +346,7 @@
     // |assigned_task_ids|.
     task_id = bits::CountTrailingZeroBits(~assigned_task_ids);
     new_assigned_task_ids = assigned_task_ids | (uint32_t(1) << task_id);
-  } while (assigned_task_ids_.compare_exchange_weak(
+  } while (!assigned_task_ids_.compare_exchange_weak(
       assigned_task_ids, new_assigned_task_ids, std::memory_order_relaxed));
   return task_id;
 }
diff --git a/base/task/thread_pool/job_task_source_unittest.cc b/base/task/thread_pool/job_task_source_unittest.cc
index 6f353af4..50e0715 100644
--- a/base/task/thread_pool/job_task_source_unittest.cc
+++ b/base/task/thread_pool/job_task_source_unittest.cc
@@ -476,13 +476,7 @@
   EXPECT_DCHECK_DEATH(registered_task_source.DidProcessTask());
 }
 
-// Disabled on Android, see https://crbug.com/1104240.
-#if defined(OS_ANDROID)
-#define MAYBE_AcquireTaskId DISABLED_AcquireTaskId
-#else
-#define MAYBE_AcquireTaskId AcquireTaskId
-#endif
-TEST_F(ThreadPoolJobTaskSourceTest, MAYBE_AcquireTaskId) {
+TEST_F(ThreadPoolJobTaskSourceTest, AcquireTaskId) {
   auto job_task =
       base::MakeRefCounted<test::MockJobTask>(DoNothing(),
                                               /* num_tasks_to_run */ 4);
diff --git a/base/test/test_suite.cc b/base/test/test_suite.cc
index 89046d3..00bde46 100644
--- a/base/test/test_suite.cc
+++ b/base/test/test_suite.cc
@@ -480,11 +480,6 @@
   check_for_thread_and_process_priority_ = false;
 }
 
-void TestSuite::DisableCheckForThreadPriorityAtTestEnd() {
-  DCHECK(!is_initialized_);
-  check_for_thread_priority_at_test_end_ = false;
-}
-
 void TestSuite::UnitTestAssertHandler(const char* file,
                                       int line,
                                       const StringPiece summary,
@@ -647,11 +642,6 @@
   if (check_for_leaked_globals_)
     listeners.Append(new CheckForLeakedGlobals);
   if (check_for_thread_and_process_priority_) {
-#if !defined(OS_ANDROID)
-    // TODO(https://crbug.com/931706): Check thread priority on Android.
-    listeners.Append(
-        new CheckThreadPriority(check_for_thread_priority_at_test_end_));
-#endif
 #if !defined(OS_IOS)
     listeners.Append(new CheckProcessPriority);
 #endif
diff --git a/base/test/test_suite.h b/base/test/test_suite.h
index ffad1c77..ed63073 100644
--- a/base/test/test_suite.h
+++ b/base/test/test_suite.h
@@ -54,12 +54,6 @@
   // each test. Most tests should not use this.
   void DisableCheckForThreadAndProcessPriority();
 
-  // Disables checks for thread priority at the end of each test (still checks
-  // at the beginning of each test). This should be used for tests that run in
-  // their own process and should start with normal priorities but are allowed
-  // to end with different priorities.
-  void DisableCheckForThreadPriorityAtTestEnd();
-
   // Disables checks for certain global objects being leaked across tests.
   void DisableCheckForLeakedGlobals();
 
@@ -108,8 +102,6 @@
 
   bool check_for_leaked_globals_ = true;
   bool check_for_thread_and_process_priority_ = true;
-  bool check_for_thread_priority_at_test_end_ = true;
-
   bool is_initialized_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(TestSuite);
diff --git a/base/threading/thread_restrictions.h b/base/threading/thread_restrictions.h
index 29a68a6..fef2b97 100644
--- a/base/threading/thread_restrictions.h
+++ b/base/threading/thread_restrictions.h
@@ -187,6 +187,14 @@
 namespace leveldb_env {
 class DBTracker;
 }
+namespace location {
+namespace nearby {
+namespace chrome {
+class ScheduledExecutor;
+class SubmittableExecutor;
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
 namespace media {
 class AudioInputDevice;
 class AudioOutputDevice;
@@ -448,6 +456,8 @@
   friend class history_report::HistoryReportJniBridge;
   friend class internal::TaskTracker;
   friend class leveldb_env::DBTracker;
+  friend class location::nearby::chrome::ScheduledExecutor;
+  friend class location::nearby::chrome::SubmittableExecutor;
   friend class media::BlockingUrlProtocol;
   friend class mojo::core::ScopedIPCSupport;
   friend class net::MultiThreadedCertVerifierScopedAllowBaseSyncPrimitives;
diff --git a/base/time/time.h b/base/time/time.h
index ffec09e..707b67e 100644
--- a/base/time/time.h
+++ b/base/time/time.h
@@ -151,6 +151,9 @@
 #if defined(OS_FUCHSIA)
   static TimeDelta FromZxDuration(zx_duration_t nanos);
 #endif
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+  static TimeDelta FromMachTime(uint64_t mach_time);
+#endif  // defined(OS_MACOSX) && !defined(OS_IOS)
 
   // From Go's doc at https://golang.org/pkg/time/#ParseDuration
   //   [ParseDuration] parses a duration string. A duration string is
diff --git a/base/time/time_mac.cc b/base/time/time_mac.cc
index eda4f20..52b06f37 100644
--- a/base/time/time_mac.cc
+++ b/base/time/time_mac.cc
@@ -32,7 +32,7 @@
 namespace {
 
 #if defined(OS_MACOSX) && !defined(OS_IOS)
-int64_t MachAbsoluteTimeToTicks(uint64_t mach_absolute_time) {
+int64_t MachTimeToMicroseconds(uint64_t mach_time) {
   static mach_timebase_info_data_t timebase_info;
   if (timebase_info.denom == 0) {
     // Zero-initialization of statics guarantees that denom will be 0 before
@@ -46,7 +46,7 @@
 
   // timebase_info converts absolute time tick units into nanoseconds.  Convert
   // to microseconds up front to stave off overflows.
-  base::CheckedNumeric<uint64_t> result(mach_absolute_time /
+  base::CheckedNumeric<uint64_t> result(mach_time /
                                         base::Time::kNanosecondsPerMicrosecond);
   result *= timebase_info.numer;
   result /= timebase_info.denom;
@@ -90,7 +90,7 @@
   // mach_absolute_time is it when it comes to ticks on the Mac.  Other calls
   // with less precision (such as TickCount) just call through to
   // mach_absolute_time.
-  return MachAbsoluteTimeToTicks(mach_absolute_time());
+  return MachTimeToMicroseconds(mach_absolute_time());
 #endif  // defined(OS_IOS)
 }
 
@@ -175,6 +175,15 @@
          kCFAbsoluteTimeIntervalSince1970;
 }
 
+// TimeDelta ------------------------------------------------------------------
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+// static
+TimeDelta TimeDelta::FromMachTime(uint64_t mach_time) {
+  return TimeDelta::FromMicroseconds(MachTimeToMicroseconds(mach_time));
+}
+#endif  // defined(OS_MACOSX) && !defined(OS_IOS)
+
 // TimeTicks ------------------------------------------------------------------
 
 namespace subtle {
@@ -196,7 +205,7 @@
 #if defined(OS_MACOSX) && !defined(OS_IOS)
 // static
 TimeTicks TimeTicks::FromMachAbsoluteTime(uint64_t mach_absolute_time) {
-  return TimeTicks(MachAbsoluteTimeToTicks(mach_absolute_time));
+  return TimeTicks(MachTimeToMicroseconds(mach_absolute_time));
 }
 #endif  // defined(OS_MACOSX) && !defined(OS_IOS)
 
diff --git a/base/util/ranges/BUILD.gn b/base/util/ranges/BUILD.gn
index 455c47d..f13afe9 100644
--- a/base/util/ranges/BUILD.gn
+++ b/base/util/ranges/BUILD.gn
@@ -7,6 +7,7 @@
     "algorithm.h",
     "functional.h",
     "iterator.h",
+    "ranges.h",
   ]
 }
 
diff --git a/base/util/ranges/algorithm.h b/base/util/ranges/algorithm.h
index 5fe39597..3b9ce44 100644
--- a/base/util/ranges/algorithm.h
+++ b/base/util/ranges/algorithm.h
@@ -7,10 +7,12 @@
 
 #include <algorithm>
 #include <iterator>
+#include <type_traits>
 #include <utility>
 
 #include "base/util/ranges/functional.h"
 #include "base/util/ranges/iterator.h"
+#include "base/util/ranges/ranges.h"
 
 namespace util {
 namespace ranges {
@@ -49,6 +51,14 @@
 using iterator_category_t =
     typename std::iterator_traits<Iter>::iterator_category;
 
+// This alias is used below to restrict range based APIs to types for which
+// `iterator_category` is defined for the underlying iterator. This is required
+// in situations where otherwise an undesired overload would be chosen, e.g.
+// transform. In spirit this is similar to C++20's std::ranges::range, a concept
+// that each range should satisfy.
+template <typename Range>
+using range_category_t = iterator_category_t<iterator_t<Range>>;
+
 }  // namespace internal
 
 // [alg.nonmodifying] Non-modifying sequence operations
@@ -87,7 +97,10 @@
 // projection.
 //
 // Reference: https://wg21.link/alg.all.of#:~:text=ranges::all_of(R
-template <typename Range, typename Pred, typename Proj = identity>
+template <typename Range,
+          typename Pred,
+          typename Proj = identity,
+          typename = internal::range_category_t<Range>>
 constexpr bool all_of(Range&& range, Pred pred, Proj proj = {}) {
   return ranges::all_of(ranges::begin(range), ranges::end(range),
                         std::move(pred), std::move(proj));
@@ -126,7 +139,10 @@
 // projection.
 //
 // Reference: https://wg21.link/alg.any.of#:~:text=ranges::any_of(R
-template <typename Range, typename Pred, typename Proj = identity>
+template <typename Range,
+          typename Pred,
+          typename Proj = identity,
+          typename = internal::range_category_t<Range>>
 constexpr bool any_of(Range&& range, Pred pred, Proj proj = {}) {
   return ranges::any_of(ranges::begin(range), ranges::end(range),
                         std::move(pred), std::move(proj));
@@ -165,7 +181,10 @@
 // projection.
 //
 // Reference: https://wg21.link/alg.none.of#:~:text=ranges::none_of(R
-template <typename Range, typename Pred, typename Proj = identity>
+template <typename Range,
+          typename Pred,
+          typename Proj = identity,
+          typename = internal::range_category_t<Range>>
 constexpr bool none_of(Range&& range, Pred pred, Proj proj = {}) {
   return ranges::none_of(ranges::begin(range), ranges::end(range),
                          std::move(pred), std::move(proj));
@@ -216,7 +235,10 @@
 // Remarks: If `f` returns a result, the result is ignored.
 //
 // Reference: https://wg21.link/alg.foreach#:~:text=ranges::for_each(R
-template <typename Range, typename Fun, typename Proj = identity>
+template <typename Range,
+          typename Fun,
+          typename Proj = identity,
+          typename = internal::range_category_t<Range>>
 constexpr auto for_each(Range&& range, Fun f, Proj proj = {}) {
   return ranges::for_each(ranges::begin(range), ranges::end(range),
                           std::move(f), std::move(proj));
@@ -258,7 +280,10 @@
 // and any projection.
 //
 // Reference: https://wg21.link/alg.find#:~:text=ranges::find(R
-template <typename Range, typename T, typename Proj = identity>
+template <typename Range,
+          typename T,
+          typename Proj = identity,
+          typename = internal::range_category_t<Range>>
 constexpr auto find(Range&& range, const T& value, Proj proj = {}) {
   return ranges::find(ranges::begin(range), ranges::end(range), value,
                       std::move(proj));
@@ -294,7 +319,10 @@
 // and any projection.
 //
 // Reference: https://wg21.link/alg.find#:~:text=ranges::find_if(R
-template <typename Range, typename Pred, typename Proj = identity>
+template <typename Range,
+          typename Pred,
+          typename Proj = identity,
+          typename = internal::range_category_t<Range>>
 constexpr auto find_if(Range&& range, Pred pred, Proj proj = {}) {
   return ranges::find_if(ranges::begin(range), ranges::end(range),
                          std::move(pred), std::move(proj));
@@ -330,7 +358,10 @@
 // and any projection.
 //
 // Reference: https://wg21.link/alg.find#:~:text=ranges::find_if_not(R
-template <typename Range, typename Pred, typename Proj = identity>
+template <typename Range,
+          typename Pred,
+          typename Proj = identity,
+          typename = internal::range_category_t<Range>>
 constexpr auto find_if_not(Range&& range, Pred pred, Proj proj = {}) {
   return ranges::find_if_not(ranges::begin(range), ranges::end(range),
                              std::move(pred), std::move(proj));
@@ -398,7 +429,9 @@
           typename Range2,
           typename Pred = ranges::equal_to,
           typename Proj1 = identity,
-          typename Proj2 = identity>
+          typename Proj2 = identity,
+          typename = internal::range_category_t<Range1>,
+          typename = internal::range_category_t<Range2>>
 constexpr auto find_end(Range1&& range1,
                         Range2&& range2,
                         Pred pred = {},
@@ -461,7 +494,9 @@
           typename Range2,
           typename Pred = ranges::equal_to,
           typename Proj1 = identity,
-          typename Proj2 = identity>
+          typename Proj2 = identity,
+          typename = internal::range_category_t<Range1>,
+          typename = internal::range_category_t<Range2>>
 constexpr auto find_first_of(Range1&& range1,
                              Range2&& range2,
                              Pred pred = {},
@@ -512,7 +547,8 @@
 // https://wg21.link/alg.adjacent.find#:~:text=ranges::adjacent_find(R
 template <typename Range,
           typename Pred = ranges::equal_to,
-          typename Proj = identity>
+          typename Proj = identity,
+          typename = internal::range_category_t<Range>>
 constexpr auto adjacent_find(Range&& range, Pred pred = {}, Proj proj = {}) {
   return ranges::adjacent_find(ranges::begin(range), ranges::end(range),
                                std::move(pred), std::move(proj));
@@ -554,7 +590,10 @@
 // and any projection.
 //
 // Reference: https://wg21.link/alg.count#:~:text=ranges::count(R
-template <typename Range, typename T, typename Proj = identity>
+template <typename Range,
+          typename T,
+          typename Proj = identity,
+          typename = internal::range_category_t<Range>>
 constexpr auto count(Range&& range, const T& value, Proj proj = {}) {
   return ranges::count(ranges::begin(range), ranges::end(range), value,
                        std::move(proj));
@@ -590,7 +629,10 @@
 // and any projection.
 //
 // Reference: https://wg21.link/alg.count#:~:text=ranges::count_if(R
-template <typename Range, typename Pred, typename Proj = identity>
+template <typename Range,
+          typename Pred,
+          typename Proj = identity,
+          typename = internal::range_category_t<Range>>
 constexpr auto count_if(Range&& range, Pred pred, Proj proj = {}) {
   return ranges::count_if(ranges::begin(range), ranges::end(range),
                           std::move(pred), std::move(proj));
@@ -646,7 +688,9 @@
           typename Range2,
           typename Pred = ranges::equal_to,
           typename Proj1 = identity,
-          typename Proj2 = identity>
+          typename Proj2 = identity,
+          typename = internal::range_category_t<Range1>,
+          typename = internal::range_category_t<Range2>>
 constexpr auto mismatch(Range1&& range1,
                         Range2&& range2,
                         Pred pred = {},
@@ -712,7 +756,9 @@
           typename Range2,
           typename Pred = ranges::equal_to,
           typename Proj1 = identity,
-          typename Proj2 = identity>
+          typename Proj2 = identity,
+          typename = internal::range_category_t<Range1>,
+          typename = internal::range_category_t<Range2>>
 constexpr bool equal(Range1&& range1,
                      Range2&& range2,
                      Pred pred = {},
@@ -791,7 +837,9 @@
 template <typename Range1,
           typename Range2,
           typename Pred = ranges::equal_to,
-          typename Proj = identity>
+          typename Proj = identity,
+          typename = internal::range_category_t<Range1>,
+          typename = internal::range_category_t<Range2>>
 constexpr bool is_permutation(Range1&& range1,
                               Range2&& range2,
                               Pred pred = {},
@@ -852,7 +900,9 @@
           typename Range2,
           typename Pred = ranges::equal_to,
           typename Proj1 = identity,
-          typename Proj2 = identity>
+          typename Proj2 = identity,
+          typename = internal::range_category_t<Range1>,
+          typename = internal::range_category_t<Range2>>
 constexpr auto search(Range1&& range1,
                       Range2&& range2,
                       Pred pred = {},
@@ -915,7 +965,8 @@
           typename Size,
           typename T,
           typename Pred = ranges::equal_to,
-          typename Proj = identity>
+          typename Proj = identity,
+          typename = internal::range_category_t<Range>>
 constexpr auto search_n(Range&& range,
                         Size count,
                         const T& value,
@@ -970,6 +1021,7 @@
 // Reference: https://wg21.link/alg.copy#:~:text=ranges::copy(R
 template <typename Range,
           typename OutputIterator,
+          typename = internal::range_category_t<Range>,
           typename = internal::iterator_category_t<OutputIterator>>
 constexpr auto copy(Range&& range, OutputIterator result) {
   return ranges::copy(ranges::begin(range), ranges::end(range), result);
@@ -1049,6 +1101,7 @@
           typename OutputIterator,
           typename Pred,
           typename Proj = identity,
+          typename = internal::range_category_t<Range>,
           typename = internal::iterator_category_t<OutputIterator>>
 constexpr auto copy_if(Range&& range,
                        OutputIterator result,
@@ -1096,6 +1149,7 @@
 // Reference: https://wg21.link/alg.copy#:~:text=ranges::copy_backward(R
 template <typename Range,
           typename BidirectionalIterator,
+          typename = internal::range_category_t<Range>,
           typename = internal::iterator_category_t<BidirectionalIterator>>
 constexpr auto copy_backward(Range&& range, BidirectionalIterator result) {
   return ranges::copy_backward(ranges::begin(range), ranges::end(range),
@@ -1147,6 +1201,7 @@
 // Reference: https://wg21.link/alg.move#:~:text=ranges::move(R
 template <typename Range,
           typename OutputIterator,
+          typename = internal::range_category_t<Range>,
           typename = internal::iterator_category_t<OutputIterator>>
 constexpr auto move(Range&& range, OutputIterator result) {
   return ranges::move(ranges::begin(range), ranges::end(range), result);
@@ -1194,6 +1249,7 @@
 // Reference: https://wg21.link/alg.move#:~:text=ranges::move_backward(R
 template <typename Range,
           typename BidirectionalIterator,
+          typename = internal::range_category_t<Range>,
           typename = internal::iterator_category_t<BidirectionalIterator>>
 constexpr auto move_backward(Range&& range, BidirectionalIterator result) {
   return ranges::move_backward(ranges::begin(range), ranges::end(range),
@@ -1244,7 +1300,10 @@
 // Complexity: Exactly `M` swaps.
 //
 // Reference: https://wg21.link/alg.swap#:~:text=ranges::swap_ranges(R1
-template <typename Range1, typename Range2>
+template <typename Range1,
+          typename Range2,
+          typename = internal::range_category_t<Range1>,
+          typename = internal::range_category_t<Range2>>
 constexpr auto swap_ranges(Range1&& range1, Range2&& range2) {
   return ranges::swap_ranges(ranges::begin(range1), ranges::end(range1),
                              ranges::begin(range2), ranges::end(range2));
@@ -1276,7 +1335,9 @@
           typename UnaryOperation,
           typename Proj = identity,
           typename = internal::iterator_category_t<InputIterator>,
-          typename = internal::iterator_category_t<OutputIterator>>
+          typename = internal::iterator_category_t<OutputIterator>,
+          typename = indirect_result_t<UnaryOperation&,
+                                       projected<InputIterator, Proj>>>
 constexpr auto transform(InputIterator first1,
                          InputIterator last1,
                          OutputIterator result,
@@ -1308,7 +1369,10 @@
           typename OutputIterator,
           typename UnaryOperation,
           typename Proj = identity,
-          typename = internal::iterator_category_t<OutputIterator>>
+          typename = internal::range_category_t<Range>,
+          typename = internal::iterator_category_t<OutputIterator>,
+          typename = indirect_result_t<UnaryOperation&,
+                                       projected<iterator_t<Range>, Proj>>>
 constexpr auto transform(Range&& range,
                          OutputIterator result,
                          UnaryOperation op,
@@ -1317,7 +1381,100 @@
                            std::move(op), std::move(proj));
 }
 
-// TODO(crbug.com/1071094): Implement binary transform.
+// Let:
+// `N` be `min(last1 - first1, last2 - first2)`,
+// `E(i)` be `invoke(binary_op, invoke(proj1, *(first1 + (i - result))),
+//                              invoke(proj2, *(first2 + (i - result))))`.
+//
+// Preconditions: `binary_op` does not invalidate iterators or subranges, nor
+// modify elements in the ranges `[first1, first1 + N]`, `[first2, first2 + N]`,
+// and `[result, result + N]`.
+//
+// Effects: Assigns through every iterator `i` in the range
+// `[result, result + N)` a new corresponding value equal to `E(i)`.
+//
+// Returns: `result + N`
+//
+// Complexity: Exactly `N` applications of `binary_op`, and any projections.
+//
+// Remarks: `result` may be equal to `first1` or `first2`.
+//
+// Reference: https://wg21.link/alg.transform#:~:text=ranges::transform(I1
+template <typename ForwardIterator1,
+          typename ForwardIterator2,
+          typename OutputIterator,
+          typename BinaryOperation,
+          typename Proj1 = identity,
+          typename Proj2 = identity,
+          typename = internal::iterator_category_t<ForwardIterator1>,
+          typename = internal::iterator_category_t<ForwardIterator2>,
+          typename = internal::iterator_category_t<OutputIterator>,
+          typename = indirect_result_t<BinaryOperation&,
+                                       projected<ForwardIterator1, Proj1>,
+                                       projected<ForwardIterator2, Proj2>>>
+constexpr auto transform(ForwardIterator1 first1,
+                         ForwardIterator1 last1,
+                         ForwardIterator2 first2,
+                         ForwardIterator2 last2,
+                         OutputIterator result,
+                         BinaryOperation binary_op,
+                         Proj1 proj1 = {},
+                         Proj2 proj2 = {}) {
+  // std::transform does not have a `last2` overload. Thus we need to adjust
+  // `last1` to ensure to not read past `last2`.
+  last1 = std::next(first1, std::min(std::distance(first1, last1),
+                                     std::distance(first2, last2)));
+  return std::transform(first1, last1, first2, result,
+                        [&binary_op, &proj1, &proj2](auto&& lhs, auto&& rhs) {
+                          return invoke(
+                              binary_op,
+                              invoke(proj1, std::forward<decltype(lhs)>(lhs)),
+                              invoke(proj2, std::forward<decltype(rhs)>(rhs)));
+                        });
+}
+
+// Let:
+// `N` be `min(size(range1), size(range2)`,
+// `E(i)` be `invoke(binary_op, invoke(proj1, *(begin(range1) + (i - result))),
+//                              invoke(proj2, *(begin(range2) + (i - result))))`
+//
+// Preconditions: `binary_op` does not invalidate iterators or subranges, nor
+// modify elements in the ranges `[begin(range1), end(range1)]`,
+// `[begin(range2), end(range2)]`, and `[result, result + N]`.
+//
+// Effects: Assigns through every iterator `i` in the range
+// `[result, result + N)` a new corresponding value equal to `E(i)`.
+//
+// Returns: `result + N`
+//
+// Complexity: Exactly `N` applications of `binary_op`, and any projections.
+//
+// Remarks: `result` may be equal to `begin(range1)` or `begin(range2)`.
+//
+// Reference: https://wg21.link/alg.transform#:~:text=ranges::transform(R1
+template <typename Range1,
+          typename Range2,
+          typename OutputIterator,
+          typename BinaryOperation,
+          typename Proj1 = identity,
+          typename Proj2 = identity,
+          typename = internal::range_category_t<Range1>,
+          typename = internal::range_category_t<Range2>,
+          typename = internal::iterator_category_t<OutputIterator>,
+          typename = indirect_result_t<BinaryOperation&,
+                                       projected<iterator_t<Range1>, Proj1>,
+                                       projected<iterator_t<Range2>, Proj2>>>
+constexpr auto transform(Range1&& range1,
+                         Range2&& range2,
+                         OutputIterator result,
+                         BinaryOperation binary_op,
+                         Proj1 proj1 = {},
+                         Proj2 proj2 = {}) {
+  return ranges::transform(ranges::begin(range1), ranges::end(range1),
+                           ranges::begin(range2), ranges::end(range2), result,
+                           std::move(binary_op), std::move(proj1),
+                           std::move(proj2));
+}
 
 // [alg.replace] Replace
 // Reference: https://wg21.link/alg.replace
diff --git a/base/util/ranges/algorithm_unittest.cc b/base/util/ranges/algorithm_unittest.cc
index 05611fc..4ff621b 100644
--- a/base/util/ranges/algorithm_unittest.cc
+++ b/base/util/ranges/algorithm_unittest.cc
@@ -454,7 +454,7 @@
   EXPECT_THAT(ints2, ElementsAre(0, 0, 0, 6, 6));
 }
 
-TEST(RangesTest, Transform) {
+TEST(RangesTest, UnaryTransform) {
   int input[] = {1, 2, 3, 4, 5};
   auto plus_1 = [](int i) { return i + 1; };
   auto times_2 = [](int i) { return i * 2; };
@@ -474,6 +474,25 @@
   EXPECT_THAT(values, ElementsAre(Int{0}, Int{4}, Int{8}, Int{10}));
 }
 
+TEST(RangesTest, BinaryTransform) {
+  int input[] = {1, 2, 3, 4, 5};
+  int output[] = {0, 0, 0, 0, 0};
+
+  EXPECT_EQ(output + 2, ranges::transform(input, input + 2, input + 3,
+                                          input + 5, output, std::plus<>{}));
+  EXPECT_THAT(output, ElementsAre(5, 7, 0, 0, 0));
+
+  EXPECT_EQ(output + 5,
+            ranges::transform(input, input, output, std::multiplies<>{}));
+  EXPECT_THAT(output, ElementsAre(1, 4, 9, 16, 25));
+
+  Int values[] = {{0}, {2}, {4}, {5}};
+  EXPECT_EQ(values + 4,
+            ranges::transform(values, values, values, std::minus<>{},
+                              &Int::value, &Int::value));
+  EXPECT_THAT(values, ElementsAre(Int{0}, Int{0}, Int{0}, Int{0}));
+}
+
 TEST(RangesTest, LowerBound) {
   int array[] = {0, 0, 1, 1, 2, 2};
 
diff --git a/base/util/ranges/functional.h b/base/util/ranges/functional.h
index 9d31a71a..b9445ff 100644
--- a/base/util/ranges/functional.h
+++ b/base/util/ranges/functional.h
@@ -52,6 +52,17 @@
   return std::forward<Functor>(f)(std::forward<Args>(args)...);
 }
 
+// Implementation of C++17's std::invoke_result_t.
+//
+// This implementation adds references to `Functor` and `Args` to work around
+// some quirks of std::result_of_t. See the #Notes section of [1] for details.
+//
+// References:
+// [1] https://en.cppreference.com/w/cpp/types/result_of
+// [2] https://wg21.link/meta.type.synop#lib:invoke_result_t
+template <typename Functor, typename... Args>
+using invoke_result_t = std::result_of_t<Functor && (Args && ...)>;
+
 // Simplified implementations of C++20's std::ranges comparison function
 // objects. As opposed to the std::ranges implementation, these versions do not
 // constrain the passed-in types.
diff --git a/base/util/ranges/iterator.h b/base/util/ranges/iterator.h
index daaedbc..50a318e 100644
--- a/base/util/ranges/iterator.h
+++ b/base/util/ranges/iterator.h
@@ -6,20 +6,75 @@
 #define BASE_UTIL_RANGES_ITERATOR_H_
 
 #include <iterator>
+#include <type_traits>
+
+#include "base/util/ranges/functional.h"
 
 namespace util {
+
+// Simplified implementation of C++20's std::iter_reference_t.
+// As opposed to std::iter_reference_t, this implementation does not restrict
+// the type of `Iter`.
+//
+// Reference: https://wg21.link/iterator.synopsis#:~:text=iter_reference_t
+template <typename Iter>
+using iter_reference_t = decltype(*std::declval<Iter&>());
+
+// Simplified implementation of C++20's std::indirect_result_t. As opposed to
+// std::indirect_result_t, this implementation does not restrict the type of
+// `Func` and `Iters`.
+//
+// Reference: https://wg21.link/iterator.synopsis#:~:text=indirect_result_t
+template <typename Func, typename... Iters>
+using indirect_result_t = invoke_result_t<Func, iter_reference_t<Iters>...>;
+
+// Simplified implementation of C++20's std::projected. As opposed to
+// std::projected, this implementation does not explicitly restrict the type of
+// `Iter` and `Proj`, but rather does so implicitly by requiring
+// `indirect_result_t<Proj, Iter>` is a valid type. This is required for SFINAE
+// friendliness.
+//
+// Reference: https://wg21.link/projected
+template <typename Iter,
+          typename Proj,
+          typename IndirectResultT = indirect_result_t<Proj, Iter>>
+struct projected {
+  using value_type = std::remove_cv_t<std::remove_reference_t<IndirectResultT>>;
+
+  IndirectResultT operator*() const;  // not defined
+};
+
 namespace ranges {
 
+namespace internal {
+
+using std::begin;
+template <typename Range>
+auto Begin(Range&& range) -> decltype(begin(std::forward<Range>(range))) {
+  return begin(std::forward<Range>(range));
+}
+
+using std::end;
+template <typename Range>
+auto End(Range&& range) -> decltype(end(std::forward<Range>(range))) {
+  return end(std::forward<Range>(range));
+}
+
+}  // namespace internal
+
 // Simplified implementation of C++20's std::ranges::begin.
 // As opposed to std::ranges::begin, this implementation does not prefer a
 // member begin() over a free standing begin(), does not check whether begin()
 // returns an iterator, does not inhibit ADL and is not constexpr.
 //
+// The trailing return type and dispatch to the internal implementation is
+// necessary to be SFINAE friendly.
+//
 // Reference: https://wg21.link/range.access.begin
 template <typename Range>
-decltype(auto) begin(Range&& range) {
-  using std::begin;
-  return begin(std::forward<Range>(range));
+auto begin(Range&& range)
+    -> decltype(internal::Begin(std::forward<Range>(range))) {
+  return internal::Begin(std::forward<Range>(range));
 }
 
 // Simplified implementation of C++20's std::ranges::end.
@@ -27,14 +82,17 @@
 // member end() over a free standing end(), does not check whether end()
 // returns an iterator, does not inhibit ADL and is not constexpr.
 //
+// The trailing return type and dispatch to the internal implementation is
+// necessary to be SFINAE friendly.
+//
 // Reference: - https://wg21.link/range.access.end
 template <typename Range>
-decltype(auto) end(Range&& range) {
-  using std::end;
-  return end(std::forward<Range>(range));
+auto end(Range&& range) -> decltype(internal::End(std::forward<Range>(range))) {
+  return internal::End(std::forward<Range>(range));
 }
 
 }  // namespace ranges
+
 }  // namespace util
 
 #endif  // BASE_UTIL_RANGES_ITERATOR_H_
diff --git a/base/util/ranges/ranges.h b/base/util/ranges/ranges.h
new file mode 100644
index 0000000..2bcdb437
--- /dev/null
+++ b/base/util/ranges/ranges.h
@@ -0,0 +1,26 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_UTIL_RANGES_RANGES_H_
+#define BASE_UTIL_RANGES_RANGES_H_
+
+#include <type_traits>
+
+#include "base/util/ranges/iterator.h"
+
+namespace util {
+
+namespace ranges {
+
+// Implementation of C++20's std::ranges::iterator_t.
+//
+// Reference: https://wg21.link/ranges.syn#:~:text=iterator_t
+template <typename Range>
+using iterator_t = decltype(ranges::begin(std::declval<Range&>()));
+
+}  // namespace ranges
+
+}  // namespace util
+
+#endif  // BASE_UTIL_RANGES_RANGES_H_
diff --git a/build/android/PRESUBMIT.py b/build/android/PRESUBMIT.py
index d6d3a44..5fd845d 100644
--- a/build/android/PRESUBMIT.py
+++ b/build/android/PRESUBMIT.py
@@ -28,8 +28,9 @@
           input_api,
           output_api,
           pylintrc='pylintrc',
-          black_list=[
+          block_list=[
               r'.*_pb2\.py',
+              r'.*list_java_targets\.py',  # crbug.com/1100664
           ] + build_pys,
           extra_paths_list=[
               J(),
@@ -49,8 +50,9 @@
       input_api.canned_checks.GetPylint(
           input_api,
           output_api,
-          white_list=build_pys,
-          black_list=[
+          allow_list=build_pys,
+          block_list=[
+              r'.*_pb2\.py',
               r'.*_pb2\.py',
           ],
           extra_paths_list=[J('gyp'), J('gn')]))
diff --git a/build/android/gradle/generate_gradle.py b/build/android/gradle/generate_gradle.py
index 85b2441..4c64b8b5 100755
--- a/build/android/gradle/generate_gradle.py
+++ b/build/android/gradle/generate_gradle.py
@@ -112,11 +112,7 @@
 
 
 def _RunGnGen(output_dir, args=None):
-  cmd = [
-      os.path.join(_DEPOT_TOOLS_PATH, 'gn'),
-      'gen',
-      output_dir,
-  ]
+  cmd = [os.path.join(_DEPOT_TOOLS_PATH, 'gn'), 'gen', output_dir]
   if args:
     cmd.extend(args)
   logging.info('Running: %r', cmd)
@@ -126,36 +122,19 @@
 def _RunNinja(output_dir, args):
   # Don't use version within _DEPOT_TOOLS_PATH, since most devs don't use
   # that one when building.
-  cmd = [
-      'autoninja',
-      '-C',
-      output_dir,
-  ]
+  cmd = ['autoninja', '-C', output_dir]
   cmd.extend(args)
   logging.info('Running: %r', cmd)
   subprocess.check_call(cmd)
 
 
 def _QueryForAllGnTargets(output_dir):
-  # Query ninja rather than GN since it's faster.
   cmd = [
-      'ninja',
-      '-C',
-      output_dir,
-      '-t',
-      'targets',
+      os.path.join(_BUILD_ANDROID, 'list_java_targets.py'), '--gn-labels',
+      '--nested', '--build-build-configs', '--output-directory', output_dir
   ]
   logging.info('Running: %r', cmd)
-  ninja_output = build_utils.CheckOutput(cmd)
-  ret = []
-  SUFFIX_LEN = len('__build_config_crbug_908819')
-  for line in ninja_output.splitlines():
-    ninja_target = line.rsplit(':', 1)[0]
-    # Ignore root aliases by ensure a : exists.
-    if ':' in ninja_target and ninja_target.endswith(
-        '__build_config_crbug_908819'):
-      ret.append('//' + ninja_target[:-SUFFIX_LEN])
-  return ret
+  return subprocess.check_output(cmd).splitlines()
 
 
 class _ProjectEntry(object):
@@ -204,9 +183,6 @@
   def GnBuildConfigTarget(self):
     return '%s__build_config_crbug_908819' % self._gn_target
 
-  def NinjaBuildConfigTarget(self):
-    return '%s__build_config_crbug_908819' % self.NinjaTarget()
-
   def GradleSubdir(self):
     """Returns the output subdirectory."""
     ninja_target = self.NinjaTarget()
@@ -870,9 +846,6 @@
 
   main_entries = [_ProjectEntry.FromGnTarget(t) for t in targets]
 
-  logging.warning('Building .build_config files...')
-  _RunNinja(output_dir, [e.NinjaBuildConfigTarget() for e in main_entries])
-
   if args.all:
     # There are many unused libraries, so restrict to those that are actually
     # used by apks/bundles/binaries/tests or that are explicitly mentioned in
diff --git a/build/android/list_java_targets.py b/build/android/list_java_targets.py
new file mode 100755
index 0000000..b7bd0220
--- /dev/null
+++ b/build/android/list_java_targets.py
@@ -0,0 +1,196 @@
+#!/usr/bin/env python3
+# Copyright 2020 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.
+
+# Lint as: python3
+"""Prints out available java targets.
+
+Examples:
+# List GN target for bundles:
+build/android/list_java_targets.py --output-directory out/Default \
+--type android_app_bundle --gn-labels
+
+# List all android targets with types:
+build/android/list_java_targets.py --output-directory out/Default --print-types
+
+# Build all apk targets:
+build/android/list_java_targets.py --output-directory out/Default \
+--type android_apk | xargs autoninja -C out/Default
+
+# Show how many of each target type exist:
+build/android/list_java_targets.py --output-directory out/Default --stats
+
+"""
+
+import argparse
+import collections
+import json
+import logging
+import os
+import subprocess
+import sys
+
+_SRC_ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), '..',
+                                          '..'))
+sys.path.append(os.path.join(_SRC_ROOT, 'build', 'android'))
+from pylib import constants
+
+_VALID_TYPES = (
+    'android_apk',
+    'android_app_bundle',
+    'android_app_bundle_module',
+    'android_assets',
+    'android_resources',
+    'dist_aar',
+    'dist_jar',
+    'group',
+    'java_annotation_processor',
+    'java_binary',
+    'java_library',
+    'junit_binary',
+    'system_java_library',
+)
+
+
+def _run_ninja(output_dir, args):
+  cmd = [
+      'autoninja',
+      '-C',
+      output_dir,
+  ]
+  cmd.extend(args)
+  logging.info('Running: %r', cmd)
+  subprocess.run(cmd, check=True, stdout=sys.stderr)
+
+
+def _query_for_build_config_targets(output_dir):
+  # Query ninja rather than GN since it's faster.
+  cmd = ['ninja', '-C', output_dir, '-t', 'targets']
+  logging.info('Running: %r', cmd)
+  ninja_output = subprocess.run(cmd,
+                                check=True,
+                                capture_output=True,
+                                encoding='ascii').stdout
+  ret = []
+  SUFFIX = '__build_config_crbug_908819'
+  SUFFIX_LEN = len(SUFFIX)
+  for line in ninja_output.splitlines():
+    ninja_target = line.rsplit(':', 1)[0]
+    # Ignore root aliases by ensuring a : exists.
+    if ':' in ninja_target and ninja_target.endswith(SUFFIX):
+      ret.append('//' + ninja_target[:-SUFFIX_LEN])
+  return ret
+
+
+class _TargetEntry(object):
+  _cached_entries = {}
+
+  def __init__(self, gn_target):
+    assert gn_target.startswith('//'), gn_target
+    if ':' not in gn_target:
+      gn_target = '%s:%s' % (gn_target, os.path.basename(gn_target))
+    self.gn_target = gn_target
+    self._build_config = None
+    self._java_files = None
+    self._all_entries = None
+    self.android_test_entries = []
+
+  @property
+  def ninja_target(self):
+    return self.gn_target[2:]
+
+  @property
+  def ninja_build_config_target(self):
+    return self.ninja_target + '__build_config_crbug_908819'
+
+  def build_config(self):
+    """Reads and returns the project's .build_config JSON."""
+    if not self._build_config:
+      ninja_target = self.ninja_target
+      # Support targets at the root level. e.g. //:foo
+      if ninja_target[0] == ':':
+        ninja_target = ninja_target[1:]
+      subpath = ninja_target.replace(':', os.path.sep) + '.build_config'
+      path = os.path.join('gen', subpath)
+      with open(os.path.join(constants.GetOutDirectory(), path)) as jsonfile:
+        self._build_config = json.load(jsonfile)
+    return self._build_config
+
+  def get_type(self):
+    """Returns the target type from its .build_config."""
+    return self.build_config()['deps_info']['type']
+
+
+def main():
+  parser = argparse.ArgumentParser(
+      description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
+  parser.add_argument('--output-directory')
+  parser.add_argument('--gn-labels',
+                      action='store_true',
+                      help='Print GN labels rather than ninja targets')
+  parser.add_argument(
+      '--nested',
+      action='store_true',
+      help='Do not convert nested targets to their top-level equivalents. '
+      'E.g. Without this, foo_test__apk -> foo_test')
+  parser.add_argument('--print-types',
+                      action='store_true',
+                      help='Print type of each target')
+  parser.add_argument('--build-build-configs',
+                      action='store_true',
+                      help='Build all .build_config files.')
+  parser.add_argument('--type',
+                      action='append',
+                      help='Restrict to targets of given type',
+                      choices=_VALID_TYPES)
+  parser.add_argument('--stats',
+                      action='store_true',
+                      help='Print counts of each target type.')
+  parser.add_argument('-v', '--verbose', default=0, action='count')
+  args = parser.parse_args()
+
+  args.build_build_configs |= bool(args.type or args.print_types or args.stats)
+
+  logging.basicConfig(level=logging.WARNING - (10 * args.verbose),
+                      format='%(levelname).1s %(relativeCreated)6d %(message)s')
+
+  if args.output_directory:
+    constants.SetOutputDirectory(args.output_directory)
+  constants.CheckOutputDirectory()
+  output_dir = constants.GetOutDirectory()
+
+  # Query ninja for all __build_config_crbug_908819 targets.
+  targets = _query_for_build_config_targets(output_dir)
+  entries = [_TargetEntry(t) for t in targets]
+
+  if args.build_build_configs:
+    logging.warning('Building %d .build_config files...', len(entries))
+    _run_ninja(output_dir, [e.ninja_build_config_target for e in entries])
+
+  if args.type:
+    entries = [e for e in entries if e.get_type() in args.type]
+
+  if args.stats:
+    counts = collections.Counter(e.get_type() for e in entries)
+    for entry_type, count in sorted(counts.items()):
+      print('{}: {}'.format(entry_type, count))
+  else:
+    for e in entries:
+      if args.gn_labels:
+        to_print = e.gn_target
+      else:
+        to_print = e.ninja_target
+
+      # Convert to top-level target
+      if not args.nested:
+        to_print = to_print.replace('__test_apk__apk', '').replace('__apk', '')
+
+      if args.print_types:
+        to_print = '{}: {}'.format(to_print, e.get_type())
+
+      print(to_print)
+
+
+if __name__ == '__main__':
+  main()
diff --git a/build/android/pylib/local/device/local_device_gtest_run.py b/build/android/pylib/local/device/local_device_gtest_run.py
index 868a4f4..d6da1a4 100644
--- a/build/android/pylib/local/device/local_device_gtest_run.py
+++ b/build/android/pylib/local/device/local_device_gtest_run.py
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import contextlib
 import collections
 import itertools
 import logging
@@ -525,25 +526,17 @@
         for f in flags:
           logging.info('  %s', f)
 
-        try:
+        with self._ArchiveLogcat(dev, 'list_tests'):
           raw_test_list = crash_handler.RetryOnSystemCrash(
               lambda d: self._delegate.Run(
                   None, d, flags=' '.join(flags), timeout=timeout),
               device=dev)
-        except device_errors.AdbCommandFailedError:
-          logging.exception('Test listing failed.')
-          # Allow subsequent error handling to dump logcat.
-          raw_test_list = []
 
         tests = gtest_test_instance.ParseGTestListTests(raw_test_list)
         if not tests:
           logging.info('No tests found. Output:')
           for l in raw_test_list:
             logging.info('  %s', l)
-          logging.info('Logcat:')
-          for line in dev.adb.Logcat(dump=True):
-            logging.info(line)
-          dev.adb.Logcat(clear=True)
           if i < retries:
             logging.info('Retrying...')
         else:
@@ -585,6 +578,35 @@
           return link
     return None
 
+  @contextlib.contextmanager
+  def _ArchiveLogcat(self, device, test):
+    if isinstance(test, str):
+      desc = test
+    else:
+      desc = hash(tuple(test))
+
+    stream_name = 'logcat_%s_%s_%s' % (
+        desc, time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()), device.serial)
+
+    logcat_file = None
+    logmon = None
+    try:
+      with self._env.output_manager.ArchivedTempfile(stream_name,
+                                                     'logcat') as logcat_file:
+        with logcat_monitor.LogcatMonitor(
+            device.adb,
+            filter_specs=local_device_environment.LOGCAT_FILTERS,
+            output_file=logcat_file.name,
+            check_error=False) as logmon:
+          with contextlib_ext.Optional(trace_event.trace(str(test)),
+                                       self._env.trace_output):
+            yield logcat_file
+    finally:
+      if logmon:
+        logmon.Close()
+      if logcat_file and logcat_file.Link():
+        logging.info('Logcat saved to %s', logcat_file.Link())
+
   #override
   def _RunTest(self, device, test):
     # Run the test.
@@ -624,28 +646,12 @@
           for f in flags:
             logging.info('  %s', f)
 
-          stream_name = 'logcat_%s_%s_%s' % (
-              hash(tuple(test)),
-              time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()),
-              device.serial)
-
-          with self._env.output_manager.ArchivedTempfile(
-              stream_name, 'logcat') as logcat_file:
-            with logcat_monitor.LogcatMonitor(
-                device.adb,
-                filter_specs=local_device_environment.LOGCAT_FILTERS,
-                output_file=logcat_file.name,
-                check_error=False) as logmon:
-              with contextlib_ext.Optional(
-                  trace_event.trace(str(test)),
-                  self._env.trace_output):
-                output = self._delegate.Run(
-                    test, device, flags=' '.join(flags),
-                    timeout=timeout, retries=0)
-            logmon.Close()
-
-          if logcat_file.Link():
-            logging.info('Logcat saved to %s', logcat_file.Link())
+          with self._ArchiveLogcat(device, test) as logcat_file:
+            output = self._delegate.Run(test,
+                                        device,
+                                        flags=' '.join(flags),
+                                        timeout=timeout,
+                                        retries=0)
 
           if self._test_instance.enable_xml_result_parsing:
             try:
diff --git a/build/config/win/manifest.gni b/build/config/win/manifest.gni
index 5f79269..e2115083f 100644
--- a/build/config/win/manifest.gni
+++ b/build/config/win/manifest.gni
@@ -42,6 +42,9 @@
 require_administrator_manifest = "//build/win/require_administrator.manifest"
 
 # Request the segment heap. See https://crbug.com/1014701 for details.
+declare_args() {
+  enable_segment_heap = false
+}
 segment_heap_manifest = "//build/win/segment_heap.manifest"
 
 # Construct a target to combine the given manifest files into a .rc file.
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 1807e7a6..87701b7 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-0.20200713.1.1
+0.20200714.0.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 1807e7a6..87701b7 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-0.20200713.1.1
+0.20200714.0.1
diff --git a/build/toolchain/concurrent_links.gni b/build/toolchain/concurrent_links.gni
index 5540772..258619e 100644
--- a/build/toolchain/concurrent_links.gni
+++ b/build/toolchain/concurrent_links.gni
@@ -7,6 +7,7 @@
 # in the context of the default_toolchain, so we can at least check for that.
 assert(current_toolchain == default_toolchain)
 
+import("//build/config/android/config.gni")
 import("//build/config/compiler/compiler.gni")
 import("//build/config/mac/symbols.gni")
 import("//build/config/sanitizers/sanitizers.gni")
@@ -47,10 +48,14 @@
   } else if (is_android && !is_component_build && symbol_level == 2) {
     # Full debug symbols require large memory for link.
     _args = [ "--mem_per_link_gb=25" ]
-  } else if (is_android && !is_debug && !using_sanitizer && symbol_level < 2) {
-    # Increase the number of concurrent links for release bots. Debug builds
-    # make heavier use of ProGuard, and so should not be raised. Sanitizers also
-    # increase the memory overhead.
+  } else if (is_android && !is_debug && !using_sanitizer && is_java_debug &&
+             disable_android_lint && symbol_level < 2) {
+    # The link pool is also shared with:
+    # * ProGuard / R8
+    # * Android Lint
+    # Increase the number of concurrent links for release bots that have
+    # ProGuard and Lint disabled, and that do not enable sanitizers (which use a
+    # lot of RAM).
     if (symbol_level == 1) {
       _args = [ "--mem_per_link_gb=6" ]
     } else {
diff --git a/cc/metrics/frame_sequence_tracker.cc b/cc/metrics/frame_sequence_tracker.cc
index 3da496f..8914ff47 100644
--- a/cc/metrics/frame_sequence_tracker.cc
+++ b/cc/metrics/frame_sequence_tracker.cc
@@ -187,6 +187,7 @@
       begin_main_frame_data_.previous_sequence_delta;
   previous_begin_main_sequence_ = current_begin_main_sequence_;
   current_begin_main_sequence_ = args.frame_id.sequence_number;
+  no_damage_impl_frames_while_expecting_main_ = 0;
 }
 
 void FrameSequenceTracker::ReportMainFrameProcessed(
@@ -374,6 +375,8 @@
     begin_impl_frame_data_.previous_sequence = 0;
     if (!IsExpectingMainFrame())
       --aggregated_throughput().frames_expected;
+    else
+      ++no_damage_impl_frames_while_expecting_main_;
   }
   // last_submitted_frame_ == 0 means the last impl frame has been presented.
   if (termination_status_ == TerminationStatus::kScheduledForTermination &&
@@ -601,6 +604,16 @@
       << TRACKER_DCHECK_MSG;
   last_no_main_damage_sequence_ = args.frame_id.sequence_number;
   --main_throughput().frames_expected;
+  // Compute the number of actually expected compositor frames during this main
+  // frame, which produced no damage..
+  DCHECK_GE(aggregated_throughput().frames_expected,
+            no_damage_impl_frames_while_expecting_main_)
+      << TRACKER_DCHECK_MSG;
+  aggregated_throughput().frames_expected -=
+      no_damage_impl_frames_while_expecting_main_;
+  DCHECK_GE(aggregated_throughput().frames_expected,
+            aggregated_throughput().frames_produced)
+      << TRACKER_DCHECK_MSG;
   DCHECK_GE(main_throughput().frames_expected, main_frames_.size())
       << TRACKER_DCHECK_MSG;
 
diff --git a/cc/metrics/frame_sequence_tracker.h b/cc/metrics/frame_sequence_tracker.h
index c406f5b..15de04e 100644
--- a/cc/metrics/frame_sequence_tracker.h
+++ b/cc/metrics/frame_sequence_tracker.h
@@ -255,6 +255,11 @@
   // only when the last impl-frame is ended (ReportFrameEnd).
   bool is_inside_frame_ = false;
 
+  // The number of no damage impl frames accumulated while expecting main. This
+  // main frame could report no damage eventually, then we need to account for
+  // that in the aggregated throughput.
+  uint32_t no_damage_impl_frames_while_expecting_main_ = 0;
+
 #if DCHECK_IS_ON()
   // This stringstream represents a sequence of frame reporting activities on
   // the current tracker. Each letter can be one of the following:
diff --git a/cc/metrics/frame_sequence_tracker_collection.cc b/cc/metrics/frame_sequence_tracker_collection.cc
index bae891e..cfd71ff 100644
--- a/cc/metrics/frame_sequence_tracker_collection.cc
+++ b/cc/metrics/frame_sequence_tracker_collection.cc
@@ -4,6 +4,9 @@
 
 #include "cc/metrics/frame_sequence_tracker_collection.h"
 
+#include <utility>
+#include <vector>
+
 #include "base/memory/ptr_util.h"
 #include "cc/metrics/compositor_frame_reporting_controller.h"
 #include "cc/metrics/frame_sequence_tracker.h"
@@ -33,6 +36,8 @@
 FrameSequenceTrackerCollection::~FrameSequenceTrackerCollection() {
   frame_trackers_.clear();
   removal_trackers_.clear();
+  custom_frame_trackers_.clear();
+  accumulated_metrics_.clear();
 }
 
 FrameSequenceTracker* FrameSequenceTrackerCollection::StartSequenceInternal(
diff --git a/cc/metrics/frame_sequence_tracker_unittest.cc b/cc/metrics/frame_sequence_tracker_unittest.cc
index 963cf104..dfe1e41 100644
--- a/cc/metrics/frame_sequence_tracker_unittest.cc
+++ b/cc/metrics/frame_sequence_tracker_unittest.cc
@@ -1907,6 +1907,33 @@
   EXPECT_FALSE(collection_.HasThroughputData());
 }
 
+// Test that when an impl frame caused no damage is due to waiting on main, the
+// computation of aggregated throughput is correct.
+TEST_F(FrameSequenceTrackerTest, ImplFrameNoDamageWaitingOnMain1) {
+  GenerateSequence("b(1)B(0,1)n(1)N(1,1)e(1,0)");
+  EXPECT_EQ(AggregatedThroughput().frames_expected, 0u);
+}
+
+TEST_F(FrameSequenceTrackerTest, ImplFrameNoDamageWaitingOnMain2) {
+  GenerateSequence("b(1)B(0,1)n(1)e(1,0)N(1,1)");
+  EXPECT_EQ(AggregatedThroughput().frames_expected, 0u);
+}
+
+TEST_F(FrameSequenceTrackerTest, ImplFrameNoDamageWaitingOnMain3) {
+  GenerateSequence("b(1)n(1)B(0,1)Re(1,0)N(0,1)");
+  EXPECT_EQ(AggregatedThroughput().frames_expected, 0u);
+}
+
+TEST_F(FrameSequenceTrackerTest, ImplFrameNoDamageWaitingOnMain4) {
+  GenerateSequence("b(1)B(0,1)n(1)e(1,0)b(2)n(2)e(2,0)N(1,1)");
+  EXPECT_EQ(AggregatedThroughput().frames_expected, 0u);
+}
+
+TEST_F(FrameSequenceTrackerTest, ImplFrameNoDamageWaitingOnMain5) {
+  GenerateSequence("b(2)B(0,2)n(2)e(2,0)b(3)s(1)S(1)e(3,0)N(2,2)");
+  EXPECT_EQ(AggregatedThroughput().frames_expected, 1u);
+}
+
 TEST_F(FrameSequenceTrackerTest, CustomTrackers) {
   // Start custom tracker 1.
   collection_.StartCustomSequence(1);
diff --git a/cc/paint/discardable_image_map.cc b/cc/paint/discardable_image_map.cc
index 6284df4..d90d943e 100644
--- a/cc/paint/discardable_image_map.cc
+++ b/cc/paint/discardable_image_map.cc
@@ -54,25 +54,9 @@
     return std::move(paint_worklet_inputs_);
   }
 
-  void RecordColorHistograms() const {
-    if (color_stats_total_image_count_ > 0) {
-      int srgb_image_percent = (100 * color_stats_srgb_image_count_) /
-                               color_stats_total_image_count_;
-      UMA_HISTOGRAM_PERCENTAGE("Renderer4.ImagesPercentSRGB",
-                               srgb_image_percent);
-    }
-
-    base::CheckedNumeric<int> srgb_pixel_percent =
-        100 * color_stats_srgb_pixel_count_ / color_stats_total_pixel_count_;
-    if (srgb_pixel_percent.IsValid()) {
-      UMA_HISTOGRAM_PERCENTAGE("Renderer4.ImagePixelsPercentSRGB",
-                               srgb_pixel_percent.ValueOrDie());
-    }
-  }
-
-  bool contains_only_srgb_images() const {
-    return color_stats_srgb_image_count_ == color_stats_total_image_count_;
-  }
+  bool contains_only_srgb_images() const { return contains_only_srgb_images_; }
+  bool contains_hbd_images() const { return contains_hbd_images_; }
+  bool contains_hdr_images() const { return contains_hdr_images_; }
 
  private:
   class ImageGatheringProvider : public ImageProvider {
@@ -259,15 +243,12 @@
       paint_worklet_inputs_.push_back(std::make_pair(
           paint_image.paint_worklet_input(), paint_image.stable_id()));
     } else {
-      // Make a note if any image was originally specified in a non-sRGB color
-      // space. PaintWorklets do not have the concept of a color space, so
-      // should not be used to accumulate either counter.
-      color_stats_total_pixel_count_ += image_rect.size().GetCheckedArea();
-      color_stats_total_image_count_++;
-      if (paint_image.isSRGB()) {
-        color_stats_srgb_pixel_count_ += image_rect.size().GetCheckedArea();
-        color_stats_srgb_image_count_++;
-      }
+      if (!paint_image.isSRGB())
+        contains_only_srgb_images_ = false;
+      if (paint_image.isHDR())
+        contains_hdr_images_ = true;
+      if (paint_image.is_high_bit_depth())
+        contains_hbd_images_ = true;
     }
 
     auto& rects = image_id_to_rects_[paint_image.stable_id()];
@@ -327,12 +308,9 @@
   base::flat_map<PaintImage::Id, PaintImage::DecodingMode> decoding_mode_map_;
   bool only_gather_animated_images_ = false;
 
-  // Statistics about the number of images and pixels that will require color
-  // conversion if the target color space is not sRGB.
-  int color_stats_srgb_image_count_ = 0;
-  int color_stats_total_image_count_ = 0;
-  base::CheckedNumeric<int64_t> color_stats_srgb_pixel_count_ = 0;
-  base::CheckedNumeric<int64_t> color_stats_total_pixel_count_ = 0;
+  bool contains_only_srgb_images_ = true;
+  bool contains_hbd_images_ = false;
+  bool contains_hdr_images_ = false;
 };
 
 }  // namespace
@@ -349,11 +327,12 @@
 
   DiscardableImageGenerator generator(bounds.right(), bounds.bottom(),
                                       paint_op_buffer);
-  generator.RecordColorHistograms();
   image_id_to_rects_ = generator.TakeImageIdToRectsMap();
   animated_images_metadata_ = generator.TakeAnimatedImagesMetadata();
   paint_worklet_inputs_ = generator.TakePaintWorkletInputs();
   decoding_mode_map_ = generator.TakeDecodingModeMap();
+  contains_hdr_images_ = generator.contains_hdr_images();
+  contains_hbd_images_ = generator.contains_hbd_images();
   contains_only_srgb_images_ = generator.contains_only_srgb_images();
   auto images = generator.TakeImages();
   images_rtree_.Build(
diff --git a/cc/paint/discardable_image_map.h b/cc/paint/discardable_image_map.h
index b14b92a..846db40 100644
--- a/cc/paint/discardable_image_map.h
+++ b/cc/paint/discardable_image_map.h
@@ -5,6 +5,7 @@
 #ifndef CC_PAINT_DISCARDABLE_IMAGE_MAP_H_
 #define CC_PAINT_DISCARDABLE_IMAGE_MAP_H_
 
+#include <memory>
 #include <utility>
 #include <vector>
 
@@ -59,6 +60,8 @@
   void GetDiscardableImagesInRect(const gfx::Rect& rect,
                                   std::vector<const DrawImage*>* images) const;
   const Rects& GetRectsForImage(PaintImage::Id image_id) const;
+  bool contains_hdr_images() const { return contains_hdr_images_; }
+  bool contains_hbd_images() const { return contains_hbd_images_; }
   bool contains_only_srgb_images() const { return contains_only_srgb_images_; }
   const std::vector<AnimatedImageMetadata>& animated_images_metadata() const {
     return animated_images_metadata_;
@@ -92,6 +95,8 @@
   std::vector<AnimatedImageMetadata> animated_images_metadata_;
   base::flat_map<PaintImage::Id, PaintImage::DecodingMode> decoding_mode_map_;
   bool contains_only_srgb_images_ = true;
+  bool contains_hbd_images_ = false;
+  bool contains_hdr_images_ = false;
 
   RTree<DrawImage> images_rtree_;
 
diff --git a/cc/paint/discardable_image_map_unittest.cc b/cc/paint/discardable_image_map_unittest.cc
index 380ee6d..f77ea15 100644
--- a/cc/paint/discardable_image_map_unittest.cc
+++ b/cc/paint/discardable_image_map_unittest.cc
@@ -6,6 +6,8 @@
 
 #include <stddef.h>
 
+#include <algorithm>
+#include <limits>
 #include <memory>
 
 #include "base/memory/ref_counted.h"
@@ -1130,6 +1132,40 @@
   EXPECT_EQ(ImageRectsToRegion(image_map.GetRectsForImage(image.stable_id())),
             expected_region);
 }
+TEST_F(DiscardableImageMapTest, HighBitDepth) {
+  gfx::Rect visible_rect(500, 500);
+
+  SkBitmap bitmap;
+  auto info = SkImageInfo::Make(visible_rect.width(), visible_rect.height(),
+                                kRGBA_F16_SkColorType, kPremul_SkAlphaType,
+                                nullptr /* color_space */);
+  bitmap.allocPixels(info);
+  bitmap.eraseColor(SK_AlphaTRANSPARENT);
+  PaintImage discardable_image = PaintImageBuilder::WithDefault()
+                                     .set_id(PaintImage::GetNextId())
+                                     .set_is_high_bit_depth(true)
+                                     .set_image(SkImage::MakeFromBitmap(bitmap),
+                                                PaintImage::GetNextContentId())
+                                     .TakePaintImage();
+
+  FakeContentLayerClient content_layer_client;
+  content_layer_client.set_bounds(visible_rect.size());
+
+  scoped_refptr<DisplayItemList> display_list =
+      content_layer_client.PaintContentsToDisplayList(
+          ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+  display_list->GenerateDiscardableImagesMetadata();
+  const DiscardableImageMap& image_map = display_list->discardable_image_map();
+  EXPECT_FALSE(image_map.contains_hbd_images());
+
+  content_layer_client.add_draw_image(discardable_image, gfx::Point(0, 0),
+                                      PaintFlags());
+  display_list = content_layer_client.PaintContentsToDisplayList(
+      ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+  display_list->GenerateDiscardableImagesMetadata();
+  const DiscardableImageMap& image_map2 = display_list->discardable_image_map();
+  EXPECT_TRUE(image_map2.contains_hbd_images());
+}
 
 class DiscardableImageMapColorSpaceTest
     : public DiscardableImageMapTest,
@@ -1151,6 +1187,8 @@
   const DiscardableImageMap& image_map = display_list->discardable_image_map();
 
   EXPECT_TRUE(image_map.contains_only_srgb_images());
+  EXPECT_FALSE(image_map.contains_hdr_images());
+  EXPECT_FALSE(image_map.contains_hbd_images());
 
   content_layer_client.add_draw_image(discardable_image, gfx::Point(0, 0),
                                       PaintFlags());
@@ -1165,12 +1203,15 @@
     EXPECT_TRUE(image_map2.contains_only_srgb_images());
   else
     EXPECT_FALSE(image_map2.contains_only_srgb_images());
+
+  if (image_color_space.IsHDR())
+    EXPECT_TRUE(image_map2.contains_hdr_images());
 }
 
 gfx::ColorSpace test_color_spaces[] = {
     gfx::ColorSpace(), gfx::ColorSpace::CreateSRGB(),
-    gfx::ColorSpace::CreateDisplayP3D65(),
-};
+    gfx::ColorSpace::CreateDisplayP3D65(), gfx::ColorSpace::CreateHDR10(),
+    gfx::ColorSpace::CreateHLG()};
 
 INSTANTIATE_TEST_SUITE_P(ColorSpace,
                          DiscardableImageMapColorSpaceTest,
diff --git a/cc/paint/paint_image.cc b/cc/paint/paint_image.cc
index aa872d5..8bab5637 100644
--- a/cc/paint/paint_image.cc
+++ b/cc/paint/paint_image.cc
@@ -7,7 +7,6 @@
 #include <memory>
 #include <sstream>
 #include <utility>
-
 #include "base/atomic_sequence_num.h"
 #include "base/hash/hash.h"
 #include "cc/paint/paint_image_builder.h"
@@ -291,6 +290,20 @@
   return color_space->isSRGB();
 }
 
+bool PaintImage::isHDR() const {
+  // Right now, JS paint worklets can only be in sRGB
+  if (paint_worklet_input_)
+    return false;
+
+  auto* color_space = GetSkImage()->colorSpace();
+  if (!color_space) {
+    // Assume the image will not be HDR if we don't know yet.
+    return false;
+  }
+
+  return gfx::ColorSpace(*color_space).IsHDR();
+}
+
 const ImageHeaderMetadata* PaintImage::GetImageHeaderMetadata() const {
   if (paint_image_generator_)
     return paint_image_generator_->GetMetadataForDecodeAcceleration();
diff --git a/cc/paint/paint_image.h b/cc/paint/paint_image.h
index 40341970..6923d79 100644
--- a/cc/paint/paint_image.h
+++ b/cc/paint/paint_image.h
@@ -269,6 +269,7 @@
     return paint_worklet_input_ ? nullptr : GetSkImageInfo().colorSpace();
   }
   bool isSRGB() const;
+  bool isHDR() const;
 
   // Returns whether this image will be decoded and rendered from YUV data
   // and fills out plane size info, plane index info, and the matrix for
diff --git a/cc/paint/paint_image_unittest.cc b/cc/paint/paint_image_unittest.cc
index e135cea..5557efc 100644
--- a/cc/paint/paint_image_unittest.cc
+++ b/cc/paint/paint_image_unittest.cc
@@ -127,4 +127,42 @@
   EXPECT_EQ(paint_image.height(), size.height());
 }
 
+TEST(PaintImageTest, HbdImage) {
+  auto generator = sk_make_sp<FakePaintImageGenerator>(
+      SkImageInfo::Make(10, 10, kRGBA_F16_SkColorType, kUnknown_SkAlphaType));
+  PaintImage image = PaintImageBuilder::WithDefault()
+                         .set_id(PaintImage::GetNextId())
+                         .set_paint_image_generator(generator)
+                         .set_is_high_bit_depth(true)
+                         .TakePaintImage();
+  EXPECT_TRUE(image.is_high_bit_depth());
+  EXPECT_FALSE(image.isHDR());
+}
+
+TEST(PaintImageTest, PqHdrImage) {
+  auto generator = sk_make_sp<FakePaintImageGenerator>(
+      SkImageInfo::Make(10, 10, kRGBA_F16_SkColorType, kUnknown_SkAlphaType,
+                        gfx::ColorSpace::CreateHDR10().ToSkColorSpace()));
+  PaintImage image = PaintImageBuilder::WithDefault()
+                         .set_id(PaintImage::GetNextId())
+                         .set_paint_image_generator(generator)
+                         .set_is_high_bit_depth(true)
+                         .TakePaintImage();
+  EXPECT_TRUE(image.is_high_bit_depth());
+  EXPECT_TRUE(image.isHDR());
+}
+
+TEST(PaintImageTest, HlgHdrImage) {
+  auto generator = sk_make_sp<FakePaintImageGenerator>(
+      SkImageInfo::Make(10, 10, kRGBA_F16_SkColorType, kUnknown_SkAlphaType,
+                        gfx::ColorSpace::CreateHLG().ToSkColorSpace()));
+  PaintImage image = PaintImageBuilder::WithDefault()
+                         .set_id(PaintImage::GetNextId())
+                         .set_paint_image_generator(generator)
+                         .set_is_high_bit_depth(true)
+                         .TakePaintImage();
+  EXPECT_TRUE(image.is_high_bit_depth());
+  EXPECT_TRUE(image.isHDR());
+}
+
 }  // namespace cc
diff --git a/cc/test/layer_tree_pixel_resource_test.cc b/cc/test/layer_tree_pixel_resource_test.cc
index e7d3834..dc01385 100644
--- a/cc/test/layer_tree_pixel_resource_test.cc
+++ b/cc/test/layer_tree_pixel_resource_test.cc
@@ -25,14 +25,14 @@
 
 const char* LayerTreeHostPixelResourceTest::GetRendererSuffix() const {
   switch (renderer_type_) {
-    case RENDERER_GL:
+    case TestRendererType::kGL:
       return "gl";
-    case RENDERER_SKIA_GL:
+    case TestRendererType::kSkiaGL:
       return "skia_gl";
-    case RENDERER_SKIA_VK:
-    case RENDERER_SKIA_DAWN:
+    case TestRendererType::kSkiaVk:
+    case TestRendererType::kSkiaDawn:
       return "skia_vk";
-    case RENDERER_SOFTWARE:
+    case TestRendererType::kSoftware:
       return "sw";
   }
 }
diff --git a/cc/test/layer_tree_pixel_resource_test.h b/cc/test/layer_tree_pixel_resource_test.h
index 7f545dff..7b02d72 100644
--- a/cc/test/layer_tree_pixel_resource_test.h
+++ b/cc/test/layer_tree_pixel_resource_test.h
@@ -13,7 +13,7 @@
 namespace cc {
 
 struct PixelResourceTestCase {
-  LayerTreeTest::RendererType renderer_type;
+  TestRendererType renderer_type;
   TestRasterType raster_type;
 };
 
@@ -21,7 +21,7 @@
  public:
   explicit LayerTreeHostPixelResourceTest(PixelResourceTestCase test_case);
 
-  RendererType renderer_type() const { return test_case_.renderer_type; }
+  TestRendererType renderer_type() const { return test_case_.renderer_type; }
 
   const char* GetRendererSuffix() const;
 
diff --git a/cc/test/layer_tree_pixel_test.cc b/cc/test/layer_tree_pixel_test.cc
index a8c5102..6ab008fa 100644
--- a/cc/test/layer_tree_pixel_test.cc
+++ b/cc/test/layer_tree_pixel_test.cc
@@ -39,12 +39,12 @@
 
 namespace {
 
-TestRasterType GetDefaultRasterType(LayerTreeTest::RendererType renderer_type) {
+TestRasterType GetDefaultRasterType(TestRendererType renderer_type) {
   switch (renderer_type) {
-    case LayerTreeTest::RENDERER_SOFTWARE:
+    case TestRendererType::kSoftware:
       return TestRasterType::kBitmap;
-    case LayerTreeTest::RENDERER_SKIA_VK:
-    case LayerTreeTest::RENDERER_SKIA_DAWN:
+    case TestRendererType::kSkiaVk:
+    case TestRendererType::kSkiaDawn:
       return TestRasterType::kOop;
     default:
       return TestRasterType::kOneCopy;
@@ -53,8 +53,7 @@
 
 }  // namespace
 
-LayerTreePixelTest::LayerTreePixelTest(
-    LayerTreeTest::RendererType renderer_type)
+LayerTreePixelTest::LayerTreePixelTest(TestRendererType renderer_type)
     : LayerTreeTest(renderer_type),
       raster_type_(GetDefaultRasterType(renderer_type)),
       pixel_comparator_(new ExactPixelComparator(true)),
@@ -145,7 +144,7 @@
 LayerTreePixelTest::CreateDisplayOutputSurfaceOnThread(
     scoped_refptr<viz::ContextProvider> compositor_context_provider) {
   std::unique_ptr<PixelTestOutputSurface> display_output_surface;
-  if (renderer_type_ == RENDERER_GL) {
+  if (renderer_type_ == TestRendererType::kGL) {
     // Pixel tests use a separate context for the Display to more closely
     // mimic texture transport from the renderer process to the Display
     // compositor.
@@ -160,7 +159,7 @@
     display_output_surface = std::make_unique<PixelTestOutputSurface>(
         std::move(display_context_provider), surface_origin);
   } else {
-    EXPECT_EQ(RENDERER_SOFTWARE, renderer_type_);
+    EXPECT_EQ(TestRendererType::kSoftware, renderer_type_);
     display_output_surface = std::make_unique<PixelTestOutputSurface>(
         std::make_unique<viz::SoftwareOutputDevice>());
   }
diff --git a/cc/test/layer_tree_pixel_test.h b/cc/test/layer_tree_pixel_test.h
index 6868d47..9473c63d 100644
--- a/cc/test/layer_tree_pixel_test.h
+++ b/cc/test/layer_tree_pixel_test.h
@@ -51,7 +51,7 @@
 
 class LayerTreePixelTest : public LayerTreeTest {
  protected:
-  explicit LayerTreePixelTest(RendererType renderer_type);
+  explicit LayerTreePixelTest(TestRendererType renderer_type);
   ~LayerTreePixelTest() override;
 
   // LayerTreeTest overrides.
diff --git a/cc/test/layer_tree_test.cc b/cc/test/layer_tree_test.cc
index a13d1b8..a76ab72 100644
--- a/cc/test/layer_tree_test.cc
+++ b/cc/test/layer_tree_test.cc
@@ -611,7 +611,7 @@
   TestHooks* hooks_;
 };
 
-LayerTreeTest::LayerTreeTest(LayerTreeTest::RendererType renderer_type)
+LayerTreeTest::LayerTreeTest(TestRendererType renderer_type)
     : renderer_type_(renderer_type),
       initial_root_bounds_(1, 1),
       layer_tree_frame_sink_client_(
@@ -651,10 +651,10 @@
 
   // Check if the graphics backend needs to initialize Vulkan.
   bool init_vulkan = false;
-  if (renderer_type_ == RENDERER_SKIA_VK) {
+  if (renderer_type_ == TestRendererType::kSkiaVk) {
     scoped_feature_list_.InitAndEnableFeature(features::kVulkan);
     init_vulkan = true;
-  } else if (renderer_type_ == RENDERER_SKIA_DAWN) {
+  } else if (renderer_type_ == TestRendererType::kSkiaDawn) {
     scoped_feature_list_.InitAndEnableFeature(features::kSkiaDawn);
 #if defined(OS_LINUX)
     init_vulkan = true;
diff --git a/cc/test/layer_tree_test.h b/cc/test/layer_tree_test.h
index f8cfb762..3e28826 100644
--- a/cc/test/layer_tree_test.h
+++ b/cc/test/layer_tree_test.h
@@ -38,6 +38,16 @@
 
 namespace cc {
 
+enum TestRendererType {
+  kGL,
+  kSkiaGL,
+  kSkiaVk,
+  // SkiaRenderer with the Dawn backend will be used; on Linux this will
+  // initialize Vulkan, and on Windows this will initialize D3D12.
+  kSkiaDawn,
+  kSoftware,
+};
+
 class Animation;
 class AnimationHost;
 class LayerTreeHost;
@@ -64,27 +74,17 @@
 // thread, but be aware that ending the test is an asynchronous process.
 class LayerTreeTest : public testing::Test, public TestHooks {
  public:
-  enum RendererType {
-    RENDERER_GL,
-    RENDERER_SKIA_GL,
-    RENDERER_SKIA_VK,
-    // SkiaRenderer with the Dawn backend will be used; on Linux this will
-    // initialize Vulkan, and on Windows this will initialize D3D12.
-    RENDERER_SKIA_DAWN,
-    RENDERER_SOFTWARE,
-  };
-
   std::string TestTypeToString() {
     switch (renderer_type_) {
-      case RENDERER_GL:
+      case TestRendererType::kGL:
         return "GL";
-      case RENDERER_SKIA_GL:
+      case TestRendererType::kSkiaGL:
         return "Skia GL";
-      case RENDERER_SKIA_VK:
+      case TestRendererType::kSkiaVk:
         return "Skia Vulkan";
-      case RENDERER_SKIA_DAWN:
+      case TestRendererType::kSkiaDawn:
         return "Skia Dawn";
-      case RENDERER_SOFTWARE:
+      case TestRendererType::kSoftware:
         return "Software";
     }
   }
@@ -128,7 +128,8 @@
   void SetUseLayerLists() { settings_.use_layer_lists = true; }
 
  protected:
-  explicit LayerTreeTest(RendererType renderer_type = RENDERER_GL);
+  explicit LayerTreeTest(
+      TestRendererType renderer_type = TestRendererType::kGL);
 
   void SkipAllocateInitialLocalSurfaceId();
   const viz::LocalSurfaceIdAllocation& GetCurrentLocalSurfaceIdAllocation()
@@ -218,23 +219,25 @@
   }
 
   bool use_skia_renderer() const {
-    return renderer_type_ == RENDERER_SKIA_GL ||
-           renderer_type_ == RENDERER_SKIA_VK ||
-           renderer_type_ == RENDERER_SKIA_DAWN;
+    return renderer_type_ == TestRendererType::kSkiaGL ||
+           renderer_type_ == TestRendererType::kSkiaVk ||
+           renderer_type_ == TestRendererType::kSkiaDawn;
   }
   bool use_software_renderer() const {
-    return renderer_type_ == RENDERER_SOFTWARE;
+    return renderer_type_ == TestRendererType::kSoftware;
   }
-  bool use_skia_vulkan() const { return renderer_type_ == RENDERER_SKIA_VK; }
+  bool use_skia_vulkan() const {
+    return renderer_type_ == TestRendererType::kSkiaVk;
+  }
   bool use_d3d12() const {
 #if defined(OS_WIN)
-    return renderer_type_ == RENDERER_SKIA_DAWN;
+    return renderer_type_ == TestRendererType::kSkiaDawn;
 #else
     return false;
 #endif
   }
 
-  const RendererType renderer_type_;
+  const TestRendererType renderer_type_;
 
   const viz::DebugRendererSettings debug_settings_;
 
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 9811ffee..b2c0baa 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -2521,8 +2521,15 @@
       frame->use_default_lower_bound_deadline);
 
   frame_rate_estimator_.WillDraw(CurrentBeginFrameArgs().frame_time);
-  metadata.preferred_frame_interval =
-      frame_rate_estimator_.GetPreferredInterval();
+
+  if (settings_.force_preferred_interval_for_video) {
+    // Use max interval to ensure the compositor's updates don't affect
+    // display's refresh rate.
+    metadata.preferred_frame_interval = base::TimeDelta::Max();
+  } else {
+    metadata.preferred_frame_interval =
+        frame_rate_estimator_.GetPreferredInterval();
+  }
 
   metadata.activation_dependencies = std::move(frame->activation_dependencies);
   active_tree()->FinishSwapPromises(&metadata);
diff --git a/cc/trees/layer_tree_host_pixeltest_blending.cc b/cc/trees/layer_tree_host_pixeltest_blending.cc
index 954d34d..390a9f8 100644
--- a/cc/trees/layer_tree_host_pixeltest_blending.cc
+++ b/cc/trees/layer_tree_host_pixeltest_blending.cc
@@ -213,7 +213,7 @@
     const int kRootHeight = kRootWidth * kCSSTestColorsCount;
 
     // Force shaders only applies to gl renderer.
-    if (renderer_type_ != RENDERER_GL && flags & kForceShaders)
+    if (renderer_type_ != TestRendererType::kGL && flags & kForceShaders)
       return;
 
     SCOPED_TRACE(TestTypeToString());
@@ -232,8 +232,8 @@
     force_antialiasing_ = (flags & kUseAntialiasing);
     force_blending_with_shaders_ = (flags & kForceShaders);
 
-    if ((renderer_type_ == RENDERER_GL && force_antialiasing_) ||
-        renderer_type_ == RENDERER_SKIA_VK) {
+    if ((renderer_type_ == TestRendererType::kGL && force_antialiasing_) ||
+        renderer_type_ == TestRendererType::kSkiaVk) {
       // Blending results might differ with one pixel.
       float percentage_pixels_error = 35.f;
       float percentage_pixels_small_error = 0.f;
@@ -260,11 +260,11 @@
 };
 
 std::vector<PixelResourceTestCase> const kTestCases = {
-    {LayerTreeTest::RENDERER_SOFTWARE, TestRasterType::kBitmap},
-    {LayerTreeTest::RENDERER_GL, TestRasterType::kZeroCopy},
-    {LayerTreeTest::RENDERER_SKIA_GL, TestRasterType::kGpu},
+    {TestRendererType::kSoftware, TestRasterType::kBitmap},
+    {TestRendererType::kGL, TestRasterType::kZeroCopy},
+    {TestRendererType::kSkiaGL, TestRasterType::kGpu},
 #if defined(ENABLE_CC_VULKAN_TESTS)
-    {LayerTreeTest::RENDERER_SKIA_VK, TestRasterType::kOop},
+    {TestRendererType::kSkiaVk, TestRasterType::kOop},
 #endif  // defined(ENABLE_CC_VULKAN_TESTS)
 };
 
diff --git a/cc/trees/layer_tree_host_pixeltest_filters.cc b/cc/trees/layer_tree_host_pixeltest_filters.cc
index 4042e973..1b07158 100644
--- a/cc/trees/layer_tree_host_pixeltest_filters.cc
+++ b/cc/trees/layer_tree_host_pixeltest_filters.cc
@@ -20,25 +20,25 @@
 
 class LayerTreeHostFiltersPixelTest
     : public LayerTreePixelTest,
-      public ::testing::WithParamInterface<LayerTreeTest::RendererType> {
+      public ::testing::WithParamInterface<TestRendererType> {
  protected:
   LayerTreeHostFiltersPixelTest() : LayerTreePixelTest(renderer_type()) {}
 
-  RendererType renderer_type() const { return GetParam(); }
+  TestRendererType renderer_type() const { return GetParam(); }
 
-  // Text string for graphics backend of the RendererType. Suitable for
+  // Text string for graphics backend of the TestRendererType. Suitable for
   // generating separate base line file paths.
   const char* GetRendererSuffix() {
     switch (renderer_type_) {
-      case RENDERER_GL:
+      case TestRendererType::kGL:
         return "gl";
-      case RENDERER_SKIA_GL:
+      case TestRendererType::kSkiaGL:
         return "skia_gl";
-      case RENDERER_SKIA_VK:
+      case TestRendererType::kSkiaVk:
         return "skia_vk";
-      case RENDERER_SKIA_DAWN:
+      case TestRendererType::kSkiaDawn:
         return "skia_dawn";
-      case RENDERER_SOFTWARE:
+      case TestRendererType::kSoftware:
         return "sw";
     }
   }
@@ -72,14 +72,14 @@
   }
 };
 
-LayerTreeTest::RendererType const kRendererTypes[] = {
-    LayerTreeTest::RENDERER_GL,        LayerTreeTest::RENDERER_SKIA_GL,
-    LayerTreeTest::RENDERER_SOFTWARE,
+TestRendererType const kRendererTypes[] = {
+    TestRendererType::kGL,       TestRendererType::kSkiaGL,
+    TestRendererType::kSoftware,
 #if defined(ENABLE_CC_VULKAN_TESTS)
-    LayerTreeTest::RENDERER_SKIA_VK,
+    TestRendererType::kSkiaVk,
 #endif  // defined(ENABLE_CC_VULKAN_TESTS)
 #if defined(ENABLE_CC_DAWN_TESTS)
-    LayerTreeTest::RENDERER_SKIA_DAWN,
+    TestRendererType::kSkiaDawn,
 #endif  // defined(ENABLE_CC_DAWN_TESTS)
 };
 
@@ -89,14 +89,14 @@
 
 using LayerTreeHostFiltersPixelTestGPU = LayerTreeHostFiltersPixelTest;
 
-LayerTreeTest::RendererType const kRendererTypesGpu[] = {
-    LayerTreeTest::RENDERER_GL,
-    LayerTreeTest::RENDERER_SKIA_GL,
+TestRendererType const kRendererTypesGpu[] = {
+    TestRendererType::kGL,
+    TestRendererType::kSkiaGL,
 #if defined(ENABLE_CC_VULKAN_TESTS)
-    LayerTreeTest::RENDERER_SKIA_VK,
+    TestRendererType::kSkiaVk,
 #endif  // defined(ENABLE_CC_VULKAN_TESTS)
 #if defined(ENABLE_CC_DAWN_TESTS)
-    LayerTreeTest::RENDERER_SKIA_DAWN,
+    TestRendererType::kSkiaDawn,
 #endif  // defined(ENABLE_CC_DAWN_TESTS)
 };
 
@@ -382,11 +382,11 @@
 
 // TODO(sgilhuly): Enable these tests for Skia Dawn, and switch over to using
 // kRendererTypesGpu.
-LayerTreeTest::RendererType const kRendererTypesGpuNonDawn[] = {
-    LayerTreeTest::RENDERER_GL,
-    LayerTreeTest::RENDERER_SKIA_GL,
+TestRendererType const kRendererTypesGpuNonDawn[] = {
+    TestRendererType::kGL,
+    TestRendererType::kSkiaGL,
 #if defined(ENABLE_CC_VULKAN_TESTS)
-    LayerTreeTest::RENDERER_SKIA_VK,
+    TestRendererType::kSkiaVk,
 #endif  // defined(ENABLE_CC_VULKAN_TESTS)
 };
 
@@ -755,12 +755,12 @@
 // kRendererTypes.
 using LayerTreeHostFiltersPixelTestNonDawn = LayerTreeHostFiltersPixelTest;
 
-LayerTreeTest::RendererType const kRendererTypesNonDawn[] = {
-    LayerTreeTest::RENDERER_GL,
-    LayerTreeTest::RENDERER_SKIA_GL,
-    LayerTreeTest::RENDERER_SOFTWARE,
+TestRendererType const kRendererTypesNonDawn[] = {
+    TestRendererType::kGL,
+    TestRendererType::kSkiaGL,
+    TestRendererType::kSoftware,
 #if defined(ENABLE_CC_VULKAN_TESTS)
-    LayerTreeTest::RENDERER_SKIA_VK,
+    TestRendererType::kSkiaVk,
 #endif  // defined(ENABLE_CC_VULKAN_TESTS)
 };
 
@@ -987,7 +987,7 @@
   parent->AddChild(child);
   clip->AddChild(parent);
 
-  if (use_software_renderer() || renderer_type_ == RENDERER_SKIA_DAWN)
+  if (use_software_renderer() || renderer_type_ == TestRendererType::kSkiaDawn)
     pixel_comparator_ = std::make_unique<FuzzyPixelOffByOneComparator>(true);
 
   RunPixelTest(clip, base::FilePath(
diff --git a/cc/trees/layer_tree_host_pixeltest_masks.cc b/cc/trees/layer_tree_host_pixeltest_masks.cc
index bb35765..6586f68 100644
--- a/cc/trees/layer_tree_host_pixeltest_masks.cc
+++ b/cc/trees/layer_tree_host_pixeltest_masks.cc
@@ -29,16 +29,16 @@
 // TODO(penghuang): Fix vulkan with one copy or zero copy
 // https://crbug.com/979703
 std::vector<PixelResourceTestCase> const kTestCases = {
-    {LayerTreeTest::RENDERER_SOFTWARE, TestRasterType::kBitmap},
-    {LayerTreeTest::RENDERER_GL, TestRasterType::kGpu},
-    {LayerTreeTest::RENDERER_GL, TestRasterType::kOneCopy},
-    {LayerTreeTest::RENDERER_GL, TestRasterType::kZeroCopy},
-    {LayerTreeTest::RENDERER_SKIA_GL, TestRasterType::kGpu},
-    {LayerTreeTest::RENDERER_SKIA_GL, TestRasterType::kOneCopy},
-    {LayerTreeTest::RENDERER_SKIA_GL, TestRasterType::kZeroCopy},
+    {TestRendererType::kSoftware, TestRasterType::kBitmap},
+    {TestRendererType::kGL, TestRasterType::kGpu},
+    {TestRendererType::kGL, TestRasterType::kOneCopy},
+    {TestRendererType::kGL, TestRasterType::kZeroCopy},
+    {TestRendererType::kSkiaGL, TestRasterType::kGpu},
+    {TestRendererType::kSkiaGL, TestRasterType::kOneCopy},
+    {TestRendererType::kSkiaGL, TestRasterType::kZeroCopy},
 #if defined(ENABLE_CC_VULKAN_TESTS)
-    {LayerTreeTest::RENDERER_SKIA_VK, TestRasterType::kOop},
-    {LayerTreeTest::RENDERER_SKIA_VK, TestRasterType::kZeroCopy},
+    {TestRendererType::kSkiaVk, TestRasterType::kOop},
+    {TestRendererType::kSkiaVk, TestRasterType::kZeroCopy},
 #endif  // defined(ENABLE_CC_VULKAN_TESTS)
 };
 
@@ -868,23 +868,20 @@
 };
 
 MaskTestConfig const kTestConfigs[] = {
-    MaskTestConfig{{LayerTreeTest::RENDERER_SOFTWARE, TestRasterType::kBitmap},
-                   0},
-    MaskTestConfig{{LayerTreeTest::RENDERER_GL, TestRasterType::kZeroCopy}, 0},
-    MaskTestConfig{{LayerTreeTest::RENDERER_GL, TestRasterType::kZeroCopy},
+    MaskTestConfig{{TestRendererType::kSoftware, TestRasterType::kBitmap}, 0},
+    MaskTestConfig{{TestRendererType::kGL, TestRasterType::kZeroCopy}, 0},
+    MaskTestConfig{{TestRendererType::kGL, TestRasterType::kZeroCopy},
                    kUseAntialiasing},
-    MaskTestConfig{{LayerTreeTest::RENDERER_GL, TestRasterType::kZeroCopy},
+    MaskTestConfig{{TestRendererType::kGL, TestRasterType::kZeroCopy},
                    kForceShaders},
-    MaskTestConfig{{LayerTreeTest::RENDERER_GL, TestRasterType::kZeroCopy},
+    MaskTestConfig{{TestRendererType::kGL, TestRasterType::kZeroCopy},
                    kUseAntialiasing | kForceShaders},
-    MaskTestConfig{{LayerTreeTest::RENDERER_SKIA_GL, TestRasterType::kZeroCopy},
-                   0},
-    MaskTestConfig{{LayerTreeTest::RENDERER_SKIA_GL, TestRasterType::kZeroCopy},
+    MaskTestConfig{{TestRendererType::kSkiaGL, TestRasterType::kZeroCopy}, 0},
+    MaskTestConfig{{TestRendererType::kSkiaGL, TestRasterType::kZeroCopy},
                    kUseAntialiasing},
 #if defined(ENABLE_CC_VULKAN_TESTS)
-    MaskTestConfig{{LayerTreeTest::RENDERER_SKIA_VK, TestRasterType::kZeroCopy},
-                   0},
-    MaskTestConfig{{LayerTreeTest::RENDERER_SKIA_VK, TestRasterType::kZeroCopy},
+    MaskTestConfig{{TestRendererType::kSkiaVk, TestRasterType::kZeroCopy}, 0},
+    MaskTestConfig{{TestRendererType::kSkiaVk, TestRasterType::kZeroCopy},
                    kUseAntialiasing},
 #endif  // defined(ENABLE_CC_VULKAN_TESTS)
 };
diff --git a/cc/trees/layer_tree_host_pixeltest_mirror.cc b/cc/trees/layer_tree_host_pixeltest_mirror.cc
index 82a9f66..1165594 100644
--- a/cc/trees/layer_tree_host_pixeltest_mirror.cc
+++ b/cc/trees/layer_tree_host_pixeltest_mirror.cc
@@ -17,19 +17,19 @@
 
 class LayerTreeHostMirrorPixelTest
     : public LayerTreePixelTest,
-      public ::testing::WithParamInterface<LayerTreeTest::RendererType> {
+      public ::testing::WithParamInterface<TestRendererType> {
  protected:
   LayerTreeHostMirrorPixelTest() : LayerTreePixelTest(renderer_type()) {}
 
-  RendererType renderer_type() const { return GetParam(); }
+  TestRendererType renderer_type() const { return GetParam(); }
 };
 
-const LayerTreeTest::RendererType kRendererTypes[] = {
-    LayerTreeTest::RENDERER_GL,
-    LayerTreeTest::RENDERER_SKIA_GL,
-    LayerTreeTest::RENDERER_SOFTWARE,
+const TestRendererType kRendererTypes[] = {
+    TestRendererType::kGL,
+    TestRendererType::kSkiaGL,
+    TestRendererType::kSoftware,
 #if defined(ENABLE_CC_VULKAN_TESTS)
-    LayerTreeTest::RENDERER_SKIA_VK,
+    TestRendererType::kSkiaVk,
 #endif  // defined(ENABLE_CC_VULKAN_TESTS)
 };
 
diff --git a/cc/trees/layer_tree_host_pixeltest_readback.cc b/cc/trees/layer_tree_host_pixeltest_readback.cc
index 6d7e936..52d7723 100644
--- a/cc/trees/layer_tree_host_pixeltest_readback.cc
+++ b/cc/trees/layer_tree_host_pixeltest_readback.cc
@@ -21,16 +21,16 @@
 namespace cc {
 namespace {
 
-// Can't templatize a class on its own members, so ReadbackType and
+// Can't templatize a class on its own members, so TestReadBackType and
 // ReadbackTestConfig are declared here, before LayerTreeHostReadbackPixelTest.
-enum ReadbackType {
-  READBACK_TEXTURE,
-  READBACK_BITMAP,
+enum class TestReadBackType {
+  kTexture,
+  kBitmap,
 };
 
 struct ReadbackTestConfig {
-  LayerTreeTest::RendererType renderer_type;
-  ReadbackType readback_type;
+  TestRendererType renderer_type;
+  TestReadBackType readback_type;
 };
 
 class LayerTreeHostReadbackPixelTest
@@ -41,21 +41,21 @@
       : LayerTreePixelTest(renderer_type()),
         insert_copy_request_after_frame_count_(0) {}
 
-  RendererType renderer_type() const { return GetParam().renderer_type; }
+  TestRendererType renderer_type() const { return GetParam().renderer_type; }
 
-  ReadbackType readback_type() const { return GetParam().readback_type; }
+  TestReadBackType readback_type() const { return GetParam().readback_type; }
 
   std::unique_ptr<viz::CopyOutputRequest> CreateCopyOutputRequest() override {
     std::unique_ptr<viz::CopyOutputRequest> request;
 
-    if (readback_type() == READBACK_BITMAP) {
+    if (readback_type() == TestReadBackType::kBitmap) {
       request = std::make_unique<viz::CopyOutputRequest>(
           viz::CopyOutputRequest::ResultFormat::RGBA_BITMAP,
           base::BindOnce(
               &LayerTreeHostReadbackPixelTest::ReadbackResultAsBitmap,
               base::Unretained(this)));
     } else {
-      DCHECK_NE(renderer_type_, RENDERER_SOFTWARE);
+      DCHECK_NE(renderer_type_, TestRendererType::kSoftware);
       request = std::make_unique<viz::CopyOutputRequest>(
           viz::CopyOutputRequest::ResultFormat::RGBA_TEXTURE,
           base::BindOnce(
@@ -417,17 +417,18 @@
 // TODO(crbug.com/971257): Enable these tests for Skia Vulkan using texture
 // readback.
 ReadbackTestConfig const kTestConfigs[] = {
-    ReadbackTestConfig{LayerTreeTest::RENDERER_SOFTWARE, READBACK_BITMAP},
-    ReadbackTestConfig{LayerTreeTest::RENDERER_GL, READBACK_TEXTURE},
-    ReadbackTestConfig{LayerTreeTest::RENDERER_GL, READBACK_BITMAP},
+    ReadbackTestConfig{TestRendererType::kSoftware, TestReadBackType::kBitmap},
+    ReadbackTestConfig{TestRendererType::kGL, TestReadBackType::kTexture},
+    ReadbackTestConfig{TestRendererType::kGL, TestReadBackType::kBitmap},
     // TODO(crbug.com/1046788): The skia readback path doesn't support
     // RGBA_TEXTURE readback requests yet. Don't run these tests on platforms
     // that have UseSkiaForGLReadback enabled by default.
     //
-    // ReadbackTestConfig{LayerTreeTest::RENDERER_SKIA_GL, READBACK_TEXTURE},
-    ReadbackTestConfig{LayerTreeTest::RENDERER_SKIA_GL, READBACK_BITMAP},
+    // ReadbackTestConfig{TestRendererType::kSkiaGL,
+    //                    TestReadBackType::kTexture},
+    ReadbackTestConfig{TestRendererType::kSkiaGL, TestReadBackType::kBitmap},
 #if defined(ENABLE_CC_VULKAN_TESTS)
-    ReadbackTestConfig{LayerTreeTest::RENDERER_SKIA_VK, READBACK_BITMAP},
+    ReadbackTestConfig{TestRendererType::kSkiaVk, TestReadBackType::kBitmap},
 #endif  // defined(ENABLE_CC_VULKAN_TESTS)
 };
 
@@ -438,18 +439,19 @@
 // TODO(crbug.com/974283): These tests are crashing with vulkan when TSan or
 // MSan are used.
 ReadbackTestConfig const kMaybeVulkanTestConfigs[] = {
-    ReadbackTestConfig{LayerTreeTest::RENDERER_SOFTWARE, READBACK_BITMAP},
-    ReadbackTestConfig{LayerTreeTest::RENDERER_GL, READBACK_TEXTURE},
-    ReadbackTestConfig{LayerTreeTest::RENDERER_GL, READBACK_BITMAP},
+    ReadbackTestConfig{TestRendererType::kSoftware, TestReadBackType::kBitmap},
+    ReadbackTestConfig{TestRendererType::kGL, TestReadBackType::kTexture},
+    ReadbackTestConfig{TestRendererType::kGL, TestReadBackType::kBitmap},
     // TODO(crbug.com/1046788): The skia readback path doesn't support
     // RGBA_TEXTURE readback requests yet. Don't run these tests on platforms
     // that have UseSkiaForGLReadback enabled by default.
     //
-    // ReadbackTestConfig{LayerTreeTest::RENDERER_SKIA_GL, READBACK_TEXTURE},
-    ReadbackTestConfig{LayerTreeTest::RENDERER_SKIA_GL, READBACK_BITMAP},
+    // ReadbackTestConfig{TestRendererType::kSkiaGL,
+    //                    TestReadBackType::kTexture},
+    ReadbackTestConfig{TestRendererType::kSkiaGL, TestReadBackType::kBitmap},
 #if defined(ENABLE_CC_VULKAN_TESTS) && !defined(THREAD_SANITIZER) && \
     !defined(MEMORY_SANITIZER)
-    ReadbackTestConfig{LayerTreeTest::RENDERER_SKIA_VK, READBACK_BITMAP},
+    ReadbackTestConfig{TestRendererType::kSkiaVk, TestReadBackType::kBitmap},
 #endif
 };
 
diff --git a/cc/trees/layer_tree_host_pixeltest_scrollbars.cc b/cc/trees/layer_tree_host_pixeltest_scrollbars.cc
index 5dc2efd..c8b4b02d 100644
--- a/cc/trees/layer_tree_host_pixeltest_scrollbars.cc
+++ b/cc/trees/layer_tree_host_pixeltest_scrollbars.cc
@@ -25,11 +25,11 @@
 
 class LayerTreeHostScrollbarsPixelTest
     : public LayerTreePixelTest,
-      public ::testing::WithParamInterface<LayerTreeTest::RendererType> {
+      public ::testing::WithParamInterface<TestRendererType> {
  protected:
   LayerTreeHostScrollbarsPixelTest() : LayerTreePixelTest(renderer_type()) {}
 
-  RendererType renderer_type() const { return GetParam(); }
+  TestRendererType renderer_type() const { return GetParam(); }
 
   void SetupTree() override {
     SetInitialDeviceScaleFactor(device_scale_factor_);
@@ -73,11 +73,11 @@
   SkColor color_ = SK_ColorGREEN;
 };
 
-LayerTreeTest::RendererType const kRendererTypes[] = {
-    LayerTreeTest::RENDERER_GL,
-    LayerTreeTest::RENDERER_SKIA_GL,
+TestRendererType const kRendererTypes[] = {
+    TestRendererType::kGL,
+    TestRendererType::kSkiaGL,
 #if defined(ENABLE_CC_VULKAN_TESTS)
-    LayerTreeTest::RENDERER_SKIA_VK,
+    TestRendererType::kSkiaVk,
 #endif  // defined(ENABLE_CC_VULKAN_TESTS)
 };
 
@@ -180,7 +180,7 @@
   scale_transform.Scale(scale, scale);
   layer->SetTransform(scale_transform);
 
-  if (renderer_type_ == RENDERER_SKIA_GL)
+  if (renderer_type_ == TestRendererType::kSkiaGL)
     pixel_comparator_ = std::make_unique<FuzzyPixelOffByOneComparator>(true);
 
   RunPixelTest(background,
diff --git a/cc/trees/layer_tree_host_pixeltest_synchronous.cc b/cc/trees/layer_tree_host_pixeltest_synchronous.cc
index e85b3f30..484851d 100644
--- a/cc/trees/layer_tree_host_pixeltest_synchronous.cc
+++ b/cc/trees/layer_tree_host_pixeltest_synchronous.cc
@@ -17,7 +17,7 @@
 
 class LayerTreeHostSynchronousPixelTest
     : public LayerTreePixelTest,
-      public ::testing::WithParamInterface<LayerTreeTest::RendererType> {
+      public ::testing::WithParamInterface<TestRendererType> {
  protected:
   LayerTreeHostSynchronousPixelTest() : LayerTreePixelTest(renderer_type()) {}
 
@@ -26,7 +26,7 @@
     settings->single_thread_proxy_scheduler = false;
   }
 
-  RendererType renderer_type() const { return GetParam(); }
+  TestRendererType renderer_type() const { return GetParam(); }
 
   void BeginTest() override {
     LayerTreePixelTest::BeginTest();
@@ -50,11 +50,11 @@
   }
 };
 
-LayerTreeTest::RendererType const kRendererTypesGpu[] = {
-    LayerTreeTest::RENDERER_GL,
-    LayerTreeTest::RENDERER_SKIA_GL,
+TestRendererType const kRendererTypesGpu[] = {
+    TestRendererType::kGL,
+    TestRendererType::kSkiaGL,
 #if defined(ENABLE_CC_VULKAN_TESTS)
-    LayerTreeTest::RENDERER_SKIA_VK,
+    TestRendererType::kSkiaVk,
 #endif  // defined(ENABLE_CC_VULKAN_TESTS)
 };
 
diff --git a/cc/trees/layer_tree_host_pixeltest_tiles.cc b/cc/trees/layer_tree_host_pixeltest_tiles.cc
index 55c8e4f..c1faa41 100644
--- a/cc/trees/layer_tree_host_pixeltest_tiles.cc
+++ b/cc/trees/layer_tree_host_pixeltest_tiles.cc
@@ -21,7 +21,7 @@
 namespace {
 
 struct TilesTestConfig {
-  LayerTreeTest::RendererType renderer_type;
+  TestRendererType renderer_type;
   TestRasterType raster_type;
 };
 
@@ -33,7 +33,7 @@
     set_raster_type(GetParam().raster_type);
   }
 
-  RendererType renderer_type() const { return GetParam().renderer_type; }
+  TestRendererType renderer_type() const { return GetParam().renderer_type; }
 
   void InitializeSettings(LayerTreeSettings* settings) override {
     LayerTreePixelTest::InitializeSettings(settings);
@@ -153,13 +153,13 @@
 };
 
 std::vector<TilesTestConfig> const kTestCases = {
-    {LayerTreeTest::RENDERER_SOFTWARE, TestRasterType::kBitmap},
-    {LayerTreeTest::RENDERER_GL, TestRasterType::kOneCopy},
-    {LayerTreeTest::RENDERER_GL, TestRasterType::kGpu},
-    {LayerTreeTest::RENDERER_SKIA_GL, TestRasterType::kOneCopy},
-    {LayerTreeTest::RENDERER_SKIA_GL, TestRasterType::kGpu},
+    {TestRendererType::kSoftware, TestRasterType::kBitmap},
+    {TestRendererType::kGL, TestRasterType::kOneCopy},
+    {TestRendererType::kGL, TestRasterType::kGpu},
+    {TestRendererType::kSkiaGL, TestRasterType::kOneCopy},
+    {TestRendererType::kSkiaGL, TestRasterType::kGpu},
 #if defined(ENABLE_CC_VULKAN_TESTS)
-    {LayerTreeTest::RENDERER_SKIA_VK, TestRasterType::kOop},
+    {TestRendererType::kSkiaVk, TestRasterType::kOop},
 #endif  // defined(ENABLE_CC_VULKAN_TESTS)
 };
 
@@ -188,12 +188,12 @@
 }
 
 std::vector<TilesTestConfig> const kTestCasesMultiThread = {
-    {LayerTreeTest::RENDERER_GL, TestRasterType::kOneCopy},
-    {LayerTreeTest::RENDERER_SKIA_GL, TestRasterType::kOneCopy},
+    {TestRendererType::kGL, TestRasterType::kOneCopy},
+    {TestRendererType::kSkiaGL, TestRasterType::kOneCopy},
 #if defined(ENABLE_CC_VULKAN_TESTS)
     // TODO(sgilhuly): Switch this to one copy raster once is is supported for
     // Vulkan in these tests.
-    {LayerTreeTest::RENDERER_SKIA_VK, TestRasterType::kOop},
+    {TestRendererType::kSkiaVk, TestRasterType::kOop},
 #endif  // defined(ENABLE_CC_VULKAN_TESTS)
 };
 
@@ -244,8 +244,8 @@
     All,
     LayerTreeHostTilesTestPartialInvalidationLowBitDepth,
     ::testing::Values(
-        TilesTestConfig{LayerTreeTest::RENDERER_SKIA_GL, TestRasterType::kGpu},
-        TilesTestConfig{LayerTreeTest::RENDERER_GL, TestRasterType::kGpu}));
+        TilesTestConfig{TestRendererType::kSkiaGL, TestRasterType::kGpu},
+        TilesTestConfig{TestRendererType::kGL, TestRasterType::kGpu}));
 
 TEST_P(LayerTreeHostTilesTestPartialInvalidationLowBitDepth, PartialRaster) {
   use_partial_raster_ = true;
diff --git a/cc/trees/layer_tree_host_unittest.cc b/cc/trees/layer_tree_host_unittest.cc
index 6006136..3ed4b97 100644
--- a/cc/trees/layer_tree_host_unittest.cc
+++ b/cc/trees/layer_tree_host_unittest.cc
@@ -8246,7 +8246,8 @@
 
 class LayerTreeHostTestImageAnimation : public LayerTreeHostTest {
  public:
-  explicit LayerTreeHostTestImageAnimation(RendererType type = RENDERER_GL)
+  explicit LayerTreeHostTestImageAnimation(
+      TestRendererType type = TestRendererType::kGL)
       : LayerTreeHostTest(type) {}
 
   void BeginTest() override { PostSetNeedsCommitToMainThread(); }
@@ -8327,7 +8328,7 @@
     : public LayerTreeHostTestImageAnimation {
  public:
   explicit LayerTreeHostTestImageAnimationDrawImage(
-      RendererType type = RENDERER_GL)
+      TestRendererType type = TestRendererType::kGL)
       : LayerTreeHostTestImageAnimation(type) {}
 
  private:
@@ -8383,7 +8384,7 @@
     : public LayerTreeHostTestImageAnimationDrawImage {
  public:
   explicit LayerTreeHostTestImageAnimationSynchronousScheduling(
-      RendererType type = RENDERER_GL)
+      TestRendererType type = TestRendererType::kGL)
       : LayerTreeHostTestImageAnimationDrawImage(type) {}
 
   void InitializeSettings(LayerTreeSettings* settings) override {
@@ -8399,7 +8400,7 @@
  public:
   LayerTreeHostTestImageAnimationSynchronousSchedulingSoftwareDraw()
       : LayerTreeHostTestImageAnimationSynchronousScheduling(
-            RENDERER_SOFTWARE) {}
+            TestRendererType::kSoftware) {}
 
   void AfterTest() override {
     LayerTreeHostTestImageAnimationSynchronousScheduling::AfterTest();
diff --git a/cc/trees/layer_tree_host_unittest_copyrequest.cc b/cc/trees/layer_tree_host_unittest_copyrequest.cc
index 8a2245f..372d7c16 100644
--- a/cc/trees/layer_tree_host_unittest_copyrequest.cc
+++ b/cc/trees/layer_tree_host_unittest_copyrequest.cc
@@ -27,8 +27,7 @@
 namespace cc {
 namespace {
 
-auto CombineWithCompositorModes(
-    const std::vector<LayerTreeTest::RendererType>& types) {
+auto CombineWithCompositorModes(const std::vector<TestRendererType>& types) {
   return ::testing::Combine(::testing::ValuesIn(types),
                             ::testing::Values(CompositorMode::SINGLE_THREADED,
                                               CompositorMode::THREADED));
@@ -37,7 +36,7 @@
 class LayerTreeHostCopyRequestTest
     : public LayerTreeTest,
       public ::testing::WithParamInterface<
-          ::testing::tuple<LayerTreeTest::RendererType, CompositorMode>> {
+          ::testing::tuple<TestRendererType, CompositorMode>> {
  public:
   LayerTreeHostCopyRequestTest() : LayerTreeTest(renderer_type()) {}
   ~LayerTreeHostCopyRequestTest() {
@@ -46,7 +45,9 @@
     CCTestSuite::RunUntilIdle();
   }
 
-  RendererType renderer_type() const { return ::testing::get<0>(GetParam()); }
+  TestRendererType renderer_type() const {
+    return ::testing::get<0>(GetParam());
+  }
 
   CompositorMode compositor_mode() const {
     return ::testing::get<1>(GetParam());
@@ -167,12 +168,11 @@
   scoped_refptr<FakePictureLayer> grand_child;
 };
 
-INSTANTIATE_TEST_SUITE_P(
-    All,
-    LayerTreeHostCopyRequestTestMultipleRequests,
-    CombineWithCompositorModes({LayerTreeTest::RENDERER_GL,
-                                LayerTreeTest::RENDERER_SKIA_GL,
-                                LayerTreeTest::RENDERER_SOFTWARE}));
+INSTANTIATE_TEST_SUITE_P(All,
+                         LayerTreeHostCopyRequestTestMultipleRequests,
+                         CombineWithCompositorModes(
+                             {TestRendererType::kGL, TestRendererType::kSkiaGL,
+                              TestRendererType::kSoftware}));
 
 TEST_P(LayerTreeHostCopyRequestTestMultipleRequests, Test) {
   RunTest(compositor_mode());
@@ -206,8 +206,8 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     LayerTreeHostCopyRequestTestMultipleRequestsOutOfOrder,
-    CombineWithCompositorModes({LayerTreeTest::RENDERER_GL,
-                                LayerTreeTest::RENDERER_SKIA_GL}));
+    CombineWithCompositorModes({TestRendererType::kGL,
+                                TestRendererType::kSkiaGL}));
 
 TEST_P(LayerTreeHostCopyRequestTestMultipleRequestsOutOfOrder, Test) {
   RunTest(compositor_mode());
@@ -267,8 +267,8 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     LayerTreeHostCopyRequestCompletionCausesCommit,
-    CombineWithCompositorModes({LayerTreeTest::RENDERER_GL,
-                                LayerTreeTest::RENDERER_SKIA_GL}));
+    CombineWithCompositorModes({TestRendererType::kGL,
+                                TestRendererType::kSkiaGL}));
 
 TEST_P(LayerTreeHostCopyRequestCompletionCausesCommit, Test) {
   RunTest(compositor_mode());
@@ -376,10 +376,11 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     LayerTreeHostCopyRequestTestLayerDestroyed,
-    CombineWithCompositorModes({LayerTreeTest::RENDERER_GL,
-                                LayerTreeTest::RENDERER_SKIA_GL}));
+    CombineWithCompositorModes({TestRendererType::kGL,
+                                TestRendererType::kSkiaGL}));
 
-TEST_P(LayerTreeHostCopyRequestTestLayerDestroyed, Test) {
+// TODO(crbug/1096962): Investigate flakiness and reenable test once fixed.
+TEST_P(LayerTreeHostCopyRequestTestLayerDestroyed, DISABLED_Test) {
   RunTest(compositor_mode());
 }
 
@@ -482,8 +483,8 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     LayerTreeHostCopyRequestTestInHiddenSubtree,
-    CombineWithCompositorModes({LayerTreeTest::RENDERER_GL,
-                                LayerTreeTest::RENDERER_SKIA_GL}));
+    CombineWithCompositorModes({TestRendererType::kGL,
+                                TestRendererType::kSkiaGL}));
 
 TEST_P(LayerTreeHostCopyRequestTestInHiddenSubtree, Test) {
   RunTest(compositor_mode());
@@ -603,8 +604,8 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     LayerTreeHostTestHiddenSurfaceNotAllocatedForSubtreeCopyRequest,
-    CombineWithCompositorModes({LayerTreeTest::RENDERER_GL,
-                                LayerTreeTest::RENDERER_SKIA_GL}));
+    CombineWithCompositorModes({TestRendererType::kGL,
+                                TestRendererType::kSkiaGL}));
 
 TEST_P(LayerTreeHostTestHiddenSurfaceNotAllocatedForSubtreeCopyRequest, Test) {
   RunTest(compositor_mode());
@@ -659,8 +660,8 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     LayerTreeHostCopyRequestTestClippedOut,
-    CombineWithCompositorModes({LayerTreeTest::RENDERER_GL,
-                                LayerTreeTest::RENDERER_SKIA_GL}));
+    CombineWithCompositorModes({TestRendererType::kGL,
+                                TestRendererType::kSkiaGL}));
 
 TEST_P(LayerTreeHostCopyRequestTestClippedOut, Test) {
   RunTest(compositor_mode());
@@ -719,8 +720,8 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     LayerTreeHostCopyRequestTestScaledLayer,
-    CombineWithCompositorModes({LayerTreeTest::RENDERER_GL,
-                                LayerTreeTest::RENDERER_SKIA_GL}));
+    CombineWithCompositorModes({TestRendererType::kGL,
+                                TestRendererType::kSkiaGL}));
 
 TEST_P(LayerTreeHostCopyRequestTestScaledLayer, Test) {
   RunTest(compositor_mode());
@@ -814,8 +815,8 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     LayerTreeHostTestAsyncTwoReadbacksWithoutDraw,
-    CombineWithCompositorModes({LayerTreeTest::RENDERER_GL,
-                                LayerTreeTest::RENDERER_SKIA_GL}));
+    CombineWithCompositorModes({TestRendererType::kGL,
+                                TestRendererType::kSkiaGL}));
 
 TEST_P(LayerTreeHostTestAsyncTwoReadbacksWithoutDraw, Test) {
   RunTest(compositor_mode());
@@ -957,8 +958,8 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     LayerTreeHostCopyRequestTestDeleteSharedImage,
-    CombineWithCompositorModes({LayerTreeTest::RENDERER_GL,
-                                LayerTreeTest::RENDERER_SKIA_GL}));
+    CombineWithCompositorModes({TestRendererType::kGL,
+                                TestRendererType::kSkiaGL}));
 
 TEST_P(LayerTreeHostCopyRequestTestDeleteSharedImage, Test) {
   RunTest(compositor_mode());
@@ -1102,8 +1103,8 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     LayerTreeHostCopyRequestTestCreatesSharedImage,
-    CombineWithCompositorModes({LayerTreeTest::RENDERER_GL,
-                                LayerTreeTest::RENDERER_SKIA_GL}));
+    CombineWithCompositorModes({TestRendererType::kGL,
+                                TestRendererType::kSkiaGL}));
 
 TEST_P(LayerTreeHostCopyRequestTestCreatesSharedImage, Test) {
   RunTest(compositor_mode());
@@ -1190,8 +1191,8 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     LayerTreeHostCopyRequestTestDestroyBeforeCopy,
-    CombineWithCompositorModes({LayerTreeTest::RENDERER_GL,
-                                LayerTreeTest::RENDERER_SKIA_GL}));
+    CombineWithCompositorModes({TestRendererType::kGL,
+                                TestRendererType::kSkiaGL}));
 
 TEST_P(LayerTreeHostCopyRequestTestDestroyBeforeCopy, Test) {
   RunTest(compositor_mode());
@@ -1273,8 +1274,8 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     LayerTreeHostCopyRequestTestShutdownBeforeCopy,
-    CombineWithCompositorModes({LayerTreeTest::RENDERER_GL,
-                                LayerTreeTest::RENDERER_SKIA_GL}));
+    CombineWithCompositorModes({TestRendererType::kGL,
+                                TestRendererType::kSkiaGL}));
 
 TEST_P(LayerTreeHostCopyRequestTestShutdownBeforeCopy, Test) {
   RunTest(compositor_mode());
@@ -1405,8 +1406,8 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     LayerTreeHostCopyRequestTestMultipleDrawsHiddenCopyRequest,
-    CombineWithCompositorModes({LayerTreeTest::RENDERER_GL,
-                                LayerTreeTest::RENDERER_SKIA_GL}));
+    CombineWithCompositorModes({TestRendererType::kGL,
+                                TestRendererType::kSkiaGL}));
 
 TEST_P(LayerTreeHostCopyRequestTestMultipleDrawsHiddenCopyRequest, Test) {
   RunTest(compositor_mode());
diff --git a/cc/trees/layer_tree_settings.h b/cc/trees/layer_tree_settings.h
index 4bb6d8a0..eaff59c 100644
--- a/cc/trees/layer_tree_settings.h
+++ b/cc/trees/layer_tree_settings.h
@@ -198,6 +198,11 @@
   // When enabled, enforces new interoperable semantics for 3D transforms.
   // See crbug.com/1008483.
   bool enable_transform_interop = false;
+
+  // When enabled, the compositor specifies a frame rate preference that would
+  // allow the display to run at a low refresh rate matching the playback rate
+  // for videos updating onscreen.
+  bool force_preferred_interval_for_video = false;
 };
 
 class CC_EXPORT LayerListSettings : public LayerTreeSettings {
diff --git a/chrome/BUILD.gn b/chrome/BUILD.gn
index 29b14b39..5eb707b 100644
--- a/chrome/BUILD.gn
+++ b/chrome/BUILD.gn
@@ -1223,6 +1223,7 @@
     "//components/services/heap_profiling",
     "//content/public/child",
     "//pdf",
+    "//pdf:pdf_ppapi",
     "//services/tracing/public/cpp",
     "//third_party/blink/public:blink_devtools_frontend_resources",
     "//third_party/blink/public:blink_devtools_inspector_resources",
diff --git a/chrome/VERSION b/chrome/VERSION
index 414569c..42cc8a2 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=86
 MINOR=0
-BUILD=4202
+BUILD=4203
 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index b3ac8ae6a..89edda3 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -419,6 +419,7 @@
     "//components/signin/core/browser/android:java",
     "//components/signin/public/android:java",
     "//components/spellcheck/browser/android:java",
+    "//components/strictmode/android:java",
     "//components/subresource_filter/android:java",
     "//components/sync/android:sync_java",
     "//components/sync/protocol:protocol_java",
@@ -973,6 +974,7 @@
     "//chrome/browser/preferences:java",
     "//chrome/browser/profiles/android:java",
     "//chrome/browser/safety_check/android:java",
+    "//chrome/browser/safety_check/android:javatests",
     "//chrome/browser/settings:java",
     "//chrome/browser/settings:javatests",
     "//chrome/browser/settings:test_support_java",
@@ -1068,6 +1070,7 @@
     "//components/signin/core/browser/android:signin_java_test_support",
     "//components/signin/core/browser/android:signin_javatests",
     "//components/signin/public/android:java",
+    "//components/strictmode/android:javatests",
     "//components/sync:sync_java_test_support",
     "//components/sync/android:sync_java",
     "//components/sync/protocol:protocol_java",
@@ -2858,7 +2861,7 @@
     "java/src/org/chromium/chrome/browser/ChromeBackupWatcher.java",
     "java/src/org/chromium/chrome/browser/ChromeVersionInfo.java",
     "java/src/org/chromium/chrome/browser/DevToolsServer.java",
-    "java/src/org/chromium/chrome/browser/IntentHeadersRecorder.java",
+    "java/src/org/chromium/chrome/browser/IntentHandler.java",
     "java/src/org/chromium/chrome/browser/IntentHelper.java",
     "java/src/org/chromium/chrome/browser/NearOomMonitor.java",
     "java/src/org/chromium/chrome/browser/SearchGeolocationDisclosureTabHelper.java",
diff --git a/chrome/android/DEPS b/chrome/android/DEPS
index 237ea5db..26891eb 100644
--- a/chrome/android/DEPS
+++ b/chrome/android/DEPS
@@ -40,6 +40,7 @@
   "+components/signin/core/browser/android",
   "+components/signin/public/android",
   "+components/spellcheck/browser",
+  "+components/strictmode/android",
   "+components/subresource_filter/android",
   "+components/translate/content/android",
   "+components/user_prefs/android",
diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni
index ece050da..cb37e5d 100644
--- a/chrome/android/chrome_java_resources.gni
+++ b/chrome/android/chrome_java_resources.gni
@@ -709,7 +709,6 @@
   "java/res/drawable/ic_full_screen_white_24dp.xml",
   "java/res/drawable/ic_globe_24dp.xml",
   "java/res/drawable/ic_google_round.xml",
-  "java/res/drawable/ic_info_outline_grey.xml",
   "java/res/drawable/ic_logo_googleg_20dp.xml",
   "java/res/drawable/ic_loop_round.xml",
   "java/res/drawable/ic_music_note_24dp.xml",
@@ -742,7 +741,6 @@
   "java/res/drawable/ic_volume_off_white_24dp.xml",
   "java/res/drawable/ic_volume_on_white_24dp.xml",
   "java/res/drawable/ic_vpn_key_blue.xml",
-  "java/res/drawable/ic_warning_red.xml",
   "java/res/drawable/ic_wb_sunny_round.xml",
   "java/res/drawable/incognito_switch_track.xml",
   "java/res/drawable/infobar_autofill_cc.xml",
@@ -947,7 +945,7 @@
   "java/res/layout/omnibox_query_tiles_suggestion.xml",
   "java/res/layout/omnibox_results_container.xml",
   "java/res/layout/optional_toolbar_button.xml",
-  "java/res/layout/os_version_unsupported_preference.xml",
+  "java/res/layout/os_version_unsupported_text.xml",
   "java/res/layout/other_forms_of_history_dialog.xml",
   "java/res/layout/passphrase_type_item.xml",
   "java/res/layout/password_entry_editor.xml",
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 5658c6c..379205e 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -39,7 +39,6 @@
   "java/src/org/chromium/chrome/browser/FileProviderHelper.java",
   "java/src/org/chromium/chrome/browser/GlobalDiscardableReferencePool.java",
   "java/src/org/chromium/chrome/browser/IntentHandler.java",
-  "java/src/org/chromium/chrome/browser/IntentHeadersRecorder.java",
   "java/src/org/chromium/chrome/browser/IntentHelper.java",
   "java/src/org/chromium/chrome/browser/KeyboardShortcuts.java",
   "java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java",
@@ -1338,7 +1337,7 @@
   "java/src/org/chromium/chrome/browser/rlz/RevenueStats.java",
   "java/src/org/chromium/chrome/browser/rlz/RlzPingHandler.java",
   "java/src/org/chromium/chrome/browser/safe_browsing/SafeBrowsingPasswordReuseDialogBridge.java",
-  "java/src/org/chromium/chrome/browser/safety_check/SafetyCheckOmahaClient.java",
+  "java/src/org/chromium/chrome/browser/safety_check/SafetyCheckUpdatesDelegateImpl.java",
   "java/src/org/chromium/chrome/browser/screenshot/EditorScreenshotSource.java",
   "java/src/org/chromium/chrome/browser/screenshot/EditorScreenshotTask.java",
   "java/src/org/chromium/chrome/browser/search_engines/SearchEngineChoiceMetrics.java",
diff --git a/chrome/android/chrome_junit_test_java_sources.gni b/chrome/android/chrome_junit_test_java_sources.gni
index 61f4fbbb..8b69f83 100644
--- a/chrome/android/chrome_junit_test_java_sources.gni
+++ b/chrome/android/chrome_junit_test_java_sources.gni
@@ -5,7 +5,6 @@
   "junit/src/org/chromium/chrome/browser/DeferredStartupHandlerTest.java",
   "junit/src/org/chromium/chrome/browser/DelayedScreenLockIntentHandlerTest.java",
   "junit/src/org/chromium/chrome/browser/DeviceConditionsTest.java",
-  "junit/src/org/chromium/chrome/browser/IntentHeadersRecorderTest.java",
   "junit/src/org/chromium/chrome/browser/ShadowDeviceConditions.java",
   "junit/src/org/chromium/chrome/browser/ShadowIdleHandlerAwareMessageQueue.java",
   "junit/src/org/chromium/chrome/browser/ShortcutHelperTest.java",
diff --git a/chrome/android/expectations/trichrome_library_apk.AndroidManifest.expected b/chrome/android/expectations/trichrome_library_apk.AndroidManifest.expected
index 943e38a..a4c555a8 100644
--- a/chrome/android/expectations/trichrome_library_apk.AndroidManifest.expected
+++ b/chrome/android/expectations/trichrome_library_apk.AndroidManifest.expected
@@ -7,9 +7,6 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools">
   <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
-  <uses-feature android:name="android.software.leanback" android:required="false"/>
-  <uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
-  <uses-feature android:glEsVersion="0x00020000"/>
   <application
       android:extractNativeLibs="false"
       android:icon="@drawable/icon_webview"
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java
index 9fc5ec3..a0f1940 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java
@@ -25,6 +25,7 @@
 import org.chromium.chrome.browser.ui.TabObscuringHandler;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager.SnackbarController;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetControllerProvider;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.base.WindowAndroid;
@@ -89,8 +90,10 @@
     private static AutofillAssistantUiController create(ChromeActivity activity,
             boolean allowTabSwitching, long nativeUiController,
             @Nullable AssistantOnboardingCoordinator onboardingCoordinator) {
+        BottomSheetController sheetController =
+                BottomSheetControllerProvider.from(activity.getWindowAndroid());
         assert activity != null;
-        assert activity.getBottomSheetController() != null;
+        assert sheetController != null;
 
         if (sActiveChromeActivities == null) {
             sActiveChromeActivities = new HashSet<>();
@@ -99,7 +102,7 @@
 
         // TODO(crbug.com/1048983): Have the params be passed in to the constructor directly rather
         //         than obtaining them from ChromeActivity getters.
-        return new AutofillAssistantUiController(activity, activity.getBottomSheetController(),
+        return new AutofillAssistantUiController(activity, sheetController,
                 activity.getTabObscuringHandler(), allowTabSwitching, nativeUiController,
                 onboardingCoordinator);
     }
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantChromeTabIntegrationTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantChromeTabIntegrationTest.java
index 3c558dd..db3335d0 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantChromeTabIntegrationTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantChromeTabIntegrationTest.java
@@ -43,6 +43,7 @@
 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.Restriction;
 import org.chromium.chrome.autofill_assistant.R;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
@@ -469,6 +470,7 @@
 
     @Test
     @MediumTest
+    @DisabledTest(message = "Flaky - https://crbug.com/1105146")
     public void interactingWithLocationBarHidesAutofillAssistant() {
         ArrayList<ActionProto> list = new ArrayList<>();
         list.add((ActionProto) ActionProto.newBuilder()
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantNavigationIntegrationTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantNavigationIntegrationTest.java
index 65bb77b..d64a962 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantNavigationIntegrationTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantNavigationIntegrationTest.java
@@ -31,6 +31,7 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.chrome.autofill_assistant.R;
 import org.chromium.chrome.browser.autofill_assistant.proto.ActionProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.ChipProto;
@@ -198,6 +199,7 @@
 
     @Test
     @MediumTest
+    @DisabledTest(message = "Flaky - https://crbug.com/1104900")
     public void navigateActionDoesNotCauseError() {
         // Push something to navigation stack so we can use back and forth.
         ChromeTabUtils.loadUrlOnUiThread(
diff --git a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java
index fe6c08c..fe88c1c 100644
--- a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java
+++ b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java
@@ -25,6 +25,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.widget.ScrimView;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetControllerProvider;
 
 /** Facade for starting Autofill Assistant on a custom tab. */
 public class AutofillAssistantFacade {
@@ -104,7 +105,8 @@
                             return;
                         }
 
-                        moduleEntry.start(activity.getBottomSheetController(),
+                        moduleEntry.start(
+                                BottomSheetControllerProvider.from(activity.getWindowAndroid()),
                                 activity.getBrowserControlsManager(),
                                 activity.getCompositorViewHolder(), activity.getScrim(), activity,
                                 tab.getWebContents(),
diff --git a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingCoordinator.java b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingCoordinator.java
index 1f4a476..96bb57b 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingCoordinator.java
+++ b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingCoordinator.java
@@ -16,6 +16,7 @@
 import org.chromium.chrome.browser.keyboard_accessory.sheet_component.AccessorySheetCoordinator;
 import org.chromium.components.autofill.AutofillDelegate;
 import org.chromium.components.autofill.AutofillSuggestion;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.ui.DropdownPopupWindow;
 import org.chromium.ui.base.WindowAndroid;
 
@@ -33,7 +34,8 @@
     public ManualFillingCoordinator() {}
 
     @Override
-    public void initialize(WindowAndroid windowAndroid, ViewStub barStub, ViewStub sheetStub) {
+    public void initialize(WindowAndroid windowAndroid, BottomSheetController sheetController,
+            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(R.layout.keyboard_accessory_modern);
@@ -42,13 +44,13 @@
         }
         sheetStub.setLayoutResource(R.layout.keyboard_accessory_sheet);
         initialize(windowAndroid, new KeyboardAccessoryCoordinator(mMediator, barStub),
-                new AccessorySheetCoordinator(sheetStub));
+                new AccessorySheetCoordinator(sheetStub), sheetController);
     }
 
     @VisibleForTesting
     void initialize(WindowAndroid windowAndroid, KeyboardAccessoryCoordinator accessoryBar,
-            AccessorySheetCoordinator accessorySheet) {
-        mMediator.initialize(accessoryBar, accessorySheet, windowAndroid);
+            AccessorySheetCoordinator accessorySheet, BottomSheetController sheetController) {
+        mMediator.initialize(accessoryBar, accessorySheet, windowAndroid, sheetController);
     }
 
     @Override
diff --git a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingMediator.java b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingMediator.java
index 1c49763..ce6a792 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingMediator.java
+++ b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingMediator.java
@@ -60,6 +60,7 @@
 import org.chromium.chrome.browser.vr.VrModuleProvider;
 import org.chromium.components.autofill.AutofillDelegate;
 import org.chromium.components.autofill.AutofillSuggestion;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.SheetState;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetObserver;
 import org.chromium.components.browser_ui.bottomsheet.EmptyBottomSheetObserver;
@@ -94,6 +95,7 @@
     private ChromeActivity mActivity; // Used to control the keyboard.
     private TabModelSelectorTabModelObserver mTabModelObserver;
     private DropdownPopupWindow mPopup;
+    private BottomSheetController mBottomSheetController;
 
     private final SceneChangeObserver mTabSwitcherObserver = new SceneChangeObserver() {
         @Override
@@ -144,12 +146,14 @@
     }
 
     void initialize(KeyboardAccessoryCoordinator keyboardAccessory,
-            AccessorySheetCoordinator accessorySheet, WindowAndroid windowAndroid) {
+            AccessorySheetCoordinator accessorySheet, WindowAndroid windowAndroid,
+            BottomSheetController sheetController) {
         mActivity = (ChromeActivity) windowAndroid.getActivity().get();
         assert mActivity != null;
         mWindowAndroid = windowAndroid;
         mWindowAndroid.getApplicationBottomInsetProvider().addSupplier(mViewportInsetSupplier);
         mKeyboardAccessory = keyboardAccessory;
+        mBottomSheetController = sheetController;
         mModel.set(PORTRAIT_ORIENTATION, hasPortraitOrientation());
         mModel.addObserver(this::onPropertyChanged);
         mAccessorySheet = accessorySheet;
@@ -177,7 +181,7 @@
             }
         };
         mActivity.getFullscreenManager().addObserver(mFullscreenObserver);
-        mActivity.getBottomSheetController().addObserver(mBottomSheetObserver);
+        mBottomSheetController.addObserver(mBottomSheetObserver);
         ensureObserverRegistered(getActiveBrowserTab());
         refreshTabs();
     }
@@ -261,7 +265,7 @@
         LayoutManager manager = getLayoutManager();
         if (manager != null) manager.removeSceneChangeObserver(mTabSwitcherObserver);
         mActivity.getFullscreenManager().removeObserver(mFullscreenObserver);
-        mActivity.getBottomSheetController().removeObserver(mBottomSheetObserver);
+        mBottomSheetController.removeObserver(mBottomSheetObserver);
         mWindowAndroid = null;
         mActivity = null;
     }
diff --git a/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingControllerTest.java b/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingControllerTest.java
index 2dd80af9..a7e4494 100644
--- a/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingControllerTest.java
+++ b/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingControllerTest.java
@@ -288,7 +288,6 @@
         ShadowRecordHistogram.reset();
         MockitoAnnotations.initMocks(this);
         KeyboardVisibilityDelegate.setInstance(mMockKeyboard);
-        when(mMockActivity.getBottomSheetController()).thenReturn(mMockBottomSheetController);
         when(mMockWindow.getKeyboardDelegate()).thenReturn(mMockKeyboard);
         when(mMockWindow.getActivity()).thenReturn(new WeakReference<>(mMockActivity));
         when(mMockWindow.getApplicationBottomInsetProvider())
@@ -320,7 +319,8 @@
         config.hardKeyboardHidden = HARDKEYBOARDHIDDEN_UNDEFINED;
         when(mMockResources.getConfiguration()).thenReturn(config);
         AccessorySheetTabCoordinator.IconProvider.setIconForTesting(mock(Drawable.class));
-        mController.initialize(mMockWindow, mMockKeyboardAccessory, mMockAccessorySheet);
+        mController.initialize(mMockWindow, mMockKeyboardAccessory, mMockAccessorySheet,
+                mMockBottomSheetController);
     }
 
     @Test
diff --git a/chrome/android/features/keyboard_accessory/public/BUILD.gn b/chrome/android/features/keyboard_accessory/public/BUILD.gn
index 842e677b..a88bcc3 100644
--- a/chrome/android/features/keyboard_accessory/public/BUILD.gn
+++ b/chrome/android/features/keyboard_accessory/public/BUILD.gn
@@ -8,6 +8,7 @@
   deps = [
     "//base:base_java",
     "//components/autofill/android:autofill_java",
+    "//components/browser_ui/android/bottomsheet:java",
     "//third_party/android_deps:androidx_annotation_annotation_java",
   ]
   sources = [
diff --git a/chrome/android/features/keyboard_accessory/public/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingComponent.java b/chrome/android/features/keyboard_accessory/public/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingComponent.java
index 06a7f08..b8a33044 100644
--- a/chrome/android/features/keyboard_accessory/public/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingComponent.java
+++ b/chrome/android/features/keyboard_accessory/public/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingComponent.java
@@ -11,6 +11,7 @@
 import org.chromium.chrome.browser.keyboard_accessory.data.PropertyProvider;
 import org.chromium.components.autofill.AutofillDelegate;
 import org.chromium.components.autofill.AutofillSuggestion;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.ui.DropdownPopupWindow;
 import org.chromium.ui.base.WindowAndroid;
 
@@ -23,11 +24,13 @@
      * is called.
      * @param windowAndroid The window needed to listen to the keyboard and to connect to
      *         activity.
+     * @param sheetController A {@link BottomSheetController} to show the UI in.
      * @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);
+    void initialize(WindowAndroid windowAndroid, BottomSheetController sheetController,
+            ViewStub barStub, ViewStub sheetStub);
 
     /**
      * Cleans up the manual UI by destroying the accessory bar and its bottom sheet.
diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceViewBinder.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceViewBinder.java
index 742448cd..97c97c2 100644
--- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceViewBinder.java
+++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceViewBinder.java
@@ -9,7 +9,7 @@
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_BOTTOM_BAR_VISIBLE;
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_EXPLORE_SURFACE_VISIBLE;
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_SHOWING_OVERVIEW;
-import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.TOP_BAR_HEIGHT;
+import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.TOP_MARGIN;
 
 import android.view.View;
 import android.view.ViewGroup;
@@ -28,8 +28,8 @@
         } else if (propertyKey == IS_SHOWING_OVERVIEW) {
             setVisibility(parentView, model,
                     model.get(IS_EXPLORE_SURFACE_VISIBLE) && model.get(IS_SHOWING_OVERVIEW));
-        } else if (propertyKey == TOP_BAR_HEIGHT) {
-            setTopBarHeight(model);
+        } else if (propertyKey == TOP_MARGIN) {
+            setTopMargin(model);
         }
     }
 
@@ -56,14 +56,14 @@
                 FrameLayout.LayoutParams layoutParams =
                         (FrameLayout.LayoutParams) feedSurfaceView.getLayoutParams();
                 layoutParams.bottomMargin = model.get(BOTTOM_BAR_HEIGHT);
-                layoutParams.topMargin = model.get(TOP_BAR_HEIGHT);
+                layoutParams.topMargin = model.get(TOP_MARGIN);
             }
         } else {
             UiUtils.removeViewFromParent(feedSurfaceView);
         }
     }
 
-    private static void setTopBarHeight(PropertyModel model) {
+    private static void setTopMargin(PropertyModel model) {
         if (model.get(FEED_SURFACE_COORDINATOR) == null) return;
         if (!model.get(IS_BOTTOM_BAR_VISIBLE)) return;
 
@@ -73,7 +73,7 @@
                 (FrameLayout.LayoutParams) feedSurfaceView.getLayoutParams();
         if (layoutParams == null) return;
 
-        layoutParams.topMargin = model.get(TOP_BAR_HEIGHT);
+        layoutParams.topMargin = model.get(TOP_MARGIN);
         feedSurfaceView.setLayoutParams(layoutParams);
     }
 }
diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/SecondaryTasksSurfaceViewBinder.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/SecondaryTasksSurfaceViewBinder.java
index 5c4c1c4..5005b08 100644
--- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/SecondaryTasksSurfaceViewBinder.java
+++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/SecondaryTasksSurfaceViewBinder.java
@@ -7,7 +7,7 @@
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_SECONDARY_SURFACE_VISIBLE;
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_SHOWING_OVERVIEW;
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_SHOWING_STACK_TAB_SWITCHER;
-import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.TOP_BAR_HEIGHT;
+import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.TOP_MARGIN;
 
 import android.view.View;
 import android.view.ViewGroup.MarginLayoutParams;
@@ -25,8 +25,8 @@
             updateVisibility(viewHolder, model);
         } else if (IS_SHOWING_STACK_TAB_SWITCHER == propertyKey) {
             updateVisibility(viewHolder, model);
-        } else if (TOP_BAR_HEIGHT == propertyKey) {
-            setTopBarHeight(viewHolder, model.get(TOP_BAR_HEIGHT));
+        } else if (TOP_MARGIN == propertyKey) {
+            setTopBarHeight(viewHolder, model.get(TOP_MARGIN));
         }
     }
 
@@ -39,7 +39,7 @@
             viewHolder.parentView.addView(viewHolder.tasksSurfaceView);
             MarginLayoutParams layoutParams =
                     (MarginLayoutParams) viewHolder.tasksSurfaceView.getLayoutParams();
-            layoutParams.topMargin = model.get(TOP_BAR_HEIGHT);
+            layoutParams.topMargin = model.get(TOP_MARGIN);
         }
 
         viewHolder.tasksSurfaceView.setVisibility(isShowing ? View.VISIBLE : View.GONE);
diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java
index 44a99ea0..d0d8ab9 100644
--- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java
+++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java
@@ -89,11 +89,12 @@
     private FeedLoadingCoordinator mFeedLoadingCoordinator;
 
     // TODO(http://crbug.com/1093421): Remove dependency on ChromeActivity.
-    public StartSurfaceCoordinator(ChromeActivity activity, ScrimCoordinator scrimCoordinator) {
+    public StartSurfaceCoordinator(ChromeActivity activity, ScrimCoordinator scrimCoordinator,
+            BottomSheetController sheetController) {
         mActivity = activity;
         mScrimCoordinator = scrimCoordinator;
         mSurfaceMode = computeSurfaceMode();
-        mBottomSheetController = mActivity.getBottomSheetController();
+        mBottomSheetController = sheetController;
 
         boolean excludeMVTiles = StartSurfaceConfiguration.START_SURFACE_EXCLUDE_MV_TILES.getValue()
                 || mSurfaceMode == SurfaceMode.OMNIBOX_ONLY
diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceDelegate.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceDelegate.java
index e985109..ea50de6 100644
--- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceDelegate.java
+++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceDelegate.java
@@ -6,24 +6,29 @@
 
 import android.content.Context;
 
+import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
 import org.chromium.chrome.browser.compositor.layouts.Layout;
 import org.chromium.chrome.browser.compositor.layouts.LayoutRenderHost;
 import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.widget.scrim.ScrimCoordinator;
 
 /** StartSurfaceDelegate. */
 public class StartSurfaceDelegate {
     public static Layout createStartSurfaceLayout(Context context, LayoutUpdateHost updateHost,
-            LayoutRenderHost renderHost, StartSurface startSurface) {
+            LayoutRenderHost renderHost, StartSurface startSurface,
+            ObservableSupplier<BrowserControlsStateProvider> browserControlsStateProviderSupplier) {
         if (StartSurfaceConfiguration.isStartSurfaceStackTabSwitcherEnabled()) {
-            return new StartSurfaceStackLayout(context, updateHost, renderHost, startSurface);
+            return new StartSurfaceStackLayout(context, updateHost, renderHost, startSurface,
+                    browserControlsStateProviderSupplier);
         }
         return new StartSurfaceLayout(context, updateHost, renderHost, startSurface);
     }
 
-    public static StartSurface createStartSurface(
-            ChromeActivity activity, ScrimCoordinator scrimCoordinator) {
-        return new StartSurfaceCoordinator(activity, scrimCoordinator);
+    public static StartSurface createStartSurface(ChromeActivity activity,
+            ScrimCoordinator scrimCoordinator, BottomSheetController sheetController) {
+        return new StartSurfaceCoordinator(activity, scrimCoordinator, sheetController);
     }
 }
\ No newline at end of file
diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
index f06bc62d..571bd82b 100644
--- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
+++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
@@ -26,7 +26,7 @@
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_SECONDARY_SURFACE_VISIBLE;
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_SHOWING_OVERVIEW;
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_SHOWING_STACK_TAB_SWITCHER;
-import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.TOP_BAR_HEIGHT;
+import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.TOP_MARGIN;
 
 import android.content.res.Resources;
 import android.view.View;
@@ -257,9 +257,10 @@
 
             mBrowserControlsObserver = new BrowserControlsStateProvider.Observer() {
                 @Override
-                public void onTopControlsHeightChanged(
-                        int topControlsHeight, int topControlsMinHeight) {
-                    mPropertyModel.set(TOP_BAR_HEIGHT, topControlsHeight);
+                public void onControlsOffsetChanged(int topOffset, int topControlsMinHeightOffset,
+                        int bottomOffset, int bottomControlsMinHeightOffset, boolean needsAnimate) {
+                    mPropertyModel.set(
+                            TOP_MARGIN, mBrowserControlsStateProvider.getContentOffset());
                 }
 
                 @Override
@@ -559,8 +560,7 @@
                 mBrowserControlsStateProvider.addObserver(mBrowserControlsObserver);
             }
 
-            mPropertyModel.set(
-                    TOP_BAR_HEIGHT, mBrowserControlsStateProvider.getTopControlsHeight());
+            mPropertyModel.set(TOP_MARGIN, mBrowserControlsStateProvider.getTopControlsHeight());
 
             mPropertyModel.set(IS_SHOWING_OVERVIEW, true);
             if (mFakeboxDelegate != null) {
diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceProperties.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceProperties.java
index 69e24e0..913e0e7 100644
--- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceProperties.java
+++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceProperties.java
@@ -41,10 +41,10 @@
     public static final PropertyModel
             .WritableObjectPropertyKey<FeedSurfaceCoordinator> FEED_SURFACE_COORDINATOR =
             new PropertyModel.WritableObjectPropertyKey<FeedSurfaceCoordinator>();
-    public static final PropertyModel.WritableIntPropertyKey TOP_BAR_HEIGHT =
+    public static final PropertyModel.WritableIntPropertyKey TOP_MARGIN =
             new PropertyModel.WritableIntPropertyKey();
     public static final PropertyKey[] ALL_KEYS = new PropertyKey[] {BOTTOM_BAR_CLICKLISTENER,
             BOTTOM_BAR_HEIGHT, BOTTOM_BAR_SELECTED_TAB_POSITION, IS_BOTTOM_BAR_VISIBLE,
             IS_EXPLORE_SURFACE_VISIBLE, IS_SECONDARY_SURFACE_VISIBLE, IS_SHOWING_OVERVIEW,
-            IS_SHOWING_STACK_TAB_SWITCHER, FEED_SURFACE_COORDINATOR, TOP_BAR_HEIGHT};
+            IS_SHOWING_STACK_TAB_SWITCHER, FEED_SURFACE_COORDINATOR, TOP_MARGIN};
 }
diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceStackLayout.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceStackLayout.java
index 6f7e4bb..dee979e 100644
--- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceStackLayout.java
+++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceStackLayout.java
@@ -6,6 +6,8 @@
 
 import android.content.Context;
 
+import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
 import org.chromium.chrome.browser.compositor.layouts.LayoutRenderHost;
 import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost;
 import org.chromium.chrome.browser.compositor.layouts.eventfilter.EventFilter;
@@ -18,8 +20,9 @@
     private boolean mIsInitialized;
 
     public StartSurfaceStackLayout(Context context, LayoutUpdateHost updateHost,
-            LayoutRenderHost renderHost, StartSurface startSurface) {
-        super(context, updateHost, renderHost);
+            LayoutRenderHost renderHost, StartSurface startSurface,
+            ObservableSupplier<BrowserControlsStateProvider> browserControlsStateProviderSupplier) {
+        super(context, updateHost, renderHost, browserControlsStateProviderSupplier);
 
         mCoordinator = (StartSurfaceCoordinator) startSurface;
         mCoordinator.setOnTabSelectingListener(this::onTabSelecting);
diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/TasksSurfaceViewBinder.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/TasksSurfaceViewBinder.java
index 3b72c034..2e31417 100644
--- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/TasksSurfaceViewBinder.java
+++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/TasksSurfaceViewBinder.java
@@ -7,7 +7,7 @@
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.BOTTOM_BAR_HEIGHT;
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_SHOWING_OVERVIEW;
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_SHOWING_STACK_TAB_SWITCHER;
-import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.TOP_BAR_HEIGHT;
+import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.TOP_MARGIN;
 
 import android.animation.ObjectAnimator;
 import android.view.View;
@@ -41,8 +41,8 @@
             updateLayoutAndVisibility(viewHolder, model);
         } else if (BOTTOM_BAR_HEIGHT == propertyKey) {
             setBottomBarHeight(viewHolder, model.get(BOTTOM_BAR_HEIGHT));
-        } else if (TOP_BAR_HEIGHT == propertyKey) {
-            setTopBarHeight(viewHolder, model.get(TOP_BAR_HEIGHT));
+        } else if (TOP_MARGIN == propertyKey) {
+            setTopBarHeight(viewHolder, model.get(TOP_MARGIN));
         }
     }
 
@@ -54,7 +54,7 @@
             MarginLayoutParams layoutParams =
                     (MarginLayoutParams) viewHolder.tasksSurfaceView.getLayoutParams();
             layoutParams.bottomMargin = model.get(BOTTOM_BAR_HEIGHT);
-            layoutParams.topMargin = model.get(TOP_BAR_HEIGHT);
+            layoutParams.topMargin = model.get(TOP_MARGIN);
         }
 
         View taskSurfaceView = viewHolder.tasksSurfaceView;
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/ExploreSurfaceViewBinderTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/ExploreSurfaceViewBinderTest.java
index de83a6c..cdab9e0d 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/ExploreSurfaceViewBinderTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/ExploreSurfaceViewBinderTest.java
@@ -14,7 +14,7 @@
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_BOTTOM_BAR_VISIBLE;
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_EXPLORE_SURFACE_VISIBLE;
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_SHOWING_OVERVIEW;
-import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.TOP_BAR_HEIGHT;
+import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.TOP_MARGIN;
 
 import android.view.View;
 import android.view.ViewGroup;
@@ -118,7 +118,7 @@
             mPropertyModel.set(FEED_SURFACE_COORDINATOR, mFeedSurfaceCoordinator);
             mPropertyModel.set(IS_BOTTOM_BAR_VISIBLE, true);
             mPropertyModel.set(BOTTOM_BAR_HEIGHT, 10);
-            mPropertyModel.set(TOP_BAR_HEIGHT, 20);
+            mPropertyModel.set(TOP_MARGIN, 20);
             mPropertyModel.set(IS_SHOWING_OVERVIEW, true);
             mPropertyModel.set(IS_EXPLORE_SURFACE_VISIBLE, true);
         });
@@ -191,7 +191,7 @@
 
     @Test
     @SmallTest
-    public void testSetTopBarHeightWithBottomBarVisible() {
+    public void testSetTopMarginWithBottomBarVisible() {
         assertFalse(mPropertyModel.get(IS_SHOWING_OVERVIEW));
         assertFalse(mPropertyModel.get(IS_EXPLORE_SURFACE_VISIBLE));
         assertNull(mFeedSurfaceView.getParent());
@@ -200,23 +200,23 @@
             mPropertyModel.set(FEED_SURFACE_COORDINATOR, mFeedSurfaceCoordinator);
             mPropertyModel.set(IS_BOTTOM_BAR_VISIBLE, true);
             mPropertyModel.set(BOTTOM_BAR_HEIGHT, 10);
-            mPropertyModel.set(TOP_BAR_HEIGHT, 20);
+            mPropertyModel.set(TOP_MARGIN, 20);
             mPropertyModel.set(IS_SHOWING_OVERVIEW, true);
             mPropertyModel.set(IS_EXPLORE_SURFACE_VISIBLE, true);
         });
 
         ViewGroup.MarginLayoutParams layoutParams =
                 (ViewGroup.MarginLayoutParams) mFeedSurfaceView.getLayoutParams();
-        assertEquals("Top bar height isn't initialized correctly.", 20, layoutParams.topMargin);
+        assertEquals("Top margin isn't initialized correctly.", 20, layoutParams.topMargin);
 
-        TestThreadUtils.runOnUiThreadBlocking(() -> mPropertyModel.set(TOP_BAR_HEIGHT, 40));
+        TestThreadUtils.runOnUiThreadBlocking(() -> mPropertyModel.set(TOP_MARGIN, 40));
         layoutParams = (ViewGroup.MarginLayoutParams) mFeedSurfaceView.getLayoutParams();
-        assertEquals("Wrong top bar height.", 40, layoutParams.topMargin);
+        assertEquals("Wrong top margin.", 40, layoutParams.topMargin);
     }
 
     @Test
     @SmallTest
-    public void testSetTopBarHeightWithBottomBarNotVisible() {
+    public void testSetTopMarginWithBottomBarNotVisible() {
         assertFalse(mPropertyModel.get(IS_SHOWING_OVERVIEW));
         assertFalse(mPropertyModel.get(IS_EXPLORE_SURFACE_VISIBLE));
         assertNull(mFeedSurfaceView.getParent());
@@ -224,19 +224,19 @@
 
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             mPropertyModel.set(FEED_SURFACE_COORDINATOR, mFeedSurfaceCoordinator);
-            mPropertyModel.set(TOP_BAR_HEIGHT, 20);
+            mPropertyModel.set(TOP_MARGIN, 20);
             mPropertyModel.set(IS_SHOWING_OVERVIEW, true);
             mPropertyModel.set(IS_EXPLORE_SURFACE_VISIBLE, true);
         });
 
         ViewGroup.MarginLayoutParams layoutParams =
                 (ViewGroup.MarginLayoutParams) mFeedSurfaceView.getLayoutParams();
-        assertEquals("Wrong top bar height.", 0, layoutParams.topMargin);
+        assertEquals("Wrong top margin.", 0, layoutParams.topMargin);
 
-        TestThreadUtils.runOnUiThreadBlocking(() -> mPropertyModel.set(TOP_BAR_HEIGHT, 40));
+        TestThreadUtils.runOnUiThreadBlocking(() -> mPropertyModel.set(TOP_MARGIN, 40));
 
-        // Top bar height shouldn't add a margin if the bottom bar is not visible.
+        // Top margin shouldn't add a margin if the bottom bar is not visible.
         layoutParams = (ViewGroup.MarginLayoutParams) mFeedSurfaceView.getLayoutParams();
-        assertEquals("Wrong top bar height.", 0, layoutParams.topMargin);
+        assertEquals("Wrong top margin.", 0, layoutParams.topMargin);
     }
 }
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
index 4ea64c0..85775d2e 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
@@ -52,7 +52,6 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.Feature;
-import org.chromium.base.test.util.FlakyTest;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.compositor.layouts.LayoutManagerChromePhone;
@@ -699,7 +698,6 @@
 
     @Test
     @SmallTest
-    @FlakyTest(message = "crbug.com/1097003")
     @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE})
     @EnableFeatures({ChromeFeatureList.TAB_SWITCHER_ON_RETURN + "<Study,",
             ChromeFeatureList.START_SURFACE_ANDROID + "<Study"})
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/SecondaryTasksSurfaceViewBinderTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/SecondaryTasksSurfaceViewBinderTest.java
index 8f8a60f..04a7cab 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/SecondaryTasksSurfaceViewBinderTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/SecondaryTasksSurfaceViewBinderTest.java
@@ -12,7 +12,7 @@
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_SECONDARY_SURFACE_VISIBLE;
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_SHOWING_OVERVIEW;
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_SHOWING_STACK_TAB_SWITCHER;
-import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.TOP_BAR_HEIGHT;
+import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.TOP_MARGIN;
 
 import android.support.test.annotation.UiThreadTest;
 import android.view.View;
@@ -145,12 +145,12 @@
     @Test
     @UiThreadTest
     @SmallTest
-    public void testSetVisibilityWithTopBar() {
+    public void testSetVisibilityWithTopMargin() {
         assertFalse(mPropertyModel.get(IS_SHOWING_OVERVIEW));
         assertFalse(mPropertyModel.get(IS_SECONDARY_SURFACE_VISIBLE));
         assertFalse(mPropertyModel.get(IS_SHOWING_STACK_TAB_SWITCHER));
         assertNull(mTasksSurfaceView.getParent());
-        mPropertyModel.set(TOP_BAR_HEIGHT, 20);
+        mPropertyModel.set(TOP_MARGIN, 20);
 
         mPropertyModel.set(IS_SHOWING_OVERVIEW, true);
         assertNull(mTasksSurfaceView.getParent());
@@ -174,23 +174,23 @@
     @Test
     @UiThreadTest
     @SmallTest
-    public void testSetTopBarHeight() {
+    public void testSetTopMargin() {
         assertFalse(mPropertyModel.get(IS_SHOWING_OVERVIEW));
         assertFalse(mPropertyModel.get(IS_SECONDARY_SURFACE_VISIBLE));
         assertFalse(mPropertyModel.get(IS_SHOWING_STACK_TAB_SWITCHER));
         assertNull(mTasksSurfaceView.getParent());
 
-        // Setting the top bar height shouldn't cause a NullPointerException when the layout params
-        // are null, since this should be handled in the *ViewBinder.
-        mPropertyModel.set(TOP_BAR_HEIGHT, 20);
+        // Setting the top margin shouldn't cause a NullPointerException when the layout params are
+        // null, since this should be handled in the *ViewBinder.
+        mPropertyModel.set(TOP_MARGIN, 20);
         mPropertyModel.set(IS_SHOWING_OVERVIEW, true);
         mPropertyModel.set(IS_SECONDARY_SURFACE_VISIBLE, true);
 
         MarginLayoutParams layoutParams = (MarginLayoutParams) mTasksSurfaceView.getLayoutParams();
-        assertEquals("Top bar height isn't initialized correctly.", 20, layoutParams.topMargin);
+        assertEquals("Top margin isn't initialized correctly.", 20, layoutParams.topMargin);
 
         layoutParams = (MarginLayoutParams) mTasksSurfaceView.getLayoutParams();
-        mPropertyModel.set(TOP_BAR_HEIGHT, 40);
-        assertEquals("Wrong top bar height.", 40, layoutParams.topMargin);
+        mPropertyModel.set(TOP_MARGIN, 40);
+        assertEquals("Wrong top margin.", 40, layoutParams.topMargin);
     }
 }
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/TasksSurfaceViewBinderTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/TasksSurfaceViewBinderTest.java
index 3e22869..593bb8d 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/TasksSurfaceViewBinderTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/TasksSurfaceViewBinderTest.java
@@ -12,7 +12,7 @@
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.BOTTOM_BAR_HEIGHT;
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_SHOWING_OVERVIEW;
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_SHOWING_STACK_TAB_SWITCHER;
-import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.TOP_BAR_HEIGHT;
+import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.TOP_MARGIN;
 
 import android.support.test.annotation.UiThreadTest;
 import android.view.View;
@@ -67,7 +67,7 @@
         assertNull(mTasksSurfaceView.getParent());
 
         mPropertyModel.set(BOTTOM_BAR_HEIGHT, 10);
-        mPropertyModel.set(TOP_BAR_HEIGHT, 20);
+        mPropertyModel.set(TOP_MARGIN, 20);
 
         mPropertyModel.set(IS_SHOWING_OVERVIEW, true);
         assertEquals(mTasksSurfaceView.getVisibility(), View.VISIBLE);
@@ -90,7 +90,7 @@
         assertNull(mTasksSurfaceView.getParent());
 
         mPropertyModel.set(BOTTOM_BAR_HEIGHT, 10);
-        mPropertyModel.set(TOP_BAR_HEIGHT, 20);
+        mPropertyModel.set(TOP_MARGIN, 20);
 
         mPropertyModel.set(IS_SHOWING_OVERVIEW, true);
         assertEquals(mTasksSurfaceView.getVisibility(), View.VISIBLE);
@@ -130,12 +130,12 @@
     @UiThreadTest
     @SmallTest
     public void testSetTopBarHeight() {
-        mPropertyModel.set(TOP_BAR_HEIGHT, 10);
+        mPropertyModel.set(TOP_MARGIN, 10);
         mPropertyModel.set(IS_SHOWING_OVERVIEW, true);
         MarginLayoutParams layoutParams = (MarginLayoutParams) mTasksSurfaceView.getLayoutParams();
         assertEquals(10, layoutParams.topMargin);
 
-        mPropertyModel.set(TOP_BAR_HEIGHT, 20);
+        mPropertyModel.set(TOP_MARGIN, 20);
         layoutParams = (MarginLayoutParams) mTasksSurfaceView.getLayoutParams();
         assertEquals(20, layoutParams.topMargin);
     }
diff --git a/chrome/android/features/start_surface/internal/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java b/chrome/android/features/start_surface/internal/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java
index 521cf30..7348a4c1 100644
--- a/chrome/android/features/start_surface/internal/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java
+++ b/chrome/android/features/start_surface/internal/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java
@@ -38,7 +38,7 @@
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_SECONDARY_SURFACE_VISIBLE;
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_SHOWING_OVERVIEW;
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_SHOWING_STACK_TAB_SWITCHER;
-import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.TOP_BAR_HEIGHT;
+import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.TOP_MARGIN;
 
 import android.content.res.Resources;
 import android.view.View;
@@ -1491,7 +1491,7 @@
     }
 
     @Test
-    public void changeTopControlsHeight() {
+    public void changeTopContentOffset() {
         doReturn(false).when(mTabModelSelector).isIncognitoSelected();
         doReturn(mVoiceRecognitionHandler).when(mFakeBoxDelegate).getVoiceRecognitionHandler();
         doReturn(true).when(mVoiceRecognitionHandler).isVoiceSearchEnabled();
@@ -1505,10 +1505,14 @@
         verify(mBrowserControlsStateProvider).addObserver(ArgumentMatchers.any());
 
         mBrowserControlsStateProviderCaptor.getValue().onTopControlsHeightChanged(100, 20);
-        assertEquals("Wrong top bar height.", 100, mPropertyModel.get(TOP_BAR_HEIGHT));
+        doReturn(100).when(mBrowserControlsStateProvider).getContentOffset();
+        mBrowserControlsStateProviderCaptor.getValue().onControlsOffsetChanged(
+                100, 20, 0, 0, false);
+        assertEquals("Wrong top content offset.", 100, mPropertyModel.get(TOP_MARGIN));
 
-        mBrowserControlsStateProviderCaptor.getValue().onTopControlsHeightChanged(50, 20);
-        assertEquals("Wrong top bar height.", 50, mPropertyModel.get(TOP_BAR_HEIGHT));
+        doReturn(50).when(mBrowserControlsStateProvider).getContentOffset();
+        mBrowserControlsStateProviderCaptor.getValue().onControlsOffsetChanged(50, 20, 0, 0, false);
+        assertEquals("Wrong top content offset.", 50, mPropertyModel.get(TOP_MARGIN));
     }
 
     @Test
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupPopupUiCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupPopupUiCoordinator.java
index df292019..b0c094d 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupPopupUiCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupPopupUiCoordinator.java
@@ -11,6 +11,7 @@
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ThemeColorProvider;
 import org.chromium.chrome.browser.lifecycle.Destroyable;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetControllerProvider;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
 
@@ -51,7 +52,8 @@
                 model, mTabGroupPopupUiParent, TabGroupPopupUiViewBinder::bind);
         mMediator = new TabGroupPopupUiMediator(model, activity.getTabModelSelector(),
                 activity.getOverviewModeBehavior(), activity.getBrowserControlsManager(), this,
-                mTabGroupUiCoordinator, activity.getBottomSheetController());
+                mTabGroupUiCoordinator,
+                BottomSheetControllerProvider.from(activity.getWindowAndroid()));
         mMediator.onAnchorViewChanged(mAnchorView, mAnchorView.getId());
     }
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegate.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegate.java
index f12b4d0..c55617f 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegate.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegate.java
@@ -13,6 +13,7 @@
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ThemeColorProvider;
+import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
 import org.chromium.chrome.browser.compositor.layouts.Layout;
 import org.chromium.chrome.browser.compositor.layouts.LayoutRenderHost;
 import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost;
@@ -22,6 +23,7 @@
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
 import org.chromium.chrome.browser.tasks.tab_management.suggestions.TabSuggestions;
 import org.chromium.chrome.features.start_surface.StartSurface;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.widget.scrim.ScrimCoordinator;
 import org.chromium.components.module_installer.builder.ModuleInterface;
 import org.chromium.ui.modelutil.PropertyModel;
@@ -96,18 +98,23 @@
      * @param updateHost The parent {@link LayoutUpdateHost}.
      * @param renderHost The parent {@link LayoutRenderHost}.
      * @param startSurface The {@link StartSurface} the layout should own.
+     * @param browserControlsStateProviderSupplier The {@link ObservableSupplier} for
+     *                                             {@link BrowserControlsStateProvider}.
      * @return The {@link StartSurfaceLayout}.
      */
     Layout createStartSurfaceLayout(Context context, LayoutUpdateHost updateHost,
-            LayoutRenderHost renderHost, StartSurface startSurface);
+            LayoutRenderHost renderHost, StartSurface startSurface,
+            ObservableSupplier<BrowserControlsStateProvider> browserControlsStateProviderSupplier);
 
     /**
      * Create the {@link StartSurface}
      * @param activity The {@link ChromeActivity} creates this {@link StartSurface}.
      * @param scrimCoordinator The {@link ScrimCoordinator} to control the scrim view.
+     * @param sheetController A {@link BottomSheetController} to show content in the bottom sheet.
      * @return the {@link StartSurface}
      */
-    StartSurface createStartSurface(ChromeActivity activity, ScrimCoordinator scrimCoordinator);
+    StartSurface createStartSurface(ChromeActivity activity, ScrimCoordinator scrimCoordinator,
+            BottomSheetController sheetController);
 
     /**
      * Create a {@link TabGroupModelFilter} for the given {@link TabModel}.
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegateImpl.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegateImpl.java
index e5dd134..03b0506 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegateImpl.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegateImpl.java
@@ -15,6 +15,7 @@
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ThemeColorProvider;
+import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
 import org.chromium.chrome.browser.compositor.layouts.Layout;
 import org.chromium.chrome.browser.compositor.layouts.LayoutRenderHost;
 import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost;
@@ -28,6 +29,7 @@
 import org.chromium.chrome.browser.tasks.tab_management.suggestions.TabSuggestionsOrchestrator;
 import org.chromium.chrome.features.start_surface.StartSurface;
 import org.chromium.chrome.features.start_surface.StartSurfaceDelegate;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.widget.scrim.ScrimCoordinator;
 import org.chromium.ui.modelutil.PropertyModel;
 
@@ -84,15 +86,16 @@
 
     @Override
     public Layout createStartSurfaceLayout(Context context, LayoutUpdateHost updateHost,
-            LayoutRenderHost renderHost, StartSurface startSurface) {
-        return StartSurfaceDelegate.createStartSurfaceLayout(
-                context, updateHost, renderHost, startSurface);
+            LayoutRenderHost renderHost, StartSurface startSurface,
+            ObservableSupplier<BrowserControlsStateProvider> browserControlsStateProviderSupplier) {
+        return StartSurfaceDelegate.createStartSurfaceLayout(context, updateHost, renderHost,
+                startSurface, browserControlsStateProviderSupplier);
     }
 
     @Override
-    public StartSurface createStartSurface(
-            ChromeActivity activity, ScrimCoordinator scrimCoordinator) {
-        return StartSurfaceDelegate.createStartSurface(activity, scrimCoordinator);
+    public StartSurface createStartSurface(ChromeActivity activity,
+            ScrimCoordinator scrimCoordinator, BottomSheetController sheetController) {
+        return StartSurfaceDelegate.createStartSurface(activity, scrimCoordinator, sheetController);
     }
 
     @Override
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStream.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStream.java
index e6af131..f13e6750 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStream.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStream.java
@@ -44,7 +44,6 @@
     private final FeedStreamSurface mFeedStreamSurface;
     private final ObserverList<ScrollListener> mScrollListeners =
             new ObserverList<ScrollListener>();
-    private final int mLoadMoreTriggerLookahead;
 
     private RecyclerView mRecyclerView;
     // setStreamContentVisibility() is always called once after onCreate(). So we can assume the
@@ -53,7 +52,6 @@
     private boolean mIsStreamContentVisible = false;
     // For loading more content.
     private int mAccumulatedDySinceLastLoadMore;
-    private boolean mIsLoadingMoreContent;
 
     public FeedStream(Activity activity, boolean isBackgroundDark, SnackbarManager snackbarManager,
             NativePageNavigationDelegate nativePageNavigationDelegate,
@@ -62,7 +60,6 @@
         this.mActivity = activity;
         this.mFeedStreamSurface = new FeedStreamSurface(activity, isBackgroundDark, snackbarManager,
                 nativePageNavigationDelegate, bottomSheetController, HelpAndFeedback.getInstance());
-        this.mLoadMoreTriggerLookahead = FeedServiceBridge.getLoadMoreTriggerLookahead();
     }
 
     @Override
@@ -238,26 +235,12 @@
             return;
         }
 
-        LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
-        if (layoutManager == null) {
-            return;
-        }
-        int totalItemCount = layoutManager.getItemCount();
-        int lastVisibleItem = layoutManager.findLastVisibleItemPosition();
-        if (totalItemCount - lastVisibleItem <= mLoadMoreTriggerLookahead) {
+        boolean canTrigger = mFeedStreamSurface.maybeLoadMore();
+        if (canTrigger) {
             mAccumulatedDySinceLastLoadMore = 0;
-            loadMore();
         }
     }
 
-    private void loadMore() {
-        if (mIsLoadingMoreContent) {
-            return;
-        }
-        mIsLoadingMoreContent = true;
-        mFeedStreamSurface.loadMoreContent((Boolean success) -> { mIsLoadingMoreContent = false; });
-    }
-
     private void restoreScrollState(String savedInstanceState) {
         try {
             JSONObject jsonSavedState = new JSONObject(savedInstanceState);
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStreamSurface.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStreamSurface.java
index 79372481..ee7b53a 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStreamSurface.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStreamSurface.java
@@ -11,6 +11,7 @@
 
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
+import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
 import org.chromium.base.Callback;
@@ -73,15 +74,18 @@
     private static final int SNACKBAR_DURATION_MS_SHORT = 4000;
     private static final int SNACKBAR_DURATION_MS_LONG = 10000;
 
-    private static final String FEEDBACK_REPORT_TYPE =
+    @VisibleForTesting
+    static final String FEEDBACK_REPORT_TYPE =
             "com.google.chrome.feed.USER_INITIATED_FEEDBACK_REPORT";
-    public static final String FEEDBACK_CONTEXT = "mobile_browser";
-    public static final String XSURFACE_CARD_URL = "Card URL";
+    @VisibleForTesting
+    static final String FEEDBACK_CONTEXT = "mobile_browser";
+    @VisibleForTesting
+    static final String XSURFACE_CARD_URL = "Card URL";
 
     private final long mNativeFeedStreamSurface;
     private final FeedListContentManager mContentManager;
     private final SurfaceScope mSurfaceScope;
-    private final View mRootView;
+    private final RecyclerView mRootView;
     private final HybridListRenderer mHybridListRenderer;
     private final SnackbarManager mSnackbarManager;
     private final Activity mActivity;
@@ -93,6 +97,8 @@
 
     private int mHeaderCount;
     private BottomSheetContent mBottomSheetContent;
+    private final int mLoadMoreTriggerLookahead;
+    private boolean mIsLoadingMoreContent;
 
     private static ProcessScope sXSurfaceProcessScope;
 
@@ -245,6 +251,7 @@
 
         mPageNavigationDelegate = pageNavigationDelegate;
         mBottomSheetController = bottomSheetController;
+        mLoadMoreTriggerLookahead = FeedServiceBridge.getLoadMoreTriggerLookahead();
 
         mContentManager = new FeedListContentManager(this, this);
 
@@ -267,12 +274,11 @@
         }
 
         if (mHybridListRenderer != null) {
-            mRootView = mHybridListRenderer.bind(mContentManager);
             // XSurface returns a View, but it should be a RecyclerView.
-            assert (mRootView instanceof RecyclerView);
+            mRootView = (RecyclerView) mHybridListRenderer.bind(mContentManager);
 
-            mSliceViewTracker = new FeedSliceViewTracker(
-                    (RecyclerView) mRootView, mContentManager, (String sliceId) -> {
+            mSliceViewTracker =
+                    new FeedSliceViewTracker(mRootView, mContentManager, (String sliceId) -> {
                         FeedStreamSurfaceJni.get().reportSliceViewed(
                                 mNativeFeedStreamSurface, FeedStreamSurface.this, sliceId);
                     });
@@ -327,11 +333,30 @@
     }
 
     /**
-     * Loads more content. The callback will be called upon completion.
+     * Attempts to load more content if it can be triggered.
+     * @return true if loading more content can be triggered.
      */
-    public void loadMoreContent(Callback<Boolean> callback) {
-        FeedStreamSurfaceJni.get().loadMore(
-                mNativeFeedStreamSurface, FeedStreamSurface.this, callback);
+    public boolean maybeLoadMore() {
+        // Checks if loading more can be triggered.
+        boolean canLoadMore = false;
+        LinearLayoutManager layoutManager = (LinearLayoutManager) mRootView.getLayoutManager();
+        if (layoutManager == null) {
+            return false;
+        }
+        int totalItemCount = layoutManager.getItemCount();
+        int lastVisibleItem = layoutManager.findLastVisibleItemPosition();
+        if (totalItemCount - lastVisibleItem > mLoadMoreTriggerLookahead) {
+            return false;
+        }
+
+        // Starts to load more content if not yet.
+        if (!mIsLoadingMoreContent) {
+            mIsLoadingMoreContent = true;
+            FeedStreamSurfaceJni.get().loadMore(mNativeFeedStreamSurface, FeedStreamSurface.this,
+                    (Boolean success) -> { mIsLoadingMoreContent = false; });
+        }
+
+        return true;
     }
 
     @VisibleForTesting
@@ -380,6 +405,16 @@
         updateContentsInPlace(newContentList);
     }
 
+    @CalledByNative
+    void replaceDataStoreEntry(String key, byte[] data) {
+        if (mSurfaceScope != null) mSurfaceScope.replaceDataStoreEntry(key, data);
+    }
+
+    @CalledByNative
+    void removeDataStoreEntry(String key) {
+        if (mSurfaceScope != null) mSurfaceScope.removeDataStoreEntry(key);
+    }
+
     private void updateContentsInPlace(
             ArrayList<FeedListContentManager.FeedContent> newContentList) {
         // 1) Builds the hash set based on keys of new contents.
@@ -448,16 +483,25 @@
     @Override
     public void navigateTab(String url) {
         openUrl(url, WindowOpenDisposition.CURRENT_TAB);
+
+        // Attempts to load more content if needed.
+        maybeLoadMore();
     }
 
     @Override
     public void navigateNewTab(String url) {
         openUrl(url, WindowOpenDisposition.NEW_FOREGROUND_TAB);
+
+        // Attempts to load more content if needed.
+        maybeLoadMore();
     }
 
     @Override
     public void navigateIncognitoTab(String url) {
         openUrl(url, WindowOpenDisposition.OFF_THE_RECORD);
+
+        // Attempts to load more content if needed.
+        maybeLoadMore();
     }
 
     @Override
@@ -559,6 +603,9 @@
     public void commitDismissal(int changeId) {
         FeedStreamSurfaceJni.get().commitEphemeralChange(
                 mNativeFeedStreamSurface, FeedStreamSurface.this, changeId);
+
+        // Attempts to load more content if needed.
+        maybeLoadMore();
     }
 
     @Override
diff --git a/chrome/android/feed/feed_java_sources.gni b/chrome/android/feed/feed_java_sources.gni
index f9e45fe..fd3ca5f 100644
--- a/chrome/android/feed/feed_java_sources.gni
+++ b/chrome/android/feed/feed_java_sources.gni
@@ -648,6 +648,7 @@
     "junit/src/org/chromium/chrome/browser/feed/FutureTaskConsumerTest.java",
     "junit/src/org/chromium/chrome/browser/feed/NtpStreamLifecycleManagerTest.java",
     "junit/src/org/chromium/chrome/browser/feed/action/FeedActionHandlerTest.java",
+    "junit/src/org/chromium/chrome/browser/feed/v2/FakeLinearLayoutManager.java",
     "junit/src/org/chromium/chrome/browser/feed/v2/FeedListContentManagerTest.java",
     "junit/src/org/chromium/chrome/browser/feed/v2/FeedSliceViewTrackerTest.java",
     "junit/src/org/chromium/chrome/browser/feed/v2/FeedStreamSurfaceTest.java",
diff --git a/chrome/android/java/AndroidManifest_trichrome_library.xml b/chrome/android/java/AndroidManifest_trichrome_library.xml
index 37ff48e..b85427f 100644
--- a/chrome/android/java/AndroidManifest_trichrome_library.xml
+++ b/chrome/android/java/AndroidManifest_trichrome_library.xml
@@ -12,10 +12,6 @@
     package="{{ manifest_package }}"
     tools:ignore="MissingVersion,MissingLeanbackLauncher">
 
-    <uses-feature android:glEsVersion="0x00020000" />
-    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
-    <uses-feature android:name="android.software.leanback" android:required="false" />
-
     <!-- TODO(torne): we should specify an icon, roundIcon, and label from resources. -->
     <application
         android:label="{{ application_label|default('Trichrome Library') }}"
diff --git a/chrome/android/java/res/layout/autofill_local_card_editor.xml b/chrome/android/java/res/layout/autofill_local_card_editor.xml
index 66564af..eb85e44 100644
--- a/chrome/android/java/res/layout/autofill_local_card_editor.xml
+++ b/chrome/android/java/res/layout/autofill_local_card_editor.xml
@@ -117,7 +117,7 @@
         app:errorEnabled="true"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/pref_autofill_field_large_top_margin"
+        android:layout_marginTop="@dimen/pref_autofill_field_extra_large_top_margin"
         android:layout_marginBottom="@dimen/pref_autofill_field_bottom_margin">
 
         <EditText
@@ -127,7 +127,7 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:imeOptions="flagNoExtractUi"
-            android:inputType="text"
+            android:inputType="textCapWords"
             android:hint="@string/autofill_credit_card_editor_nickname" />
 
     </com.google.android.material.textfield.TextInputLayout>
diff --git a/chrome/android/java/res/layout/os_version_unsupported_preference.xml b/chrome/android/java/res/layout/os_version_unsupported_preference.xml
deleted file mode 100644
index f216fc6..0000000
--- a/chrome/android/java/res/layout/os_version_unsupported_preference.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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. -->
-
-<!-- Layout for a preference with a title and a compound drawable below. -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    style="@style/PreferenceLayout"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:background="?android:attr/selectableItemBackground"
-    android:orientation="vertical">
-
-    <TextView
-        android:id="@android:id/title"
-        style="@style/PreferenceTitle"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="@string/os_version_title" />
-
-    <TextView
-        android:id="@android:id/summary"
-        style="@style/PreferenceSummary"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content" />
-
-    <TextView
-        android:id="@+id/os_deprecation_warning"
-        style="@style/PreferenceSummary"
-        android:layout_marginTop="7dp"
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:layout_weight="1"
-        android:layout_gravity="center_vertical"
-        android:text="@string/deprecation_warning"
-        android:drawablePadding="30dp"
-        android:drawableEnd="@drawable/exclamation_triangle"
-        android:layout_marginEnd="17dp"
-        android:visibility="gone" />
-</LinearLayout>
diff --git a/chrome/android/java/res/layout/os_version_unsupported_text.xml b/chrome/android/java/res/layout/os_version_unsupported_text.xml
new file mode 100644
index 0000000..7b3d4add
--- /dev/null
+++ b/chrome/android/java/res/layout/os_version_unsupported_text.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2020 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. -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/PreferenceSummary"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_below="@android:id/summary"
+    android:layout_alignStart="@android:id/summary"
+    android:layout_gravity="start"
+    android:layout_marginTop="7dp"
+    android:layout_marginEnd="17dp"
+    android:drawableEnd="@drawable/exclamation_triangle"
+    android:drawablePadding="30dp"
+    android:text="@string/deprecation_warning" />
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml
index 34263aa..fdf2aa0 100644
--- a/chrome/android/java/res/values/dimens.xml
+++ b/chrome/android/java/res/values/dimens.xml
@@ -501,6 +501,7 @@
     <dimen name="pref_autofill_field_horizontal_padding">4dp</dimen>
     <dimen name="pref_autofill_field_top_margin">8dp</dimen>
     <dimen name="pref_autofill_field_large_top_margin">16dp</dimen>
+    <dimen name="pref_autofill_field_extra_large_top_margin">24dp</dimen>
     <dimen name="pref_autofill_field_bottom_margin">8dp</dimen>
     <!-- The dropdown element has no embedded bottom padding like the TextView has so the margin
          is doubled. -->
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 f0635d0..600cff31 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
@@ -149,6 +149,7 @@
 import org.chromium.chrome.browser.ui.appmenu.AppMenuPropertiesDelegate;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager.SnackbarManageable;
+import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManagerProvider;
 import org.chromium.chrome.browser.ui.system.StatusBarColorController;
 import org.chromium.chrome.browser.vr.ArDelegate;
 import org.chromium.chrome.browser.vr.ArDelegateProvider;
@@ -413,8 +414,10 @@
                 VrModuleProvider.getDelegate().maybeHandleVrIntentPreNative(this, intent);
             }
 
+            // TODO(1099750): Move this to the RootUiCoordinator.
             mSnackbarManager = new SnackbarManager(
                     this, findViewById(R.id.bottom_container), getWindowAndroid());
+            SnackbarManagerProvider.attach(getWindowAndroid(), mSnackbarManager);
 
             mAssistStatusHandler = createAssistStatusHandler();
             if (mAssistStatusHandler != null) {
@@ -1222,6 +1225,10 @@
             mContextualSearchManager = null;
         }
 
+        if (mSnackbarManager != null) {
+            SnackbarManagerProvider.detach(mSnackbarManager);
+        }
+
         if (mTabModelSelectorTabObserver != null) {
             mTabModelSelectorTabObserver.destroy();
             mTabModelSelectorTabObserver = null;
@@ -1293,9 +1300,10 @@
      */
     @Override
     public SnackbarManager getSnackbarManager() {
-        if (mRootUiCoordinator != null && getBottomSheetController() != null
-                && getBottomSheetController().isSheetOpen()
-                && !getBottomSheetController().isSheetHiding()) {
+        BottomSheetController controller =
+                mRootUiCoordinator == null ? null : mRootUiCoordinator.getBottomSheetController();
+        if (mRootUiCoordinator != null && controller != null && controller.isSheetOpen()
+                && !controller.isSheetHiding()) {
             return mRootUiCoordinator.getBottomSheetSnackbarManager();
         }
         return mSnackbarManager;
@@ -1361,6 +1369,7 @@
         super.finishNativeInitialization();
 
         mManualFillingComponent.initialize(getWindowAndroid(),
+                mRootUiCoordinator.getBottomSheetController(),
                 findViewById(R.id.keyboard_accessory_stub),
                 findViewById(R.id.keyboard_accessory_sheet_stub));
 
@@ -1524,7 +1533,8 @@
 
     public TabDelegateFactory getTabDelegateFactory() {
         return new TabbedModeTabDelegateFactory(this,
-                new ComposedBrowserControlsVisibilityDelegate(), getShareDelegateSupplier(), null);
+                new ComposedBrowserControlsVisibilityDelegate(), getShareDelegateSupplier(), null,
+                mRootUiCoordinator.getBottomSheetController());
     }
 
     /**
@@ -2319,14 +2329,6 @@
         return true;
     }
 
-    /**
-     * @return A lazily created {@link BottomSheetController}. The first time this method is called,
-     *         a new controller is created.
-     */
-    public BottomSheetController getBottomSheetController() {
-        return mRootUiCoordinator.getBottomSheetController();
-    }
-
     @VisibleForTesting
     public RootUiCoordinator getRootUiCoordinatorForTesting() {
         return mRootUiCoordinator;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeStrictMode.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeStrictMode.java
index 2ecea3d..30780a2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeStrictMode.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeStrictMode.java
@@ -4,10 +4,10 @@
 
 package org.chromium.chrome.browser;
 
-import android.app.ApplicationErrorReport;
 import android.os.Build;
 import android.os.Looper;
 import android.os.StrictMode;
+import android.text.TextUtils;
 
 import androidx.annotation.UiThread;
 
@@ -18,8 +18,13 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
+import org.chromium.components.strictmode.KnownViolations;
+import org.chromium.components.strictmode.StrictModePolicyViolation;
+import org.chromium.components.strictmode.ThreadStrictModeInterceptor;
+import org.chromium.components.strictmode.Violation;
 
-import java.lang.reflect.Field;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -34,29 +39,10 @@
     private static final double MAX_UPLOADS_PER_SESSION = 3;
 
     private static boolean sIsStrictModeAlreadyConfigured;
-    private static List<Object> sCachedStackTraces =
-            Collections.synchronizedList(new ArrayList<Object>());
+    private static List<Violation> sCachedViolations =
+            Collections.synchronizedList(new ArrayList<>());
     private static AtomicInteger sNumUploads = new AtomicInteger();
 
-    private static class SnoopingArrayList<T> extends ArrayList<T> {
-        @Override
-        public void clear() {
-            for (int i = 0; i < size(); i++) {
-                // It is likely that we have at most one violation pass this check each time around.
-                if (Math.random() < UPLOAD_PROBABILITY) {
-                    // Ensure that we do not upload too many StrictMode violations in any single
-                    // session. To prevent races, we allow sNumUploads to increase beyond the
-                    // limit, but just skip actually uploading the stack trace then.
-                    if (sNumUploads.getAndAdd(1) >= MAX_UPLOADS_PER_SESSION) {
-                        break;
-                    }
-                    sCachedStackTraces.add(get(i));
-                }
-            }
-            super.clear();
-        }
-    }
-
     /**
      * Always process the violation on the UI thread. This ensures other crash reports are not
      * corrupted. Since each individual user has a very small chance of uploading each violation,
@@ -65,45 +51,36 @@
      * @param violationInfo The violation info from the StrictMode violation in question.
      */
     @UiThread
-    private static void reportStrictModeViolation(Object violationInfo) {
-        try {
-            Field crashInfoField = violationInfo.getClass().getField("crashInfo");
-            ApplicationErrorReport.CrashInfo crashInfo =
-                    (ApplicationErrorReport.CrashInfo) crashInfoField.get(violationInfo);
-            String stackTrace = crashInfo.stackTrace;
-            if (stackTrace == null) {
-                Log.d(TAG, "StrictMode violation stack trace was null.");
-            } else {
-                Log.d(TAG, "Upload stack trace: " + stackTrace);
-                JavaExceptionReporter.reportStackTrace(stackTrace);
-            }
-        } catch (Exception e) {
-            // Ignore all exceptions.
-            Log.d(TAG, "Could not handle observed StrictMode violation.", e);
+    private static void reportStrictModeViolation(Violation violation) {
+        StringWriter stackTraceWriter = new StringWriter();
+        new StrictModePolicyViolation(violation).printStackTrace(new PrintWriter(stackTraceWriter));
+        String stackTrace = stackTraceWriter.toString();
+        if (TextUtils.isEmpty(stackTrace)) {
+            Log.d(TAG, "StrictMode violation stack trace was empty.");
+        } else {
+            Log.d(TAG, "Upload stack trace: " + stackTrace);
+            JavaExceptionReporter.reportStackTrace(stackTrace);
         }
     }
 
     /**
-     * Replace Android OS's StrictMode.violationsBeingTimed with a custom ArrayList acting as an
-     * observer into violation stack traces. Set up an idle handler so StrictMode violations that
-     * occur on startup are not ignored.
+     * Add custom {@link ThreadStrictModeInterceptor} penalty which records strict mode violations.
+     * Set up an idle handler so StrictMode violations that occur on startup are not ignored.
      */
-    @SuppressWarnings({"unchecked", "rawtypes" })
     @UiThread
-    private static void initializeStrictModeWatch() {
-        try {
-            Field violationsBeingTimedField =
-                    StrictMode.class.getDeclaredField("violationsBeingTimed");
-            violationsBeingTimedField.setAccessible(true);
-            ThreadLocal<ArrayList> violationsBeingTimed =
-                    (ThreadLocal<ArrayList>) violationsBeingTimedField.get(null);
-            ArrayList replacementList = new SnoopingArrayList();
-            violationsBeingTimed.set(replacementList);
-        } catch (Exception e) {
-            // Terminate watch if any exceptions are raised.
-            Log.w(TAG, "Could not initialize StrictMode watch.", e);
-            return;
-        }
+    private static void initializeStrictModeWatch(
+            ThreadStrictModeInterceptor.Builder threadInterceptor) {
+        threadInterceptor.setCustomPenalty(violation -> {
+            if (Math.random() < UPLOAD_PROBABILITY) {
+                // Ensure that we do not upload too many StrictMode violations in any single
+                // session. To prevent races, we allow sNumUploads to increase beyond the limit, but
+                // just skip actually uploading the stack trace then.
+                if (sNumUploads.getAndAdd(1) < MAX_UPLOADS_PER_SESSION) {
+                    sCachedViolations.add(violation);
+                }
+            }
+        });
+
         sNumUploads.set(0);
         // Delay handling StrictMode violations during initialization until the main loop is idle.
         Looper.myQueue().addIdleHandler(() -> {
@@ -111,14 +88,14 @@
             if (!LibraryLoader.getInstance().isInitialized()) return true;
             // Check again next time if no more cached stack traces to upload, and we have not
             // reached the max number of uploads for this session.
-            if (sCachedStackTraces.isEmpty()) {
+            if (sCachedViolations.isEmpty()) {
                 // TODO(wnwen): Add UMA count when this happens.
                 // In case of races, continue checking an extra time (equal condition).
                 return sNumUploads.get() <= MAX_UPLOADS_PER_SESSION;
             }
             // Since this is the only place we are removing elements, no need for additional
             // synchronization to ensure it is still non-empty.
-            reportStrictModeViolation(sCachedStackTraces.remove(0));
+            reportStrictModeViolation(sCachedViolations.remove(0));
             return true;
         });
     }
@@ -139,14 +116,12 @@
         }
     }
 
-    private static void addDefaultPenalties(StrictMode.ThreadPolicy.Builder threadPolicy,
-            StrictMode.VmPolicy.Builder vmPolicy) {
+    private static void addDefaultThreadPenalties(StrictMode.ThreadPolicy.Builder threadPolicy) {
         threadPolicy.penaltyLog().penaltyFlashScreen().penaltyDeathOnNetwork();
-        vmPolicy.penaltyLog();
     }
 
-    private static void addThreadDeathPenalty(StrictMode.ThreadPolicy.Builder threadPolicy) {
-        threadPolicy.penaltyDeath();
+    private static void addDefaultVmPenalties(StrictMode.VmPolicy.Builder vmPolicy) {
+        vmPolicy.penaltyLog();
     }
 
     private static void addVmDeathPenalty(StrictMode.VmPolicy.Builder vmPolicy) {
@@ -188,25 +163,28 @@
                 new StrictMode.ThreadPolicy.Builder(StrictMode.getThreadPolicy());
         StrictMode.VmPolicy.Builder vmPolicy =
                 new StrictMode.VmPolicy.Builder(StrictMode.getVmPolicy());
+        ThreadStrictModeInterceptor.Builder threadInterceptor =
+                new ThreadStrictModeInterceptor.Builder();
+        turnOnDetection(threadPolicy, vmPolicy);
 
         if (shouldApplyPenalties) {
-            turnOnDetection(threadPolicy, vmPolicy);
-            addDefaultPenalties(threadPolicy, vmPolicy);
+            addDefaultVmPenalties(vmPolicy);
             if ("death".equals(commandLine.getSwitchValue(ChromeSwitches.STRICT_MODE))) {
-                addThreadDeathPenalty(threadPolicy);
+                threadInterceptor.replaceAllPenaltiesWithDeathPenalty();
                 addVmDeathPenalty(vmPolicy);
             } else if ("testing".equals(commandLine.getSwitchValue(ChromeSwitches.STRICT_MODE))) {
-                addThreadDeathPenalty(threadPolicy);
+                threadInterceptor.replaceAllPenaltiesWithDeathPenalty();
                 // Currently VmDeathPolicy kills the process, and is not visible on bot test output.
+            } else {
+                addDefaultThreadPenalties(threadPolicy);
             }
         }
-
         if (enableStrictModeWatch) {
-            turnOnDetection(threadPolicy, vmPolicy);
-            initializeStrictModeWatch();
+            initializeStrictModeWatch(threadInterceptor);
         }
 
-        StrictMode.setThreadPolicy(threadPolicy.build());
+        KnownViolations.addExemptions(threadInterceptor);
+        threadInterceptor.build().install(threadPolicy.build());
         StrictMode.setVmPolicy(vmPolicy.build());
     }
 }
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 42c1b2e..ea32872 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -659,8 +659,9 @@
                 TabManagementDelegate tabManagementDelegate =
                         TabManagementModuleProvider.getDelegate();
                 if (tabManagementDelegate != null) {
-                    mStartSurface = tabManagementDelegate.createStartSurface(
-                            this, mRootUiCoordinator.getScrimCoordinator());
+                    mStartSurface = tabManagementDelegate.createStartSurface(this,
+                            mRootUiCoordinator.getScrimCoordinator(),
+                            mRootUiCoordinator.getBottomSheetController());
                 }
             }
             mLayoutManager = new LayoutManagerChromePhone(compositorViewHolder, mStartSurface);
@@ -862,6 +863,7 @@
             return;
         }
 
+        DefaultBrowserPromoUtils.maybeRemoveIntentData(intent);
         mIntentHandlingTimeMs = SystemClock.uptimeMillis();
         super.onNewIntent(intent);
     }
@@ -1690,7 +1692,8 @@
     protected Pair<ChromeTabCreator, ChromeTabCreator> createTabCreators() {
         Supplier<TabDelegateFactory> tabDelegateFactorySupplier = () -> {
             return new TabbedModeTabDelegateFactory(this, getAppBrowserControlsVisibilityDelegate(),
-                    getShareDelegateSupplier(), mEphemeralTabCoordinatorSupplier);
+                    getShareDelegateSupplier(), mEphemeralTabCoordinatorSupplier,
+                    mRootUiCoordinator.getBottomSheetController());
         };
 
         ChromeTabCreator.OverviewNTPCreator overviewNTPCreator = null;
@@ -2215,7 +2218,8 @@
             mShowHistoryRunnable = null;
             if (event.getEventTime() - event.getDownTime()
                             >= ViewConfiguration.getLongPressTimeout()
-                    && NavigationSheet.isInstanceShowing(getBottomSheetController())) {
+                    && NavigationSheet.isInstanceShowing(
+                            mRootUiCoordinator.getBottomSheetController())) {
                 // If tab history popup is showing, do not process the keyUp event
                 // which will dismiss it immediately.
                 return true;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
index 6d03dbb..6ca3e36f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
@@ -33,6 +33,8 @@
 import org.chromium.base.FileUtils;
 import org.chromium.base.IntentUtils;
 import org.chromium.base.Log;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
@@ -41,7 +43,6 @@
 import org.chromium.chrome.browser.externalnav.ExternalNavigationDelegateImpl;
 import org.chromium.chrome.browser.externalnav.IntentWithRequestMetadataHandler;
 import org.chromium.chrome.browser.externalnav.IntentWithRequestMetadataHandler.RequestMetadata;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.offlinepages.OfflinePageUtils;
 import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteCoordinatorFactory;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -71,6 +72,7 @@
 /**
  * Handles all browser-related Intents.
  */
+@JNINamespace("chrome::android")
 public class IntentHandler {
     private static final String TAG = "IntentHandler";
 
@@ -468,7 +470,7 @@
         }
 
         String referrerUrl = getReferrerUrlIncludingExtraHeaders(intent);
-        String extraHeaders = getExtraHeadersFromIntent(intent, true);
+        String extraHeaders = getExtraHeadersFromIntent(intent);
 
         if (isIntentForMhtmlFileOrContent(intent) && tabOpenType == TabOpenType.OPEN_NEW_TAB
                 && referrerUrl == null && extraHeaders == null) {
@@ -582,7 +584,7 @@
         if (referrer != null) {
             params.setReferrer(new Referrer(referrer, getReferrerPolicyFromIntent(intent)));
         }
-        String headers = getExtraHeadersFromIntent(intent, true);
+        String headers = getExtraHeadersFromIntent(intent);
         if (headers != null) params.setVerbatimHeaders(headers);
     }
 
@@ -786,52 +788,32 @@
     }
 
     /**
-     * Calls {@link #getExtraHeadersFromIntent(Intent, boolean)} with shouldLogHeaders as false.
-     */
-    public static String getExtraHeadersFromIntent(Intent intent) {
-        return getExtraHeadersFromIntent(intent, false);
-    }
-
-    /**
      * Returns a String (or null) containing the extra headers sent by the intent, if any.
      *
      * This methods skips the referrer header.
      *
      * @param intent The intent containing the bundle extra with the HTTP headers.
-     * @param shouldLogHeaders Whether we should perform logging on the types of headers that the
-     *                         Intent contains. This should only be done for Intents as they come
-     *                         in to Chrome.
      */
-    public static String getExtraHeadersFromIntent(Intent intent, boolean shouldLogHeaders) {
+    public static String getExtraHeadersFromIntent(Intent intent) {
         Bundle bundleExtraHeaders = IntentUtils.safeGetBundleExtra(intent, Browser.EXTRA_HEADERS);
         if (bundleExtraHeaders == null) return null;
         StringBuilder extraHeaders = new StringBuilder();
 
-        // We do some logging to determine what kinds of headers developers are inserting.
-        IntentHeadersRecorder recorder = shouldLogHeaders ? new IntentHeadersRecorder() : null;
-        boolean shouldBlockNonSafelistedHeaders = ChromeFeatureList.isEnabled(
-                ChromeFeatureList.ANDROID_BLOCK_INTENT_NON_SAFELISTED_HEADERS);
-        IntentHeadersRecorder.HeaderClassifier headerClassifier = shouldBlockNonSafelistedHeaders
-                ? new IntentHeadersRecorder.HeaderClassifier()
-                : null;
         boolean fromChrome = IntentHandler.wasIntentSenderChrome(intent);
-        boolean firstParty = shouldLogHeaders
-                ? (IntentHandler.notSecureIsIntentChromeOrFirstParty(intent) && !fromChrome)
-                : false;
 
         for (String key : bundleExtraHeaders.keySet()) {
             String value = bundleExtraHeaders.getString(key);
 
-            if (!HttpUtil.isAllowedHeader(key, value)) continue;
+            if (!HttpUtil.isAllowedHeader(key, value)) {
+                Log.w(TAG, "Ignoring forbidden header " + key + "in EXTRA_HEADERS.");
+            }
 
             // Strip the custom header that can only be added by ourselves.
             if ("x-chrome-intent-type".equals(key.toLowerCase(Locale.US))) continue;
 
             if (!fromChrome) {
-                if (shouldLogHeaders) recorder.recordHeader(key, value, firstParty);
-
-                if (shouldBlockNonSafelistedHeaders
-                        && !headerClassifier.isCorsSafelistedHeader(key, value, firstParty)) {
+                if (!IntentHandlerJni.get().isCorsSafelistedHeader(key, value)) {
+                    Log.w(TAG, "Ignoring non-CORS-safelisted header " + key + "in EXTRA_HEADERS.");
                     continue;
                 }
             }
@@ -842,7 +824,6 @@
             extraHeaders.append(value);
         }
 
-        if (shouldLogHeaders) recorder.report(firstParty);
         return extraHeaders.length() == 0 ? null : extraHeaders.toString();
     }
 
@@ -1391,4 +1372,9 @@
 
         return newIntent;
     }
+
+    @NativeMethods
+    interface Natives {
+        boolean isCorsSafelistedHeader(String name, String value);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/IntentHeadersRecorder.java b/chrome/android/java/src/org/chromium/chrome/browser/IntentHeadersRecorder.java
deleted file mode 100644
index acf59dd..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/IntentHeadersRecorder.java
+++ /dev/null
@@ -1,111 +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.
-
-package org.chromium.chrome.browser;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.VisibleForTesting;
-
-import org.chromium.base.annotations.JNINamespace;
-import org.chromium.base.annotations.NativeMethods;
-import org.chromium.base.metrics.RecordHistogram;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Collects information about the HTTP headers passed into an Intent as Browser.EXTRA_HEADERS and
- * records UMA. Call {@link #recordHeader} for each header in the Intent and {@link #report}
- * afterwards.
- *
- * Lifecycle: Create an instance of this class for each Intent whose Headers you want to record.
- * Thread safety: All methods on this class should be called on the UI thread.
- */
-public class IntentHeadersRecorder {
-    /** Determines whether a header is CORS Safelisted or not. */
-    @JNINamespace("chrome::android")
-    /* package */ static class HeaderClassifier {
-        /* package */ boolean isCorsSafelistedHeader(
-                String name, String value, boolean firstParty) {
-            return IntentHeadersRecorderJni.get().isCorsSafelistedHeader(name, value, firstParty);
-        }
-    }
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({IntentHeadersResult.FIRST_PARTY_NO_HEADERS,
-            IntentHeadersResult.FIRST_PARTY_ONLY_SAFE_HEADERS,
-            IntentHeadersResult.FIRST_PARTY_UNSAFE_HEADERS,
-            IntentHeadersResult.THIRD_PARTY_NO_HEADERS,
-            IntentHeadersResult.THIRD_PARTY_ONLY_SAFE_HEADERS,
-            IntentHeadersResult.THIRD_PARTY_UNSAFE_HEADERS})
-    @VisibleForTesting
-    @interface IntentHeadersResult {
-        // Don't reuse or reorder values. If you add something, update NUM_ENTRIES.
-        int FIRST_PARTY_NO_HEADERS = 0;
-        int FIRST_PARTY_ONLY_SAFE_HEADERS = 1;
-        int FIRST_PARTY_UNSAFE_HEADERS = 2;
-        int THIRD_PARTY_NO_HEADERS = 3;
-        int THIRD_PARTY_ONLY_SAFE_HEADERS = 4;
-        int THIRD_PARTY_UNSAFE_HEADERS = 5;
-        int NUM_ENTRIES = 6;
-    }
-
-    private final HeaderClassifier mClassifier;
-    private int mSafeHeaders;
-    private int mUnsafeHeaders;
-
-    /** Creates this class with a custom classifier (for testing). */
-    public IntentHeadersRecorder(HeaderClassifier classifier) {
-        mClassifier = classifier;
-    }
-
-    /** Creates this class with a classifier that checks Chrome native code. */
-    public IntentHeadersRecorder() {
-        this(new HeaderClassifier());
-    }
-
-    /* Records that a HTTP header has been used. */
-    public void recordHeader(String name, String value, boolean firstParty) {
-        if (mClassifier.isCorsSafelistedHeader(name, value, firstParty)) {
-            mSafeHeaders++;
-        } else {
-            mUnsafeHeaders++;
-        }
-    }
-
-    /**
-     * Logs the types of headers that have previously been {@link #recordHeader}ed.
-     * @param firstParty Whether the Intent is from a first or third party app. As it is just for
-     *                   logging, this is not security sensitive.
-     */
-    public void report(boolean firstParty) {
-        if (firstParty) {
-            if (mSafeHeaders == 0 && mUnsafeHeaders == 0) {
-                record(IntentHeadersResult.FIRST_PARTY_NO_HEADERS);
-            } else if (mUnsafeHeaders == 0) {
-                record(IntentHeadersResult.FIRST_PARTY_ONLY_SAFE_HEADERS);
-            } else {
-                record(IntentHeadersResult.FIRST_PARTY_UNSAFE_HEADERS);
-            }
-        } else {
-            if (mSafeHeaders == 0 && mUnsafeHeaders == 0) {
-                record(IntentHeadersResult.THIRD_PARTY_NO_HEADERS);
-            } else if (mUnsafeHeaders == 0) {
-                record(IntentHeadersResult.THIRD_PARTY_ONLY_SAFE_HEADERS);
-            } else {
-                record(IntentHeadersResult.THIRD_PARTY_UNSAFE_HEADERS);
-            }
-        }
-    }
-
-    private static void record(@IntentHeadersResult int result) {
-        RecordHistogram.recordEnumeratedHistogram("Android.IntentHeaders", result,
-                IntentHeadersResult.NUM_ENTRIES);
-    }
-
-    @NativeMethods
-    interface Natives {
-        boolean isCorsSafelistedHeader(String name, String value, boolean firstParty);
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/TabbedModeTabDelegateFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/TabbedModeTabDelegateFactory.java
index c993028..2a7c0ca2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/TabbedModeTabDelegateFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/TabbedModeTabDelegateFactory.java
@@ -19,6 +19,7 @@
 import org.chromium.chrome.browser.tab.TabStateBrowserControlsVisibilityDelegate;
 import org.chromium.chrome.browser.tab.TabWebContentsDelegateAndroid;
 import org.chromium.chrome.browser.ui.native_page.NativePage;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.util.BrowserControlsVisibilityDelegate;
 import org.chromium.components.browser_ui.util.ComposedBrowserControlsVisibilityDelegate;
 import org.chromium.components.external_intents.ExternalNavigationHandler;
@@ -32,16 +33,19 @@
     private final BrowserControlsVisibilityDelegate mAppBrowserControlsVisibilityDelegate;
     private final Supplier<ShareDelegate> mShareDelegateSupplier;
     private final Supplier<EphemeralTabCoordinator> mEphemeralTabCoordinatorSupplier;
+    private final BottomSheetController mBottomSheetController;
     private NativePageFactory mNativePageFactory;
 
     public TabbedModeTabDelegateFactory(ChromeActivity activity,
             BrowserControlsVisibilityDelegate appBrowserControlsVisibilityDelegate,
             Supplier<ShareDelegate> shareDelegateSupplier,
-            Supplier<EphemeralTabCoordinator> ephemeralTabCoordinatorSupplier) {
+            Supplier<EphemeralTabCoordinator> ephemeralTabCoordinatorSupplier,
+            BottomSheetController sheetController) {
         mActivity = activity;
         mAppBrowserControlsVisibilityDelegate = appBrowserControlsVisibilityDelegate;
         mShareDelegateSupplier = shareDelegateSupplier;
         mEphemeralTabCoordinatorSupplier = ephemeralTabCoordinatorSupplier;
+        mBottomSheetController = sheetController;
     }
 
     @Override
@@ -72,7 +76,9 @@
 
     @Override
     public NativePage createNativePage(String url, NativePage candidatePage, Tab tab) {
-        if (mNativePageFactory == null) mNativePageFactory = new NativePageFactory(mActivity);
+        if (mNativePageFactory == null) {
+            mNativePageFactory = new NativePageFactory(mActivity, mBottomSheetController);
+        }
         return mNativePageFactory.createNativePage(url, candidatePage, tab);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/about_settings/AboutChromePreferenceOSVersion.java b/chrome/android/java/src/org/chromium/chrome/browser/about_settings/AboutChromePreferenceOSVersion.java
index 7d7cbf7..f6bfb21 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/about_settings/AboutChromePreferenceOSVersion.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/about_settings/AboutChromePreferenceOSVersion.java
@@ -6,7 +6,8 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
-import android.view.View;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
 
 import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
@@ -24,7 +25,6 @@
      */
     public AboutChromePreferenceOSVersion(Context context, AttributeSet attrs) {
         super(context, attrs);
-        setLayoutResource(R.layout.os_version_unsupported_preference);
     }
 
     @Override
@@ -33,6 +33,7 @@
         // Show additional information only if the OS version is not supported.
         if (VersionNumberGetter.isCurrentOsVersionSupported()) return;
 
-        holder.findViewById(R.id.os_deprecation_warning).setVisibility(View.VISIBLE);
+        ViewGroup root = (ViewGroup) holder.findViewById(android.R.id.summary).getParent();
+        LayoutInflater.from(getContext()).inflate(R.layout.os_version_unsupported_text, root, true);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/accessibility_tab_switcher/OverviewListLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/accessibility_tab_switcher/OverviewListLayout.java
index 3967ae4..18025be5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/accessibility_tab_switcher/OverviewListLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/accessibility_tab_switcher/OverviewListLayout.java
@@ -16,6 +16,7 @@
 
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.accessibility_tab_switcher.AccessibilityTabModelAdapter.AccessibilityTabModelAdapterListener;
+import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
 import org.chromium.chrome.browser.compositor.layouts.Layout;
 import org.chromium.chrome.browser.compositor.layouts.LayoutRenderHost;
 import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost;
@@ -39,13 +40,34 @@
     private final float mDensity;
     private final BlackHoleEventFilter mBlackHoleEventFilter;
     private final SceneLayer mSceneLayer;
+    private final BrowserControlsStateProvider mBrowserControlsStateProvider;
+    private final BrowserControlsStateProvider.Observer mBrowserControlsObserver;
 
-    public OverviewListLayout(
-            Context context, LayoutUpdateHost updateHost, LayoutRenderHost renderHost) {
+    public OverviewListLayout(Context context, LayoutUpdateHost updateHost,
+            LayoutRenderHost renderHost,
+            BrowserControlsStateProvider browserControlsStateProvider) {
         super(context, updateHost, renderHost);
         mBlackHoleEventFilter = new BlackHoleEventFilter(context);
         mDensity = context.getResources().getDisplayMetrics().density;
         mSceneLayer = new SceneLayer();
+        mBrowserControlsStateProvider = browserControlsStateProvider;
+        mBrowserControlsObserver = new BrowserControlsStateProvider.Observer() {
+            @Override
+            public void onControlsOffsetChanged(int topOffset, int topControlsMinHeightOffset,
+                    int bottomOffset, int bottomControlsMinHeightOffset, boolean needsAnimate) {
+                adjustForFullscreen();
+            }
+        };
+        mBrowserControlsStateProvider.addObserver(mBrowserControlsObserver);
+    }
+
+    @Override
+    public void destroy() {
+        if (mBrowserControlsStateProvider != null) {
+            mBrowserControlsStateProvider.removeObserver(mBrowserControlsObserver);
+        }
+
+        super.destroy();
     }
 
     @Override
@@ -84,7 +106,7 @@
         if (params == null) return;
 
         params.bottomMargin = (int) (getBottomBrowserControlsHeight() * mDensity);
-        params.topMargin = (int) (getTopBrowserControlsHeight() * mDensity);
+        params.topMargin = mBrowserControlsStateProvider.getContentOffset();
 
         mTabModelWrapper.setLayoutParams(params);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPromoHeader.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPromoHeader.java
index 42d9c76..ef1940e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPromoHeader.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPromoHeader.java
@@ -20,6 +20,7 @@
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
 import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.signin.IdentityServicesProvider;
 import org.chromium.chrome.browser.signin.PersonalizedSigninPromoView;
 import org.chromium.chrome.browser.signin.ProfileDataCache;
@@ -82,7 +83,8 @@
 
         AndroidSyncSettings.get().registerObserver(this);
 
-        mSignInManager = IdentityServicesProvider.get().getSigninManager();
+        mSignInManager = IdentityServicesProvider.get().getSigninManager(
+                Profile.getLastUsedRegularProfile());
         mSignInManager.addSignInStateObserver(this);
 
         mPromoState = calculatePromoState();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
index 2d441cb..273641b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
@@ -40,6 +40,8 @@
 import org.chromium.base.TraceEvent;
 import org.chromium.base.compat.ApiHelperForN;
 import org.chromium.base.compat.ApiHelperForO;
+import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsUtils;
@@ -144,6 +146,8 @@
 
     private TabModelSelector mTabModelSelector;
     private @Nullable BrowserControlsManager mBrowserControlsManager;
+    private ObservableSupplierImpl<BrowserControlsManager> mBrowserControlsManagerSupplier =
+            new ObservableSupplierImpl<>();
     private View mAccessibilityView;
     private CompositorAccessibilityProvider mNodeProvider;
 
@@ -1106,6 +1110,11 @@
     }
 
     @Override
+    public ObservableSupplier<BrowserControlsManager> getBrowserControlsManagerSupplier() {
+        return mBrowserControlsManagerSupplier;
+    }
+
+    @Override
     public FullscreenManager getFullscreenManager() {
         return mBrowserControlsManager.getFullscreenManager();
     }
@@ -1117,6 +1126,7 @@
     public void setBrowserControlsManager(BrowserControlsManager manager) {
         mBrowserControlsManager = manager;
         mBrowserControlsManager.addObserver(this);
+        mBrowserControlsManagerSupplier.set(mBrowserControlsManager);
         onViewportChanged();
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabSheetContent.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabSheetContent.java
index ff78a13..cb18621 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabSheetContent.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabSheetContent.java
@@ -157,8 +157,8 @@
         // TODO(shaktisahu): Find out if there is a better way for this animation.
         Drawable presentedDrawable = favicon;
         if (mCurrentFavicon != null && !(mCurrentFavicon instanceof TransitionDrawable)) {
-            TransitionDrawable transitionDrawable = ApiCompatibilityUtils.createTransitionDrawable(
-                    new Drawable[] {mCurrentFavicon, favicon});
+            TransitionDrawable transitionDrawable =
+                    new TransitionDrawable(new Drawable[] {mCurrentFavicon, favicon});
             transitionDrawable.setCrossFadeEnabled(true);
             transitionDrawable.startTransition(BASE_ANIMATION_DURATION_MS);
             presentedDrawable = transitionDrawable;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/Layout.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/Layout.java
index 253b12e..3c30dd4a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/Layout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/Layout.java
@@ -343,18 +343,17 @@
 
     /**
      * Called when the size of the viewport has changed.
-     * @param visibleViewport        The visible viewport that represents the area on the screen
-     *                               this {@link Layout} gets to draw to in px (potentially takes
-     *                               into account browser controls).
-     * @param screenViewport         The viewport of the screen in px.
-     * @param heightMinusBrowserControls The height the {@link Layout} gets excluding the height of
-     *                               the browser controls in px. TODO(dtrainor): Look at getting rid
-     *                               of this.
-     * @param orientation            The new orientation.  Valid values are defined by
-     *                               {@link Orientation}.
+     * @param visibleViewportPx             The visible viewport that represents the area on the
+     *                                      screen this {@link Layout} gets to draw to in px
+     *                                      (potentially takes into account browser controls).
+     * @param screenViewportPx              The viewport of the screen in px.
+     * @param topBrowserControlsHeightPx    The top browser controls height in px.
+     * @param bottomBrowserControlsHeightPx The bottom browser controls height in px.
+     * @param orientation                   The new orientation.  Valid values are defined by
+     *                                      {@link Orientation}.
      */
-    public final void sizeChanged(RectF visibleViewportPx, RectF screenViewportPx,
-            float topBrowserControlsHeightPx, float bottomBrowserControlsHeightPx,
+    final void sizeChanged(RectF visibleViewportPx, RectF screenViewportPx,
+            int topBrowserControlsHeightPx, int bottomBrowserControlsHeightPx,
             @Orientation int orientation) {
         // 1. Pull out this Layout's width and height properties based on the viewport.
         float width = screenViewportPx.width() / mDpToPx;
@@ -551,13 +550,6 @@
     }
 
     /**
-     * @return The height of the top browser controls in dp.
-     */
-    public float getTopBrowserControlsHeight() {
-        return mTopBrowserControlsHeightDp;
-    }
-
-    /**
      * @return The height of the bottom browser controls in dp.
      */
     public float getBottomBrowserControlsHeight() {
@@ -565,13 +557,6 @@
     }
 
     /**
-     * @return The height of the drawing area minus the browser controls in dp.
-     */
-    public float getHeightMinusBrowserControls() {
-        return getHeight() - (getTopBrowserControlsHeight() + getBottomBrowserControlsHeight());
-    }
-
-    /**
      * @see Orientation
      * @return The orientation of the screen (portrait or landscape). Values are defined by
      *         {@link Orientation}.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChrome.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChrome.java
index 5479292..5bebb44 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChrome.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChrome.java
@@ -12,7 +12,9 @@
 
 import org.chromium.base.ObserverList;
 import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.chrome.browser.accessibility_tab_switcher.OverviewListLayout;
+import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
 import org.chromium.chrome.browser.compositor.TitleCache;
 import org.chromium.chrome.browser.compositor.layouts.components.LayoutTab;
 import org.chromium.chrome.browser.compositor.layouts.components.VirtualView;
@@ -106,8 +108,11 @@
                         }
                     }
                 });
-                mOverviewLayout = tabManagementDelegate.createStartSurfaceLayout(
-                        context, this, renderHost, startSurface);
+                final ObservableSupplier<? extends BrowserControlsStateProvider>
+                        browserControlsSupplier = mHost.getBrowserControlsManagerSupplier();
+                mOverviewLayout = tabManagementDelegate.createStartSurfaceLayout(context, this,
+                        renderHost, startSurface,
+                        (ObservableSupplier<BrowserControlsStateProvider>) browserControlsSupplier);
             } else {
                 mCreateOverviewLayout = true;
             }
@@ -145,13 +150,19 @@
             DynamicResourceLoader dynamicResourceLoader) {
         Context context = mHost.getContext();
         LayoutRenderHost renderHost = mHost.getLayoutRenderHost();
+        BrowserControlsStateProvider browserControlsStateProvider =
+                mHost.getBrowserControlsManager();
 
         // Build Layouts
-        mOverviewListLayout = new OverviewListLayout(context, this, renderHost);
+        mOverviewListLayout =
+                new OverviewListLayout(context, this, renderHost, browserControlsStateProvider);
         mToolbarSwipeLayout = new ToolbarSwipeLayout(context, this, renderHost);
 
         if (mCreateOverviewLayout) {
-            mOverviewLayout = new StackLayout(context, this, renderHost);
+            final ObservableSupplier<? extends BrowserControlsStateProvider>
+                    browserControlsSupplier = mHost.getBrowserControlsManagerSupplier();
+            mOverviewLayout = new StackLayout(context, this, renderHost,
+                    (ObservableSupplier<BrowserControlsStateProvider>) browserControlsSupplier);
         }
 
         super.init(selector, creator, content, androidContentContainer, controlContainer,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerHost.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerHost.java
index 78f690b..cfdf682 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerHost.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerHost.java
@@ -8,6 +8,7 @@
 import android.graphics.RectF;
 import android.view.View;
 
+import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.chrome.browser.compositor.TitleCache;
 import org.chromium.chrome.browser.fullscreen.BrowserControlsManager;
 import org.chromium.chrome.browser.fullscreen.FullscreenManager;
@@ -111,6 +112,11 @@
     BrowserControlsManager getBrowserControlsManager();
 
     /**
+     * @return An {@link ObservableSupplier} supplier for the {@link BrowserControlsManager}.
+     */
+    ObservableSupplier<BrowserControlsManager> getBrowserControlsManagerSupplier();
+
+    /**
      * @return The manager in charge of handling fullscreen changes.
      */
     FullscreenManager getFullscreenManager();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayout.java
index 4e0ffa7..880c9042 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayout.java
@@ -7,6 +7,8 @@
 import android.content.Context;
 
 import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
 import org.chromium.chrome.browser.compositor.layouts.LayoutRenderHost;
 import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost;
 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
@@ -33,12 +35,17 @@
     private boolean mAnimatingStackSwitch;
 
     /**
-     * @param context     The current Android's context.
-     * @param updateHost  The {@link LayoutUpdateHost} view for this layout.
-     * @param renderHost  The {@link LayoutRenderHost} view for this layout.
+     * @param context                              The current Android's context.
+     * @param updateHost                           The {@link LayoutUpdateHost} view for this
+     *                                             layout.
+     * @param renderHost                           The {@link LayoutRenderHost} view for this
+     *                                             layout.
+     * @param browserControlsStateProviderSupplier The {@link ObservableSupplier} for the
+     *                                             {@link BrowserControlsStateProvider}.
      */
-    public StackLayout(Context context, LayoutUpdateHost updateHost, LayoutRenderHost renderHost) {
-        super(context, updateHost, renderHost);
+    public StackLayout(Context context, LayoutUpdateHost updateHost, LayoutRenderHost renderHost,
+            ObservableSupplier<BrowserControlsStateProvider> browserControlsStateProviderSupplier) {
+        super(context, updateHost, renderHost, browserControlsStateProviderSupplier);
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayoutBase.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayoutBase.java
index 0f311d0..b3f1c85 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayoutBase.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayoutBase.java
@@ -18,11 +18,14 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.VisibleForTesting;
 
+import org.chromium.base.Callback;
 import org.chromium.base.MathUtils;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
+import org.chromium.chrome.browser.browser_controls.BrowserControlsUtils;
 import org.chromium.chrome.browser.compositor.LayerTitleCache;
 import org.chromium.chrome.browser.compositor.animation.CompositorAnimator;
 import org.chromium.chrome.browser.compositor.animation.FloatProperty;
@@ -233,6 +236,10 @@
     private final ArrayList<Pair<CompositorAnimator, FloatProperty>> mLayoutAnimations =
             new ArrayList<>();
 
+    private final ObservableSupplier<BrowserControlsStateProvider> mBrowserControlsSupplier;
+    private final BrowserControlsStateProvider.Observer mBrowserControlsObserver;
+    private Callback<BrowserControlsStateProvider> mBrowserControlsSupplierObserver;
+
     private class StackLayoutGestureHandler implements GestureHandler {
         @Override
         public void onDown(float x, float y, boolean fromMouse, int buttons) {
@@ -368,12 +375,17 @@
     }
 
     /**
-     * @param context     The current Android's context.
-     * @param updateHost  The {@link LayoutUpdateHost} view for this layout.
-     * @param renderHost  The {@link LayoutRenderHost} view for this layout.
+     * @param context                              The current Android's context.
+     * @param updateHost                           The {@link LayoutUpdateHost} view for this
+     *                                             layout.
+     * @param renderHost                           The {@link LayoutRenderHost} view for this
+     *                                             layout.
+     * @param browserControlsStateProviderSupplier An {@link ObservableSupplier} for the
+     *                                             {@link BrowserControlsStateProvider}.
      */
-    public StackLayoutBase(
-            Context context, LayoutUpdateHost updateHost, LayoutRenderHost renderHost) {
+    public StackLayoutBase(Context context, LayoutUpdateHost updateHost,
+            LayoutRenderHost renderHost,
+            ObservableSupplier<BrowserControlsStateProvider> browserControlsStateProviderSupplier) {
         super(context, updateHost, renderHost);
 
         mGestureHandler = new StackLayoutGestureHandler();
@@ -387,6 +399,32 @@
         mViewContainer = new FrameLayout(getContext());
         mSceneLayer = new TabListSceneLayer();
         mDpToPx = context.getResources().getDisplayMetrics().density;
+        mBrowserControlsSupplier = browserControlsStateProviderSupplier;
+        mBrowserControlsObserver = new BrowserControlsStateProvider.Observer() {
+            @Override
+            public void onControlsOffsetChanged(int topOffset, int topControlsMinHeightOffset,
+                    int bottomOffset, int bottomControlsMinHeightOffset, boolean needsAnimate) {
+                notifySizeChanged(mWidth, mHeight, mOrientation);
+            }
+        };
+
+        // TODO(https://crbug.com/1084528): Replace with OneShotSupplier when it is available.
+        mBrowserControlsSupplierObserver = (browserControlsStateProvider)
+                -> browserControlsStateProvider.addObserver(mBrowserControlsObserver);
+        mBrowserControlsSupplier.addObserver(mBrowserControlsSupplierObserver);
+    }
+
+    @Override
+    public void destroy() {
+        if (mBrowserControlsSupplier != null) {
+            mBrowserControlsSupplier.removeObserver(mBrowserControlsSupplierObserver);
+
+            if (mBrowserControlsSupplier.get() != null) {
+                mBrowserControlsSupplier.get().removeObserver(mBrowserControlsObserver);
+            }
+        }
+
+        super.destroy();
     }
 
     /**
@@ -1006,7 +1044,7 @@
         protected float mHeight;
         PortraitViewport() {
             mWidth = StackLayoutBase.this.getWidth();
-            mHeight = StackLayoutBase.this.getHeightMinusBrowserControls();
+            mHeight = StackLayoutBase.this.getHeightMinusContentOffsetsDp();
         }
 
         float getClampedRenderedScrollOffset() {
@@ -1095,14 +1133,14 @@
         }
 
         float getTopHeightOffset() {
-            return getTopBrowserControlsHeight() * mStackOffsetYPercent;
+            return getTopContentOffsetDp() * mStackOffsetYPercent;
         }
     }
 
     class LandscapeViewport extends PortraitViewport {
         LandscapeViewport() {
             // This is purposefully inverted.
-            mWidth = StackLayoutBase.this.getHeightMinusBrowserControls();
+            mWidth = StackLayoutBase.this.getHeightMinusContentOffsetsDp();
             mHeight = StackLayoutBase.this.getWidth();
         }
 
@@ -1174,6 +1212,30 @@
         }
     }
 
+    /**
+     * @return The height of the drawing area minus the top and bottom content offsets in dp.
+     */
+    public float getHeightMinusContentOffsetsDp() {
+        return getHeight() - (getTopContentOffsetDp() + getBottomContentOffsetDp());
+    }
+
+    /**
+     * @return The offset of the content from the top of the screen in dp.
+     */
+    public float getTopContentOffsetDp() {
+        final BrowserControlsStateProvider provider = mBrowserControlsSupplier.get();
+        return provider != null ? provider.getContentOffset() / mDpToPx : 0.f;
+    }
+
+    /**
+     * @return The offset of the content from the bottom of the screen in dp.
+     */
+    private float getBottomContentOffsetDp() {
+        final BrowserControlsStateProvider provider = mBrowserControlsSupplier.get();
+        return provider != null ? BrowserControlsUtils.getBottomContentOffset(provider) / mDpToPx
+                                : 0.f;
+    }
+
     private PortraitViewport getViewportParameters() {
         if (isUsingHorizontalLayout()) {
             if (mCachedLandscapeViewport == null) {
@@ -1376,7 +1438,7 @@
         // status bar when switching to incognito mode.
         if (isHorizontalTabSwitcherFlagEnabled()) return getHeight();
 
-        float distance = isUsingHorizontalLayout() ? getHeightMinusBrowserControls() : getWidth();
+        float distance = isUsingHorizontalLayout() ? getHeightMinusContentOffsetsDp() : getWidth();
         if (mStacks.size() > 2) {
             return distance - getViewportParameters().getInnerMargin();
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/stack/OverlappingStack.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/stack/OverlappingStack.java
index 264cc57..a0905515 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/stack/OverlappingStack.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/stack/OverlappingStack.java
@@ -631,9 +631,9 @@
         // eventually when overview mode is visible. Hence, we pre-acknowledge the fact and assume
         // the bottom control height to be 0 here so that the animation is correctly set up.
         if (TabUiFeatureUtilities.isConditionalTabStripEnabled()) {
-            return mLayout.getHeight() - mLayout.getTopBrowserControlsHeight();
+            return mLayout.getHeight() - mLayout.getTopContentOffsetDp();
         }
-        return mLayout.getHeightMinusBrowserControls();
+        return mLayout.getHeightMinusContentOffsetsDp();
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/stack/Stack.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/stack/Stack.java
index 9857628..7cb8c7c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/stack/Stack.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/stack/Stack.java
@@ -1231,7 +1231,7 @@
     }
 
     protected float getScrollDimensionSize() {
-        return mCurrentMode == Orientation.PORTRAIT ? mLayout.getHeightMinusBrowserControls()
+        return mCurrentMode == Orientation.PORTRAIT ? mLayout.getHeightMinusContentOffsetsDp()
                                                     : mLayout.getWidth();
     }
 
@@ -1494,7 +1494,7 @@
             // Resolve bottom stacking
             stackedCount = 0;
             float maxStackedPosition =
-                    portrait ? mLayout.getHeightMinusBrowserControls() : mLayout.getWidth();
+                    portrait ? mLayout.getHeightMinusContentOffsetsDp() : mLayout.getWidth();
             for (int i = mStackTabs.length - 1; i >= 0; i--) {
                 assert mStackTabs[i] != null;
                 StackTab stackTab = mStackTabs[i];
@@ -1812,7 +1812,7 @@
     private float getStackScale(RectF stackRect) {
         return mCurrentMode == Orientation.PORTRAIT
                 ? stackRect.width() / mLayout.getWidth()
-                : stackRect.height() / mLayout.getHeightMinusBrowserControls();
+                : stackRect.height() / mLayout.getHeightMinusContentOffsetsDp();
     }
 
     protected void setScrollTarget(float offset, boolean immediate) {
@@ -1975,7 +1975,7 @@
     private float getRange(float range) {
         return range
                 * (mCurrentMode == Orientation.PORTRAIT ? mLayout.getWidth()
-                                                        : mLayout.getHeightMinusBrowserControls());
+                                                        : mLayout.getHeightMinusContentOffsetsDp());
     }
 
     /**
@@ -2036,7 +2036,7 @@
         mDiscardDirection = getDefaultDiscardDirection();
         final float opaqueTopPadding = mBorderTopPadding - mBorderTransparentTop;
         mAnimationFactory = new StackAnimation(this, mLayout.getWidth(), mLayout.getHeight(),
-                mLayout.getTopBrowserControlsHeight(), mBorderTopPadding, opaqueTopPadding,
+                mLayout.getTopContentOffsetDp(), mBorderTopPadding, opaqueTopPadding,
                 mBorderLeftPadding, mCurrentMode);
         mViewAnimationFactory = new StackViewAnimation(mLayout.getContext().getResources());
         if (mStackTabs == null) return;
@@ -2124,7 +2124,7 @@
     public void swipeUpdated(long time, float x, float y, float dx, float dy, float tx, float ty) {
         if (!mInSwipe) return;
 
-        final float toolbarSize = mLayout.getTopBrowserControlsHeight();
+        final float toolbarSize = mLayout.getTopContentOffsetDp();
         if (ty > toolbarSize) mSwipeCanScroll = true;
         if (!mSwipeCanScroll) return;
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchIPH.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchIPH.java
index d0606e9..19907bd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchIPH.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchIPH.java
@@ -20,12 +20,14 @@
 import org.chromium.components.feature_engagement.FeatureConstants;
 import org.chromium.components.feature_engagement.Tracker;
 import org.chromium.components.feature_engagement.TriggerState;
+import org.chromium.ui.widget.AnchoredPopupWindow;
 import org.chromium.ui.widget.RectProvider;
 
 /**
  * Helper class for displaying In-Product Help UI for Contextual Search.
  */
 public class ContextualSearchIPH {
+    private static final int FLOATING_BUBBLE_SPACING_FACTOR = 10;
     private View mParentView;
     private ContextualSearchPanel mSearchPanel;
     private TextBubble mHelpBubble;
@@ -174,6 +176,7 @@
             mDismissListener = null;
         }
 
+        maybeSetPreferredOrientation();
         mHelpBubble.show();
         mIsShowing = true;
     }
@@ -194,11 +197,13 @@
         int yInsetPx = mParentView.getResources().getDimensionPixelOffset(
                 R.dimen.contextual_search_bubble_y_inset);
         if (!mIsPositionedByPanel) {
-            // Position the bubble to point to the tap location, since there's no panel, just a
-            // selected word.  It would be better to point to the rectangle of the selected word,
-            // but that's not easy to get.
-            return new Rect(mFloatingBubbleAnchorPoint.x, mFloatingBubbleAnchorPoint.y,
-                    mFloatingBubbleAnchorPoint.x, mFloatingBubbleAnchorPoint.y);
+            // Position the bubble to point to an adjusted tap location, since there's no panel,
+            // just a selected word.  It would be better to point to the rectangle of the selected
+            // word, but that's not easy to get.
+            int adjustFactor = shouldPositionBubbleBelowArrow() ? -1 : 1;
+            int yAdjust = FLOATING_BUBBLE_SPACING_FACTOR * yInsetPx * adjustFactor;
+            return new Rect(mFloatingBubbleAnchorPoint.x, mFloatingBubbleAnchorPoint.y + yAdjust,
+                    mFloatingBubbleAnchorPoint.x, mFloatingBubbleAnchorPoint.y + yAdjust);
         }
 
         Rect anchorRect = mSearchPanel.getPanelRect();
@@ -206,6 +211,22 @@
         return anchorRect;
     }
 
+    /** Overrides the preferred orientation if the bubble is not anchored to the panel. */
+    private void maybeSetPreferredOrientation() {
+        if (mIsPositionedByPanel) return;
+
+        mHelpBubble.setPreferredVerticalOrientation(shouldPositionBubbleBelowArrow()
+                        ? AnchoredPopupWindow.VerticalOrientation.BELOW
+                        : AnchoredPopupWindow.VerticalOrientation.ABOVE);
+    }
+
+    /** @return whether the bubble should be positioned below it's arrow pointer. */
+    private boolean shouldPositionBubbleBelowArrow() {
+        // The bubble looks best when above the arrow, so we use that for most of the screen,
+        // but needs to appear below the arrow near the top.
+        return mFloatingBubbleAnchorPoint.y < mParentView.getHeight() / 3;
+    }
+
     /**
      * Dismisses the In-Product Help UI.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/dependency_injection/ChromeActivityCommonsModule.java b/chrome/android/java/src/org/chromium/chrome/browser/dependency_injection/ChromeActivityCommonsModule.java
index 40acfdc..7c1e4f8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/dependency_injection/ChromeActivityCommonsModule.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/dependency_injection/ChromeActivityCommonsModule.java
@@ -30,6 +30,7 @@
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.chrome.browser.ui.system.StatusBarColorController;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetControllerProvider;
 import org.chromium.components.browser_ui.notifications.NotificationManagerProxy;
 import org.chromium.components.browser_ui.notifications.NotificationManagerProxyImpl;
 import org.chromium.content_public.browser.ScreenOrientationProvider;
@@ -62,10 +63,7 @@
 
     @Provides
     public BottomSheetController provideBottomSheetController() {
-        // Once the BottomSheetController is in the dependency graph, this method would no longer
-        // be necessary, as well as the getter in ChromeActivity. Same is true for a few other
-        // methods below.
-        return mActivity.getBottomSheetController();
+        return BottomSheetControllerProvider.from(mActivity.getWindowAndroid());
     }
 
     @Provides
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java
index 8166dee..a1d8fc8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java
@@ -34,6 +34,7 @@
 import org.chromium.chrome.browser.infobar.InfoBarIdentifier;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetControllerProvider;
 import org.chromium.components.download.DownloadState;
 import org.chromium.components.feature_engagement.FeatureConstants;
 import org.chromium.components.infobars.InfoBar;
@@ -410,7 +411,9 @@
     public IPHInfoBarSupport.TrackerParameters getTrackerParameters() {
         if (getDownloadCount().inProgress == 0) return null;
 
-        if (getActivity() == null || getActivity().getBottomSheetController().isSheetOpen()) {
+        if (getActivity() == null
+                || BottomSheetControllerProvider.from(getActivity().getWindowAndroid())
+                           .isSheetOpen()) {
             return null;
         }
 
@@ -1065,6 +1068,7 @@
                 DownloadLaterUiEvent.DOWNLOAD_INFOBAR_CHANGE_SCHEDULE_CLICKED);
         mDownloadLaterDialogHelper.showChangeScheduleDialog(
                 currentSchedule, Source.DOWNLOAD_INFOBAR, (newSchedule) -> {
+                    if (newSchedule == null) return;
                     if (mUseNewDownloadPath) {
                         OfflineContentAggregatorFactory.get().changeSchedule(id, newSchedule);
                     } else {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java
index 9c7fd7f..7458355 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java
@@ -46,6 +46,7 @@
 import org.chromium.chrome.browser.preferences.Pref;
 import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.profiles.ProfileKey;
 import org.chromium.chrome.browser.profiles.ProfileManager;
 import org.chromium.components.browser_ui.util.ConversionUtils;
 import org.chromium.components.download.DownloadCollectionBridge;
@@ -297,6 +298,14 @@
         mSharedPrefs.removeKey(ChromePreferenceKeys.DOWNLOAD_UMA_ENTRY);
     }
 
+    // TODO(https://crbug.com/1060940): Remove this function and update all use cases so that
+    // the profile would be available instead of isOffTheRecord boolean.
+    private static ProfileKey getProfileKey(boolean isOffTheRecord) {
+        Profile profile = Profile.getLastUsedRegularProfile();
+        if (isOffTheRecord) profile = profile.getPrimaryOTRProfile();
+        return profile.getProfileKey();
+    }
+
     /**
      * Initializes download related systems for background task.
      */
@@ -998,8 +1007,8 @@
             incrementDownloadRetryCount(item.getId(), false);
         }
         DownloadManagerServiceJni.get().resumeDownload(getNativeDownloadManagerService(),
-                DownloadManagerService.this, item.getId(), item.getDownloadInfo().isOffTheRecord(),
-                hasUserGesture);
+                DownloadManagerService.this, item.getId(),
+                getProfileKey(item.getDownloadInfo().isOffTheRecord()), hasUserGesture);
     }
 
     /**
@@ -1010,8 +1019,8 @@
      */
     public void retryDownload(ContentId id, DownloadItem item, boolean hasUserGesture) {
         DownloadManagerServiceJni.get().retryDownload(getNativeDownloadManagerService(),
-                DownloadManagerService.this, item.getId(), item.getDownloadInfo().isOffTheRecord(),
-                hasUserGesture);
+                DownloadManagerService.this, item.getId(),
+                getProfileKey(item.getDownloadInfo().isOffTheRecord()), hasUserGesture);
     }
 
     /**
@@ -1022,7 +1031,7 @@
     @Override
     public void cancelDownload(ContentId id, boolean isOffTheRecord) {
         DownloadManagerServiceJni.get().cancelDownload(getNativeDownloadManagerService(),
-                DownloadManagerService.this, id.id, isOffTheRecord);
+                DownloadManagerService.this, id.id, getProfileKey(isOffTheRecord));
         DownloadProgress progress = mDownloadProgressMap.get(id.id);
         if (progress != null) {
             DownloadInfo info =
@@ -1046,7 +1055,7 @@
     @Override
     public void pauseDownload(ContentId id, boolean isOffTheRecord) {
         DownloadManagerServiceJni.get().pauseDownload(getNativeDownloadManagerService(),
-                DownloadManagerService.this, id.id, isOffTheRecord);
+                DownloadManagerService.this, id.id, getProfileKey(isOffTheRecord));
         DownloadProgress progress = mDownloadProgressMap.get(id.id);
         // Calling pause will stop listening to the download item. Update its progress now.
         // If download is already completed, canceled or failed, there is no need to update the
@@ -1076,7 +1085,7 @@
             final String downloadGuid, boolean isOffTheRecord, boolean externallyRemoved) {
         mHandler.post(() -> {
             DownloadManagerServiceJni.get().removeDownload(getNativeDownloadManagerService(),
-                    DownloadManagerService.this, downloadGuid, isOffTheRecord);
+                    DownloadManagerService.this, downloadGuid, getProfileKey(isOffTheRecord));
             removeDownloadProgress(downloadGuid);
         });
 
@@ -1125,7 +1134,7 @@
     public void onProfileAdded(Profile profile) {
         ProfileManager.removeObserver(this);
         DownloadManagerServiceJni.get().onProfileAdded(
-                mNativeDownloadManagerService, DownloadManagerService.this);
+                mNativeDownloadManagerService, DownloadManagerService.this, profile);
     }
 
     @Override
@@ -1359,8 +1368,8 @@
      * @param isOffTheRecord Whether or not to get downloads for the off the record profile.
      */
     public void getAllDownloads(boolean isOffTheRecord) {
-        DownloadManagerServiceJni.get().getAllDownloads(
-                getNativeDownloadManagerService(), DownloadManagerService.this, isOffTheRecord);
+        DownloadManagerServiceJni.get().getAllDownloads(getNativeDownloadManagerService(),
+                DownloadManagerService.this, getProfileKey(isOffTheRecord));
     }
 
     /**
@@ -1379,7 +1388,7 @@
     public void renameDownload(ContentId id, String name,
             Callback<Integer /*RenameResult*/> callback, boolean isOffTheRecord) {
         DownloadManagerServiceJni.get().renameDownload(getNativeDownloadManagerService(),
-                DownloadManagerService.this, id.id, name, callback, isOffTheRecord);
+                DownloadManagerService.this, id.id, name, callback, getProfileKey(isOffTheRecord));
     }
 
     /**
@@ -1393,7 +1402,8 @@
         boolean onlyOnWifi = (schedule == null) ? false : schedule.onlyOnWifi;
         long startTimeMs = (schedule == null) ? -1 : schedule.startTimeMs;
         DownloadManagerServiceJni.get().changeSchedule(getNativeDownloadManagerService(),
-                DownloadManagerService.this, id.id, onlyOnWifi, startTimeMs, isOffTheRecord);
+                DownloadManagerService.this, id.id, onlyOnWifi, startTimeMs,
+                getProfileKey(isOffTheRecord));
     }
 
     /**
@@ -1427,7 +1437,8 @@
      */
     public void checkForExternallyRemovedDownloads(boolean isOffTheRecord) {
         DownloadManagerServiceJni.get().checkForExternallyRemovedDownloads(
-                getNativeDownloadManagerService(), DownloadManagerService.this, isOffTheRecord);
+                getNativeDownloadManagerService(), DownloadManagerService.this,
+                getProfileKey(isOffTheRecord));
     }
 
     @CalledByNative
@@ -1441,7 +1452,10 @@
     }
 
     @CalledByNative
-    private void onAllDownloadsRetrieved(final List<DownloadItem> list, boolean isOffTheRecord) {
+    private void onAllDownloadsRetrieved(final List<DownloadItem> list, ProfileKey profileKey) {
+        // TODO(https://crbug.com/1099577): Pass the profileKey/profile to adapter instead of the
+        // boolean.
+        boolean isOffTheRecord = profileKey.isOffTheRecord();
         for (DownloadObserver adapter : mDownloadObservers) {
             adapter.onAllDownloadsRetrieved(list, isOffTheRecord);
         }
@@ -1566,7 +1580,7 @@
      */
     public void openDownload(ContentId id, boolean isOffTheRecord, @DownloadOpenSource int source) {
         DownloadManagerServiceJni.get().openDownload(getNativeDownloadManagerService(),
-                DownloadManagerService.this, id.id, isOffTheRecord, source);
+                DownloadManagerService.this, id.id, getProfileKey(isOffTheRecord), source);
     }
 
     /**
@@ -1794,7 +1808,7 @@
         if (TextUtils.isEmpty(downloadGuid)) return;
 
         DownloadManagerServiceJni.get().updateLastAccessTime(getNativeDownloadManagerService(),
-                DownloadManagerService.this, downloadGuid, isOffTheRecord);
+                DownloadManagerService.this, downloadGuid, getProfileKey(isOffTheRecord));
     }
 
     @Override
@@ -1818,29 +1832,30 @@
         int getAutoResumptionLimit();
         long init(DownloadManagerService caller, boolean isProfileAdded);
         void openDownload(long nativeDownloadManagerService, DownloadManagerService caller,
-                String downloadGuid, boolean isOffTheRecord, int source);
+                String downloadGuid, ProfileKey profileKey, int source);
         void resumeDownload(long nativeDownloadManagerService, DownloadManagerService caller,
-                String downloadGuid, boolean isOffTheRecord, boolean hasUserGesture);
+                String downloadGuid, ProfileKey profileKey, boolean hasUserGesture);
         void retryDownload(long nativeDownloadManagerService, DownloadManagerService caller,
-                String downloadGuid, boolean isOffTheRecord, boolean hasUserGesture);
+                String downloadGuid, ProfileKey profileKey, boolean hasUserGesture);
         void cancelDownload(long nativeDownloadManagerService, DownloadManagerService caller,
-                String downloadGuid, boolean isOffTheRecord);
+                String downloadGuid, ProfileKey profileKey);
         void pauseDownload(long nativeDownloadManagerService, DownloadManagerService caller,
-                String downloadGuid, boolean isOffTheRecord);
+                String downloadGuid, ProfileKey profileKey);
         void removeDownload(long nativeDownloadManagerService, DownloadManagerService caller,
-                String downloadGuid, boolean isOffTheRecord);
+                String downloadGuid, ProfileKey profileKey);
         void renameDownload(long nativeDownloadManagerService, DownloadManagerService caller,
                 String downloadGuid, String targetName, Callback</*RenameResult*/ Integer> callback,
-                boolean isOffTheRecord);
+                ProfileKey profileKey);
         void changeSchedule(long nativeDownloadManagerService, DownloadManagerService caller,
-                String downloadGuid, boolean onlyOnWifi, long startTimeMs, boolean isOffTheRecord);
+                String downloadGuid, boolean onlyOnWifi, long startTimeMs, ProfileKey profileKey);
         void getAllDownloads(long nativeDownloadManagerService, DownloadManagerService caller,
-                boolean isOffTheRecord);
+                ProfileKey profileKey);
         void checkForExternallyRemovedDownloads(long nativeDownloadManagerService,
-                DownloadManagerService caller, boolean isOffTheRecord);
+                DownloadManagerService caller, ProfileKey profileKey);
         void updateLastAccessTime(long nativeDownloadManagerService, DownloadManagerService caller,
-                String downloadGuid, boolean isOffTheRecord);
-        void onProfileAdded(long nativeDownloadManagerService, DownloadManagerService caller);
+                String downloadGuid, ProfileKey profileKey);
+        void onProfileAdded(
+                long nativeDownloadManagerService, DownloadManagerService caller, Profile profile);
         void createInterruptedDownloadForTest(long nativeDownloadManagerService,
                 DownloadManagerService caller, String url, String guid, String targetPath);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMediator.java
index ad2824f..f7efaf2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMediator.java
@@ -353,8 +353,11 @@
         UmaUtils.recordItemAction(ViewAction.MENU_CHANGE);
         DownloadLaterMetrics.recordDownloadLaterUiEvent(
                 DownloadLaterUiEvent.DOWNLOAD_HOME_CHANGE_SCHEDULE_CLICKED);
-        mDownloadLaterDialogHelper.showChangeScheduleDialog(item.schedule, Source.DOWNLOAD_HOME,
-                (newSchedule) -> { mProvider.changeSchedule(item, newSchedule); });
+        mDownloadLaterDialogHelper.showChangeScheduleDialog(
+                item.schedule, Source.DOWNLOAD_HOME, (newSchedule) -> {
+                    if (newSchedule == null) return;
+                    mProvider.changeSchedule(item, newSchedule);
+                });
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegateImpl.java
index ff968bb..a0a441a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegateImpl.java
@@ -38,7 +38,6 @@
 import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResult;
 import org.chromium.components.external_intents.ExternalNavigationParams;
 import org.chromium.components.external_intents.RedirectHandler;
-import org.chromium.components.webapk.lib.client.WebApkValidator;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.base.WindowAndroid;
@@ -336,11 +335,6 @@
     }
 
     @Override
-    public boolean isValidWebApk(String packageName) {
-        return WebApkValidator.isValidWebApk(ContextUtils.getApplicationContext(), packageName);
-    }
-
-    @Override
     public boolean handleWithAutofillAssistant(ExternalNavigationParams params, Intent targetIntent,
             String browserFallbackUrl, boolean isGoogleReferrer) {
         if (browserFallbackUrl != null && !params.isIncognito()
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/feedback/ScreenshotTask.java b/chrome/android/java/src/org/chromium/chrome/browser/feedback/ScreenshotTask.java
index 48082a0..10e8e5e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/feedback/ScreenshotTask.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/feedback/ScreenshotTask.java
@@ -20,6 +20,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tasks.tab_management.TabUiFeatureUtilities;
 import org.chromium.chrome.features.start_surface.StartSurfaceConfiguration;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetControllerProvider;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.ui.UiUtils;
 import org.chromium.ui.base.WindowAndroid;
@@ -131,7 +132,9 @@
         // so that the Android View for the bottom sheet will be captured.
         // TODO(https://crbug.com/835862): When the sheet is partially opened both the compositor
         // and Android views should be captured in the screenshot.
-        if (chromeActivity.getBottomSheetController().isSheetOpen()) return false;
+        if (BottomSheetControllerProvider.from(chromeActivity.getWindowAndroid()).isSheetOpen()) {
+            return false;
+        }
 
         // If the start surface or the grid tab switcher are in use, do not use the compositor, it
         // will snapshot the last active tab instead of the current screen if we try to use it.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/NavigationHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/NavigationHandler.java
index d4a7cf14..893f464 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/NavigationHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/NavigationHandler.java
@@ -185,7 +185,7 @@
             if (mState == GestureState.DRAGGED && mSideSlideLayout != null) {
                 mSideSlideLayout.release(mNavigationSheet.isHidden());
                 mNavigationSheet.release();
-            } else if (mState == GestureState.GLOW && mGlowEffectSupplier != null) {
+            } else if (mState == GestureState.GLOW && mGlowEffectSupplier.get() != null) {
                 mGlowEffectSupplier.get().release();
             }
         }
@@ -270,7 +270,7 @@
      */
     private void showGlow(float startX, float startY) {
         if (mState != GestureState.STARTED) reset();
-        mGlowEffectSupplier.get().prepare(startX, startY);
+        if (mGlowEffectSupplier.get() != null) mGlowEffectSupplier.get().prepare(startX, startY);
         mState = GestureState.GLOW;
     }
 
@@ -309,7 +309,7 @@
                 mState = GestureState.NONE;
             }
         } else if (mState == GestureState.GLOW) {
-            mGlowEffectSupplier.get().onScroll(-delta);
+            if (mGlowEffectSupplier.get() != null) mGlowEffectSupplier.get().onScroll(-delta);
         }
     }
 
@@ -340,7 +340,7 @@
             mSideSlideLayout.release(allowNav && mNavigationSheet.isHidden());
             mNavigationSheet.release();
         } else if (mState == GestureState.GLOW) {
-            mGlowEffectSupplier.get().release();
+            if (mGlowEffectSupplier.get() != null) mGlowEffectSupplier.get().release();
         }
     }
 
@@ -352,7 +352,7 @@
             cancelStopNavigatingRunnable();
             mSideSlideLayout.reset();
         } else if (mState == GestureState.GLOW) {
-            mGlowEffectSupplier.get().reset();
+            if (mGlowEffectSupplier.get() != null) mGlowEffectSupplier.get().reset();
         }
         mState = GestureState.NONE;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarContainer.java b/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarContainer.java
index 783ea89..590639f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarContainer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarContainer.java
@@ -22,6 +22,7 @@
 import org.chromium.chrome.browser.tab.TabObserver;
 import org.chromium.chrome.browser.util.ChromeAccessibilityUtil;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetControllerProvider;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetObserver;
 import org.chromium.components.browser_ui.bottomsheet.EmptyBottomSheetObserver;
 import org.chromium.components.infobars.InfoBar;
@@ -194,6 +195,9 @@
     /** A {@link BottomSheetObserver} so this view knows when to show/hide. */
     private @Nullable BottomSheetObserver mBottomSheetObserver;
 
+    /** */
+    private BottomSheetController mBottomSheetController;
+
     public static InfoBarContainer from(Tab tab) {
         InfoBarContainer container = get(tab);
         if (container == null) {
@@ -465,8 +469,9 @@
                                                     : View.VISIBLE);
                                 }
                             };
-                            getActivity(mTab).getBottomSheetController().addObserver(
-                                    mBottomSheetObserver);
+                            mBottomSheetController =
+                                    BottomSheetControllerProvider.from(mTab.getWindowAndroid());
+                            mBottomSheetController.addObserver(mBottomSheetObserver);
                         }
 
                         for (InfoBarContainer.InfoBarContainerObserver observer : mObservers) {
@@ -507,7 +512,7 @@
 
         ChromeActivity activity = getActivity(mTab);
         if (activity != null && mBottomSheetObserver != null) {
-            activity.getBottomSheetController().removeObserver(mBottomSheetObserver);
+            mBottomSheetController.removeObserver(mBottomSheetObserver);
         }
 
         mTab.getWindowAndroid().getKeyboardDelegate().removeKeyboardVisibilityListener(this);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManager.java b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManager.java
index b418c50..81c8032 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManager.java
@@ -20,6 +20,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import org.chromium.base.ActivityState;
+import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.CommandLine;
 import org.chromium.base.metrics.RecordUserAction;
@@ -85,6 +86,7 @@
     private boolean mShouldMergeOnConfigurationChange;
     private boolean mIsRecreating;
     private int mDisplayId;
+    private static List<Integer> sTestDisplayIds;
 
     /**
      * Create a new {@link MultiInstanceManager}.
@@ -220,7 +222,13 @@
 
             @Override
             public void onDisplayChanged(int displayId) {
-                // TODO(crbug.com/824954): try to merge tabs sharing logic w/ onDisplayRemoved
+                if (displayId == mDisplayId) return;
+                List<Integer> ids = sTestDisplayIds != null
+                    ? sTestDisplayIds
+                    : ApiCompatibilityUtils.getTargetableDisplayIds(mActivity);
+                if (ids.size() == 1 && ids.get(0).equals(mDisplayId)) {
+                    maybeMergeTabs();
+                }
             }
         };
         displayManager.registerDisplayListener(mDisplayListener, null);
@@ -433,4 +441,9 @@
     public DisplayManager.DisplayListener getDisplayListenerForTesting() {
         return mDisplayListener;
     }
+
+    @VisibleForTesting
+    public static void setTestDisplayIds(List<Integer> testDisplayIds) {
+        sTestDisplayIds = testDisplayIds;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java
index 11f1bbc..dfb4081 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java
@@ -32,6 +32,7 @@
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.ui.native_page.NativePage;
 import org.chromium.chrome.browser.ui.native_page.NativePageHost;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.content_public.browser.LoadUrlParams;
 
@@ -43,17 +44,20 @@
  */
 public class NativePageFactory {
     private final ChromeActivity mActivity;
+    private final BottomSheetController mBottomSheetController;
     private NewTabPageUma mNewTabPageUma;
 
     private NativePageBuilder mNativePageBuilder;
 
-    public NativePageFactory(ChromeActivity activity) {
+    public NativePageFactory(ChromeActivity activity, BottomSheetController sheetController) {
         mActivity = activity;
+        mBottomSheetController = sheetController;
     }
 
     private NativePageBuilder getBuilder() {
         if (mNativePageBuilder == null) {
-            mNativePageBuilder = new NativePageBuilder(mActivity, this::getNewTabPageUma);
+            mNativePageBuilder = new NativePageBuilder(
+                    mActivity, this::getNewTabPageUma, mBottomSheetController);
         }
         return mNativePageBuilder;
     }
@@ -71,11 +75,14 @@
     @VisibleForTesting
     static class NativePageBuilder {
         private final ChromeActivity mActivity;
+        private final BottomSheetController mBottomSheetController;
         private final Supplier<NewTabPageUma> mUma;
 
-        public NativePageBuilder(ChromeActivity activity, Supplier<NewTabPageUma> uma) {
+        public NativePageBuilder(ChromeActivity activity, Supplier<NewTabPageUma> uma,
+                BottomSheetController sheetController) {
             mActivity = activity;
             mUma = uma;
+            mBottomSheetController = sheetController;
         }
 
         protected NativePage buildNewTabPage(Tab tab) {
@@ -87,7 +94,7 @@
                     mActivity.getSnackbarManager(), mActivity.getLifecycleDispatcher(),
                     mActivity.getTabModelSelector(), mActivity.isTablet(), mUma.get(),
                     mActivity.getNightModeStateProvider().isInNightMode(), nativePageHost, tab,
-                    mActivity.getBottomSheetController());
+                    mBottomSheetController);
         }
 
         protected NativePage buildBookmarksPage(Tab tab) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationConstants.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationConstants.java
index fd388f8..9ae6c3e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationConstants.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationConstants.java
@@ -55,8 +55,9 @@
     @SuppressWarnings("unused")
     public static final int NOTIFICATION_ID_SIGNED_IN = 2;
     /**
-     * Unique identifier for the Physical Web notification.
+     * Unique identifier for the Physical Web notification. No longer used.
      */
+    @SuppressWarnings("unused")
     public static final int NOTIFICATION_ID_PHYSICAL_WEB = 3;
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/RecentTabsManager.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/RecentTabsManager.java
index b100ad1..ec490b1f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/RecentTabsManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/RecentTabsManager.java
@@ -105,7 +105,7 @@
         mRecentlyClosedTabManager = sRecentlyClosedTabManagerForTests != null
                 ? sRecentlyClosedTabManagerForTests
                 : new RecentlyClosedBridge(profile);
-        mSignInManager = IdentityServicesProvider.get().getSigninManager();
+        mSignInManager = IdentityServicesProvider.get().getSigninManager(mProfile);
 
         mProfileDataCache = ProfileDataCache.createProfileDataCache(context, 0);
         mSigninPromoController = new SigninPromoController(SigninAccessPoint.RECENT_TABS);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaBase.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaBase.java
index 7903ea8..70db4b5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaBase.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaBase.java
@@ -176,8 +176,14 @@
         // Since this update check is synchronous and blocking on the network
         // connection, it should not be run on the UI thread.
         assert !ThreadUtils.runningOnUiThread();
+        Log.i(TAG,
+                "OmahaBase::checkForUpdates(): Current version String: \"" + getInstalledVersion()
+                        + "\"");
         // This is not available on developer builds.
         if (getRequestGenerator() == null) {
+            Log.w(TAG,
+                    "OmahaBase::checkForUpdates(): Request generator is null. This is probably "
+                            + "a developer build.");
             return UpdateStatus.FAILED;
         }
         // Create all the metadata needed for an Omaha request.
@@ -193,10 +199,14 @@
         VersionConfig versionConfig = generateAndPostRequest(
                 currentTimestamp, sessionID, currentRequest, timestampOfInstall);
         if (versionConfig == null) {
+            Log.w(TAG, "OmahaBase::checkForUpdates(): versionConfig parsed from response is null.");
             return (mRequestErrorCode == RequestFailureException.ERROR_CONNECTIVITY)
                     ? UpdateStatus.OFFLINE
                     : UpdateStatus.FAILED;
         }
+        Log.i(TAG,
+                "OmahaBase::checkForUpdates(): Received latest version String from Omaha "
+                        + "server: \"" + versionConfig.latestVersion + "\"");
         // Compare the current version with the latest received from the server.
         VersionNumber current = VersionNumber.fromString(getInstalledVersion());
         VersionNumber latest = VersionNumber.fromString(versionConfig.latestVersion);
@@ -320,9 +330,13 @@
                     installAgeInDays,
                     mVersionConfig == null ? UNKNOWN_DATE : mVersionConfig.serverDate,
                     currentRequest);
+            Log.i(TAG, "OmahaBase::generateAndPostRequest(): Sending request to Omaha:\n" + xml);
 
             // Send the request to the server & wait for a response.
             String response = postRequest(currentTimestamp, xml);
+            Log.i(TAG,
+                    "OmahaBase::generateAndPostRequest(): Received response from Omaha:\n"
+                            + response);
 
             // Parse out the response.
             String appId = getRequestGenerator().getAppId();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
index 3aae201..70c67b2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
@@ -51,6 +51,7 @@
 import org.chromium.chrome.browser.ui.favicon.FaviconHelper;
 import org.chromium.components.autofill.Completable;
 import org.chromium.components.autofill.EditableOption;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetControllerProvider;
 import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.components.page_info.CertificateChainHelper;
 import org.chromium.components.payments.AbortReason;
@@ -1135,7 +1136,8 @@
         mPaymentUisShowStateReconciler.onBottomSheetShown();
 
         mMinimalUi = new MinimalUICoordinator();
-        if (mMinimalUi.show(chromeActivity, chromeActivity.getBottomSheetController(),
+        if (mMinimalUi.show(chromeActivity,
+                    BottomSheetControllerProvider.from(chromeActivity.getWindowAndroid()),
                     (PaymentApp) mPaymentMethodsSection.getSelectedItem(),
                     mCurrencyFormatterMap.get(mRawTotal.amount.currency),
                     mUiShoppingCart.getTotal(), this::onMinimalUIReady, this::onMinimalUiConfirmed,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/WebPaymentIntentHelperTypeConverter.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/WebPaymentIntentHelperTypeConverter.java
index 3cf4598..ac4abb0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/WebPaymentIntentHelperTypeConverter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/WebPaymentIntentHelperTypeConverter.java
@@ -111,7 +111,7 @@
             @Nullable PaymentShippingOption shippingOption) {
         if (shippingOption == null) return null;
         return new WebPaymentIntentHelperType.PaymentShippingOption(shippingOption.id,
-                shippingOption.label, shippingOption.amount.currency, shippingOption.amount.value,
+                shippingOption.label, fromMojoPaymentCurrencyAmount(shippingOption.amount),
                 shippingOption.selected);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/handler/PaymentHandlerCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/handler/PaymentHandlerCoordinator.java
index 5e15eaa..e4ff2b0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/handler/PaymentHandlerCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/handler/PaymentHandlerCoordinator.java
@@ -17,6 +17,7 @@
 import org.chromium.chrome.browser.thinwebview.ThinWebViewConstraints;
 import org.chromium.chrome.browser.thinwebview.ThinWebViewFactory;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetControllerProvider;
 import org.chromium.components.browser_ui.styles.R;
 import org.chromium.components.embedder_support.view.ContentView;
 import org.chromium.components.payments.PaymentFeatureList;
@@ -91,9 +92,11 @@
                 mWebContents, uiObserver, activity.getActivityTab().getView(),
                 mToolbarCoordinator.getToolbarHeightPx(),
                 calculateBottomSheetToolbarContainerTopPadding(activity),
-                activity.getLifecycleDispatcher());
+                activity.getLifecycleDispatcher(),
+                BottomSheetControllerProvider.from(activity.getWindowAndroid()));
         activity.getWindow().getDecorView().addOnLayoutChangeListener(mediator);
-        BottomSheetController bottomSheetController = activity.getBottomSheetController();
+        BottomSheetController bottomSheetController =
+                BottomSheetControllerProvider.from(activity.getWindowAndroid());
         bottomSheetController.addObserver(mediator);
         mWebContents.addObserver(mediator);
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/handler/PaymentHandlerMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/handler/PaymentHandlerMediator.java
index 0b5b2de..7612fbe 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/handler/PaymentHandlerMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/handler/PaymentHandlerMediator.java
@@ -58,6 +58,7 @@
     private final Destroyable mActivityDestroyListener;
     private final ActivityLifecycleDispatcher mActivityLifecycleDispatcher;
     private final View mTabView;
+    private final BottomSheetController mBottomSheetController;
     private final int mToolbarViewHeightPx;
     private final int mContainerTopPaddingPx;
     private @CloseReason int mCloseReason = CloseReason.OTHERS;
@@ -89,14 +90,17 @@
      * @param containerTopPaddingPx The padding top of bottom_sheet_toolbar_container in px
      * @param activityLifeCycleDispatcher The lifecycle dispatcher of the activity where this UI
      *         lives.
+     * @param sheetController A {@link BottomSheetController} to show UI in.
      */
     /* package */ PaymentHandlerMediator(PropertyModel model, Runnable hider,
             WebContents webContents, PaymentHandlerUiObserver observer, View tabView,
             int toolbarViewHeightPx, int containerTopPaddingPx,
-            ActivityLifecycleDispatcher activityLifeCycleDispatcher) {
+            ActivityLifecycleDispatcher activityLifeCycleDispatcher,
+            BottomSheetController sheetController) {
         super(webContents);
         assert webContents != null;
         mTabView = tabView;
+        mBottomSheetController = sheetController;
         mWebContentsRef = webContents;
         mToolbarViewHeightPx = toolbarViewHeightPx;
         mModel = model;
@@ -170,9 +174,8 @@
         ChromeActivity activity = ChromeActivity.fromWebContents(mWebContentsRef);
         assert activity != null;
 
-        BottomSheetController controller = activity.getBottomSheetController();
-        PropertyModel params = controller.createScrimParams();
-        ScrimCoordinator coordinator = controller.getScrimCoordinator();
+        PropertyModel params = mBottomSheetController.createScrimParams();
+        ScrimCoordinator coordinator = mBottomSheetController.getScrimCoordinator();
         coordinator.showScrim(params);
 
         setIsObscuringAllTabs(activity, true);
@@ -238,7 +241,7 @@
 
         setIsObscuringAllTabs(activity, false);
 
-        ScrimCoordinator coordinator = activity.getBottomSheetController().getScrimCoordinator();
+        ScrimCoordinator coordinator = mBottomSheetController.getScrimCoordinator();
         if (coordinator == null) return;
         coordinator.hideScrim(/*animate=*/true);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckOmahaClient.java b/chrome/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckOmahaClient.java
deleted file mode 100644
index e07cb1e1..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckOmahaClient.java
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2020 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.safety_check;
-
-import android.content.Context;
-
-import org.chromium.base.Callback;
-import org.chromium.base.task.PostTask;
-import org.chromium.base.task.TaskTraits;
-import org.chromium.chrome.browser.omaha.OmahaBase.UpdateStatus;
-import org.chromium.chrome.browser.omaha.OmahaService;
-import org.chromium.chrome.browser.safety_check.SafetyCheckModel.Updates;
-import org.chromium.content_public.browser.UiThreadTaskTraits;
-
-/**
- * Glue code for interactions between Safety check and Omaha on Android.
- *
- * This class is needed because {@link OmahaService} is in //chrome/android,
- * while Safety check is modularized in //chrome/browser. Once Omaha is
- * modularized as well, this class will not be needed anymore.
- */
-public class SafetyCheckOmahaClient implements SafetyCheckUpdatesDelegate {
-    private OmahaService mOmaha;
-
-    /**
-     * Creates a new instance of the glue class to be passed to
-     * {@link SafetyCheckSettingsFragment}.
-     * @param context A {@link Context} object, used by Omaha.
-     */
-    public SafetyCheckOmahaClient(Context context) {
-        mOmaha = OmahaService.getInstance(context);
-    }
-
-    /**
-     * Converts Omaha's {@link UpdateStatus} into a
-     * {@link SafetyCheckModel.Updates} enum value.
-     * @param status Update status returned by Omaha.
-     * @return A corresponding {@link SafetyCheckModel.Updates} value.
-     */
-    public static Updates convertOmahaUpdateStatus(@UpdateStatus int status) {
-        switch (status) {
-            case UpdateStatus.UPDATED:
-                return Updates.UPDATED;
-            case UpdateStatus.OUTDATED:
-                return Updates.OUTDATED;
-            case UpdateStatus.OFFLINE:
-                return Updates.OFFLINE;
-            case UpdateStatus.FAILED: // Intentional fall through.
-            default:
-                return Updates.ERROR;
-        }
-    }
-
-    /**
-     * Assynchronously checks for updates and invokes the provided callback with
-     * the result.
-     * @param statusCallback A callback to invoke with the result.
-     */
-    @Override
-    public void checkForUpdates(Callback<Updates> statusCallback) {
-        PostTask.postTask(TaskTraits.USER_VISIBLE, () -> {
-            @UpdateStatus
-            int status = mOmaha.checkForUpdates();
-            // Post the results back to the UI thread.
-            PostTask.postTask(UiThreadTaskTraits.DEFAULT,
-                    () -> { statusCallback.onResult(convertOmahaUpdateStatus(status)); });
-        });
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckUpdatesDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckUpdatesDelegateImpl.java
new file mode 100644
index 0000000..ebccbbb
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckUpdatesDelegateImpl.java
@@ -0,0 +1,77 @@
+// Copyright 2020 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.safety_check;
+
+import android.content.Context;
+
+import org.chromium.base.Callback;
+import org.chromium.base.task.PostTask;
+import org.chromium.base.task.TaskTraits;
+import org.chromium.chrome.browser.omaha.OmahaBase.UpdateStatus;
+import org.chromium.chrome.browser.omaha.OmahaService;
+import org.chromium.chrome.browser.safety_check.SafetyCheckProperties.UpdatesState;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Glue code for interactions between Safety check and Omaha on Android.
+ *
+ * This class is needed because {@link OmahaService} is in //chrome/android,
+ * while Safety check is modularized in //chrome/browser. Once Omaha is
+ * modularized as well, this class will not be needed anymore.
+ */
+public class SafetyCheckUpdatesDelegateImpl implements SafetyCheckUpdatesDelegate {
+    private OmahaService mOmaha;
+
+    /**
+     * Creates a new instance of the glue class to be passed to
+     * {@link SafetyCheckSettingsFragment}.
+     * @param context A {@link Context} object, used by Omaha.
+     */
+    public SafetyCheckUpdatesDelegateImpl(Context context) {
+        mOmaha = OmahaService.getInstance(context);
+    }
+
+    /**
+     * Converts Omaha's {@link UpdateStatus} into a
+     * {@link SafetyCheckModel.Updates} enum value.
+     * @param status Update status returned by Omaha.
+     * @return A corresponding {@link SafetyCheckModel.Updates} value.
+     */
+    public static @UpdatesState int convertOmahaUpdateStatus(@UpdateStatus int status) {
+        switch (status) {
+            case UpdateStatus.UPDATED:
+                return UpdatesState.UPDATED;
+            case UpdateStatus.OUTDATED:
+                return UpdatesState.OUTDATED;
+            case UpdateStatus.OFFLINE:
+                return UpdatesState.OFFLINE;
+            case UpdateStatus.FAILED: // Intentional fall through.
+            default:
+                return UpdatesState.ERROR;
+        }
+    }
+
+    /**
+     * Asynchronously checks for updates and invokes the provided callback with
+     * the result.
+     * @param statusCallback A callback to invoke with the result. Takes an element of
+     *                       {@link SafetyCheckProperties.UpdatesState} as an argument.
+     */
+    @Override
+    public void checkForUpdates(WeakReference<Callback<Integer>> statusCallback) {
+        PostTask.postTask(TaskTraits.USER_VISIBLE, () -> {
+            @UpdateStatus
+            int status = mOmaha.checkForUpdates();
+            // Post the results back to the UI thread.
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
+                if (statusCallback.get() != null) {
+                    statusCallback.get().onResult(convertOmahaUpdateStatus(status));
+                }
+            });
+        });
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/screenshot/EditorScreenshotTask.java b/chrome/android/java/src/org/chromium/chrome/browser/screenshot/EditorScreenshotTask.java
index 5b336fce..e643044 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/screenshot/EditorScreenshotTask.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/screenshot/EditorScreenshotTask.java
@@ -18,6 +18,7 @@
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.tab.SadTab;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.ui.UiUtils;
 import org.chromium.ui.base.WindowAndroid;
@@ -30,6 +31,7 @@
 @JNINamespace("chrome::android")
 public final class EditorScreenshotTask implements EditorScreenshotSource {
     private final Activity mActivity;
+    private final BottomSheetController mBottomSheetController;
 
     private boolean mDone;
     private Bitmap mBitmap;
@@ -39,9 +41,11 @@
      * Creates a {@link EditorScreenshotTask} instance that, will grab a screenshot of {@code
      * activity}.
      * @param activity The {@link Activity} to grab a screenshot of.
+     * @param sheetController A {@link BottomSheetController} to check if the sheet is showing.
      */
-    public EditorScreenshotTask(Activity activity) {
+    public EditorScreenshotTask(Activity activity, BottomSheetController sheetController) {
         mActivity = activity;
+        mBottomSheetController = sheetController;
     }
 
     // ScreenshotSource implementation.
@@ -126,7 +130,7 @@
         // so that the Android View for the bottom sheet will be captured.
         // TODO(https://crbug.com/835862): When the sheet is partially opened both the compositor
         // and Android views should be captured in the screenshot.
-        if (chromeActivity.getBottomSheetController().isSheetOpen()) return false;
+        if (mBottomSheetController.isSheetOpen()) return false;
 
         // If the tab is null, assume in the tab switcher so a Compositor snapshot is good.
         if (currentTab == null) return true;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/send_tab_to_self/SendTabToSelfShareActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/send_tab_to_self/SendTabToSelfShareActivity.java
index c14706c..e8d5c6c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/send_tab_to_self/SendTabToSelfShareActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/send_tab_to_self/SendTabToSelfShareActivity.java
@@ -13,13 +13,16 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetControllerProvider;
 import org.chromium.content_public.browser.NavigationEntry;
+import org.chromium.ui.base.WindowAndroid;
 
 /**
  * A simple activity that allows Chrome to expose send tab to self as an option in the share menu.
  */
 public class SendTabToSelfShareActivity extends ChromeAccessorActivity {
     private static BottomSheetContent sBottomSheetContentForTesting;
+    private static BottomSheetController sBottomSheetControllerForTesting;
 
     @Override
     protected void handleAction(ChromeActivity triggeringActivity) {
@@ -28,7 +31,7 @@
         NavigationEntry entry = tab.getWebContents().getNavigationController().getVisibleEntry();
         if (entry == null) return;
         actionHandler(triggeringActivity, entry.getUrl(), entry.getTitle(), entry.getTimestamp(),
-                triggeringActivity.getBottomSheetController());
+                getBottomSheetController(triggeringActivity.getWindowAndroid()));
     }
 
     public static void actionHandler(Context context, String url, String title, long navigationTime,
@@ -56,6 +59,16 @@
         return SendTabToSelfAndroidBridge.isFeatureAvailable(currentTab.getWebContents());
     }
 
+    private BottomSheetController getBottomSheetController(WindowAndroid window) {
+        if (sBottomSheetControllerForTesting != null) return sBottomSheetControllerForTesting;
+        return BottomSheetControllerProvider.from(window);
+    }
+
+    @VisibleForTesting
+    public static void setBottomSheetControllerForTesting(BottomSheetController controller) {
+        sBottomSheetControllerForTesting = controller;
+    }
+
     @VisibleForTesting
     public static void setBottomSheetContentForTesting(BottomSheetContent bottomSheetContent) {
         sBottomSheetContentForTesting = bottomSheetContent;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/settings/MainSettings.java b/chrome/android/java/src/org/chromium/chrome/browser/settings/MainSettings.java
index ef2b5b4..b28a439 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/settings/MainSettings.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/settings/MainSettings.java
@@ -25,6 +25,8 @@
 import org.chromium.chrome.browser.offlinepages.prefetch.PrefetchConfiguration;
 import org.chromium.chrome.browser.password_manager.ManagePasswordsReferrer;
 import org.chromium.chrome.browser.password_manager.PasswordManagerLauncher;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.safety_check.SafetyCheckSettingsFragment;
 import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
 import org.chromium.chrome.browser.signin.IdentityServicesProvider;
 import org.chromium.chrome.browser.signin.SigninManager;
@@ -96,7 +98,8 @@
     @Override
     public void onStart() {
         super.onStart();
-        SigninManager signinManager = IdentityServicesProvider.get().getSigninManager();
+        SigninManager signinManager = IdentityServicesProvider.get().getSigninManager(
+                Profile.getLastUsedRegularProfile());
         if (signinManager.isSigninSupported()) {
             signinManager.addSignInStateObserver(this);
             mSignInPreference.registerForUpdates();
@@ -110,7 +113,8 @@
     @Override
     public void onStop() {
         super.onStop();
-        SigninManager signinManager = IdentityServicesProvider.get().getSigninManager();
+        SigninManager signinManager = IdentityServicesProvider.get().getSigninManager(
+                Profile.getLastUsedRegularProfile());
         if (signinManager.isSigninSupported()) {
             signinManager.removeSignInStateObserver(this);
             mSignInPreference.unregisterForUpdates();
@@ -196,6 +200,10 @@
         // Only display the Safety check section if a corresponding flag is enabled.
         if (!ChromeFeatureList.isEnabled(ChromeFeatureList.SAFETY_CHECK_ANDROID)) {
             getPreferenceScreen().removePreference(findPreference(PREF_SAFETY_CHECK));
+        } else {
+            findPreference(PREF_SAFETY_CHECK)
+                    .setTitle(SafetyCheckSettingsFragment.getSafetyCheckSettingsElementTitle(
+                            getContext()));
         }
     }
 
@@ -218,7 +226,9 @@
     }
 
     private void updatePreferences() {
-        if (IdentityServicesProvider.get().getSigninManager().isSigninSupported()) {
+        if (IdentityServicesProvider.get()
+                        .getSigninManager(Profile.getLastUsedRegularProfile())
+                        .isSigninSupported()) {
             addPreferenceIfAbsent(PREF_SIGN_IN);
         } else {
             removePreferenceIfPresent(PREF_SIGN_IN);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/settings/SettingsActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/settings/SettingsActivity.java
index 60ce2ee2..cf65ded 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/settings/SettingsActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/settings/SettingsActivity.java
@@ -31,8 +31,9 @@
 import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.profiles.ProfileManagerUtils;
-import org.chromium.chrome.browser.safety_check.SafetyCheckOmahaClient;
+import org.chromium.chrome.browser.safety_check.SafetyCheckCoordinator;
 import org.chromium.chrome.browser.safety_check.SafetyCheckSettingsFragment;
+import org.chromium.chrome.browser.safety_check.SafetyCheckUpdatesDelegateImpl;
 import org.chromium.chrome.browser.site_settings.ChromeSiteSettingsClient;
 import org.chromium.components.browser_ui.settings.SettingsUtils;
 import org.chromium.components.browser_ui.site_settings.SiteSettingsPreferenceFragment;
@@ -270,8 +271,8 @@
             ((SiteSettingsPreferenceFragment) fragment)
                     .setSiteSettingsClient(new ChromeSiteSettingsClient(this));
         } else if (fragment instanceof SafetyCheckSettingsFragment) {
-            ((SafetyCheckSettingsFragment) fragment)
-                    .setUpdatesClient(new SafetyCheckOmahaClient(this));
+            SafetyCheckCoordinator.create((SafetyCheckSettingsFragment) fragment,
+                    new SafetyCheckUpdatesDelegateImpl(this));
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/share/LensUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/share/LensUtils.java
index 835082b2..709d2b1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/share/LensUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/share/LensUtils.java
@@ -43,6 +43,8 @@
     private static final String REQUIRE_ACCOUNT_DIALOG_KEY = "requiresConfirmation";
 
     private static final String MIN_AGSA_VERSION_FEATURE_PARAM_NAME = "minAgsaVersionName";
+    private static final String MIN_AGSA_VERSION_SHOPPING_FEATURE_PARAM_NAME =
+            "minAgsaVersionNameForShopping";
     private static final String USE_SEARCH_BY_IMAGE_TEXT_FEATURE_PARAM_NAME =
             "useSearchByImageText";
     private static final String LENS_SHOPPING_ALLOWLIST_ENTRIES_FEATURE_PARAM_NAME =
@@ -54,7 +56,7 @@
     private static final String SEND_ALT_PARAM_NAME = "sendAlt";
     private static final String USE_DIRECT_INTENT_FEATURE_PARAM_NAME = "useDirectIntent";
     private static final String MIN_AGSA_VERSION_NAME_FOR_LENS_POSTCAPTURE = "8.19";
-    private static final String MIN_AGSA_VERSION_NAME_FOR_LENS_CHROME_SHOPPING_INTENT = "11.16";
+    private static final String MIN_AGSA_VERSION_NAME_FOR_LENS_CHROME_SHOPPING_INTENT = "11.20";
     private static final String LENS_INTENT_TYPE_LENS_CHROME_SHOPPING = "18";
     private static final String LENS_SHOPPING_FEATURE_FLAG_VARIANT_NAME = "lensShopVariation";
     private static final String LENS_DEFAULT_SHOPPING_URL_PATTERNS =
@@ -184,7 +186,7 @@
             final String serverProvidedMinAgsaVersion =
                     ChromeFeatureList.getFieldTrialParamByFeature(
                             ChromeFeatureList.CONTEXT_MENU_SHOP_WITH_GOOGLE_LENS,
-                            MIN_AGSA_VERSION_FEATURE_PARAM_NAME);
+                            MIN_AGSA_VERSION_SHOPPING_FEATURE_PARAM_NAME);
             if (TextUtils.isEmpty(serverProvidedMinAgsaVersion)) {
                 // Falls into this block if the user enabled the feature using chrome://flags
                 // and the param was not set by the server.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninUtils.java
index 9fde44b..a6dc6e6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninUtils.java
@@ -21,6 +21,7 @@
 import org.chromium.chrome.browser.signin.account_picker.AccountPickerBottomSheetCoordinator;
 import org.chromium.chrome.browser.signin.account_picker.AccountPickerDelegate;
 import org.chromium.chrome.browser.sync.settings.AccountManagementFragment;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetControllerProvider;
 import org.chromium.components.browser_ui.settings.ManagedPreferencesUtils;
 import org.chromium.components.signin.GAIAServiceType;
 import org.chromium.components.signin.metrics.SigninAccessPoint;
@@ -78,7 +79,7 @@
             ChromeActivity activity = (ChromeActivity) windowAndroid.getActivity().get();
             AccountPickerBottomSheetCoordinator coordinator =
                     new AccountPickerBottomSheetCoordinator(activity,
-                            activity.getBottomSheetController(),
+                            BottomSheetControllerProvider.from(activity.getWindowAndroid()),
                             new AccountPickerDelegate(activity, continueUrl));
         }
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/ThumbnailGradient.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/ThumbnailGradient.java
index 2a34b07..5b6383f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/ThumbnailGradient.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/ThumbnailGradient.java
@@ -9,6 +9,7 @@
 import android.graphics.Color;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
 import android.os.SystemClock;
 
 import androidx.annotation.IntDef;
@@ -83,7 +84,7 @@
                             ? R.drawable.thumbnail_gradient_top_left
                             : R.drawable.thumbnail_gradient_top_right);
 
-            return ApiCompatibilityUtils.createLayerDrawable(
+            return new LayerDrawable(
                     new Drawable[] {new BitmapDrawable(resources, bitmap), gradient});
         }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
index 8540252..786b4190 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
@@ -207,7 +207,7 @@
             mEphemeralTabCoordinatorSupplier.set(new EphemeralTabCoordinator(mActivity,
                     mActivity.getWindowAndroid(), mActivity.getWindow().getDecorView(),
                     mActivity.getActivityTabProvider(), mActivity::getCurrentTabCreator,
-                    mActivity.getBottomSheetController(), true));
+                    getBottomSheetController(), true));
         }
 
         PostTask.postTask(UiThreadTaskTraits.DEFAULT, this::initializeIPH);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
index ab6bd331..f534cb6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
@@ -248,6 +248,7 @@
             if (mContextualSearchSuppressor != null) {
                 mBottomSheetController.removeObserver(mContextualSearchSuppressor);
             }
+            BottomSheetControllerFactory.detach(mBottomSheetController);
             mBottomSheetController.destroy();
         }
 
@@ -642,6 +643,7 @@
                 () -> mScrimCoordinator, sheetInitializedCallback, mActivity.getWindow(),
                 mActivity.getWindowAndroid().getKeyboardDelegate(),
                 () -> mActivity.findViewById(R.id.coordinator));
+        BottomSheetControllerFactory.attach(mActivity.getWindowAndroid(), mBottomSheetController);
 
         mBottomSheetManager = new BottomSheetManager(mBottomSheetController, mActivityTabProvider,
                 mActivity.getBrowserControlsManager(), mActivity.getFullscreenManager(),
@@ -686,7 +688,7 @@
         TabModelSelector tabModelSelector = mActivity.getTabModelSelector();
         mDirectActionInitializer = new DirectActionInitializer(mActivity, activityType, mActivity,
                 mActivity::onBackPressed, tabModelSelector, mFindToolbarManager,
-                mActivity.getBottomSheetController(), mActivity.getBrowserControlsManager(),
+                getBottomSheetController(), mActivity.getBrowserControlsManager(),
                 mActivity.getCompositorViewHolder(), mActivity.getActivityTabProvider(),
                 mScrimView);
         mActivity.getLifecycleDispatcher().register(mDirectActionInitializer);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/IntentHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/IntentHandlerTest.java
index 8629fa8..f3ca6d2 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/IntentHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/IntentHandlerTest.java
@@ -27,18 +27,13 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.CollectionUtil;
-import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
 import org.chromium.chrome.browser.customtabs.CustomTabsTestUtils;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.test.CommandLineInitRule;
 import org.chromium.chrome.browser.webapps.WebappLauncherActivity;
 import org.chromium.chrome.test.ChromeBrowserTestRule;
-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.chrome.test.util.browser.webapps.WebappTestHelper;
 import org.chromium.components.embedder_support.util.UrlConstants;
 
@@ -50,11 +45,9 @@
  * TODO(nileshagrawal): Add tests for onNewIntent.
  */
 @RunWith(BaseJUnit4ClassRunner.class)
-@DisableFeatures({ChromeFeatureList.ANDROID_BLOCK_INTENT_NON_SAFELISTED_HEADERS})
 public class IntentHandlerTest {
     @Rule
-    public final RuleChain mChain = RuleChain.outerRule(new Features.JUnitProcessor())
-                                            .around(new CommandLineInitRule(null))
+    public final RuleChain mChain = RuleChain.outerRule(new CommandLineInitRule(null))
                                             .around(new ChromeBrowserTestRule())
                                             .around(new UiThreadTestRule());
 
@@ -453,7 +446,6 @@
     @SmallTest
     @UiThreadTest
     @Feature({"Android-AppBase"})
-    @EnableFeatures({ChromeFeatureList.ANDROID_BLOCK_INTENT_NON_SAFELISTED_HEADERS})
     public void testStripNonCorsSafelistedCustomHeader() {
         Bundle bundle = new Bundle();
         bundle.putString("X-Some-Header", "1");
@@ -464,25 +456,6 @@
 
     @Test
     @SmallTest
-    @UiThreadTest
-    @Feature({"Android-AppBase"})
-    public void testLogHeaders() {
-        Bundle bundle = new Bundle();
-        bundle.putString("Content-Length", "1234");
-        Intent headersIntent = new Intent(Intent.ACTION_VIEW);
-        headersIntent.putExtra(Browser.EXTRA_HEADERS, bundle);
-
-        IntentHandler.getExtraHeadersFromIntent(headersIntent);
-        Assert.assertEquals(0,
-                RecordHistogram.getHistogramTotalCountForTesting("Android.IntentHeaders"));
-
-        IntentHandler.getExtraHeadersFromIntent(headersIntent, true);
-        Assert.assertEquals(1,
-                RecordHistogram.getHistogramTotalCountForTesting("Android.IntentHeaders"));
-    }
-
-    @Test
-    @SmallTest
     @Feature({"Android-AppBase"})
     public void testMaybeAddAdditionalExtraHeaders() {
         String contentUrl = "content://com.example.org/document/1";
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ShareIntentTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ShareIntentTest.java
index 6a0c074..d9fca5a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ShareIntentTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ShareIntentTest.java
@@ -35,7 +35,6 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.browser.tabmodel.MockTabModelSelector;
-import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.base.WindowAndroid;
 
@@ -140,11 +139,6 @@
         }
 
         @Override
-        public BottomSheetController getBottomSheetController() {
-            return mActivity.getBottomSheetController();
-        }
-
-        @Override
         public TabModelSelector getTabModelSelector() {
             // TabModelSelector remains uninitialized for this test. Return a mock instead.
             return new MockTabModelSelector(1, 0, null);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/MockLayoutHost.java b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/MockLayoutHost.java
index 44ce4664..a53bff2 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/MockLayoutHost.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/MockLayoutHost.java
@@ -9,6 +9,8 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 
+import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.chrome.browser.compositor.TitleCache;
 import org.chromium.chrome.browser.fullscreen.BrowserControlsManager;
 import org.chromium.chrome.browser.fullscreen.FullscreenManager;
@@ -26,6 +28,8 @@
 
     private final Context mContext;
     private boolean mPortrait = true;
+    private final BrowserControlsManager mBrowserControlsManager;
+    private final ObservableSupplierImpl<BrowserControlsManager> mBrowserControlsManagerSupplier;
 
     static class MockTitleCache implements TitleCache {
         @Override
@@ -44,6 +48,10 @@
 
     MockLayoutHost(Context context) {
         mContext = context;
+        mBrowserControlsManager =
+                new BrowserControlsManager(null, BrowserControlsManager.ControlsPosition.TOP);
+        mBrowserControlsManagerSupplier = new ObservableSupplierImpl<>();
+        mBrowserControlsManagerSupplier.set(mBrowserControlsManager);
     }
 
     public void setOrientation(boolean portrait) {
@@ -135,7 +143,12 @@
 
     @Override
     public BrowserControlsManager getBrowserControlsManager() {
-        return null;
+        return mBrowserControlsManager;
+    }
+
+    @Override
+    public ObservableSupplier<BrowserControlsManager> getBrowserControlsManagerSupplier() {
+        return mBrowserControlsManagerSupplier;
     }
 
     @Override
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/ContextMenuTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/ContextMenuTest.java
index 84526860..00f65c7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/ContextMenuTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/ContextMenuTest.java
@@ -235,6 +235,30 @@
     @Test
     @MediumTest
     @Feature({"Browser"})
+    @EnableFeatures({ChromeFeatureList.CONTEXT_MENU_SEARCH_WITH_GOOGLE_LENS})
+    @CommandLineFlags.Add({"enable-features=" + ChromeFeatureList.CONTEXT_MENU_SHOP_WITH_GOOGLE_LENS
+                    + "<FakeStudyName",
+            "force-fieldtrials=FakeStudyName/Enabled",
+            "force-fieldtrial-params=FakeStudyName.Enabled:"
+                    + "lensShopVariation/SearchSimilarProducts/minAgsaVersionNameForShopping/11.21"})
+    public void
+    testSearchSimilarProductsBelowShoppingMinimumAgsaVersion() throws Throwable {
+        LensUtils.setFakePassableLensEnvironmentForTesting(true);
+        LensUtils.setFakeImageUrlInShoppingAllowlistForTesting(true);
+        Tab tab = mDownloadTestRule.getActivity().getActivityTab();
+        ShareHelper.setIgnoreActivityNotFoundExceptionForTesting(true);
+        hardcodeTestImageForSharing(TEST_JPG_IMAGE_FILE_EXTENSION);
+
+        // Fallback to search with google lens when Agsa below minimum shopping supported version.
+        ContextMenuUtils.selectContextMenuItemWithExpectedIntent(
+                InstrumentationRegistry.getInstrumentation(), mDownloadTestRule.getActivity(), tab,
+                "testImage", R.id.contextmenu_search_with_google_lens,
+                "com.google.android.googlequicksearchbox");
+    }
+
+    @Test
+    @MediumTest
+    @Feature({"Browser"})
     @CommandLineFlags.Add({"enable-features="
                     + ChromeFeatureList.CONTEXT_MENU_SEARCH_WITH_GOOGLE_LENS + "<FakeStudyName",
             "force-fieldtrials=FakeStudyName/Enabled",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/gesturenav/NavigationSheetTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/gesturenav/NavigationSheetTest.java
index 32a798c..2098cf1 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/gesturenav/NavigationSheetTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/gesturenav/NavigationSheetTest.java
@@ -31,6 +31,7 @@
 import org.chromium.chrome.browser.ui.RootUiCoordinator;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.content_public.browser.NavigationController;
 import org.chromium.content_public.browser.NavigationEntry;
 import org.chromium.content_public.browser.NavigationHistory;
@@ -56,9 +57,14 @@
     private static final int NAVIGATION_INDEX_1 = 1;
     private static final int NAVIGATION_INDEX_2 = 5;
 
+    private BottomSheetController mBottomSheetController;
+
     @Before
     public void setUp() throws Exception {
         mActivityTestRule.startMainActivityOnBlankPage();
+        mBottomSheetController = mActivityTestRule.getActivity()
+                                         .getRootUiCoordinatorForTesting()
+                                         .getBottomSheetController();
     }
 
     private static class TestNavigationEntry extends NavigationEntry {
@@ -200,9 +206,8 @@
     private NavigationSheet showPopup(NavigationController controller) throws ExecutionException {
         return TestThreadUtils.runOnUiThreadBlocking(() -> {
             Tab tab = mActivityTestRule.getActivity().getActivityTabProvider().get();
-            NavigationSheet navigationSheet =
-                    NavigationSheet.create(tab.getContentView(), mActivityTestRule.getActivity(),
-                            mActivityTestRule.getActivity()::getBottomSheetController);
+            NavigationSheet navigationSheet = NavigationSheet.create(tab.getContentView(),
+                    mActivityTestRule.getActivity(), () -> mBottomSheetController);
             navigationSheet.setDelegate(new TestSheetDelegate(controller));
             navigationSheet.startAndExpand(false, false);
             return navigationSheet;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentDetailsUpdateServiceHelperTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentDetailsUpdateServiceHelperTest.java
index 2f14c5c..15747e06 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentDetailsUpdateServiceHelperTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentDetailsUpdateServiceHelperTest.java
@@ -133,8 +133,8 @@
 
         // Populate shipping options.
         List<PaymentShippingOption> shippingOptions = new ArrayList<PaymentShippingOption>();
-        shippingOptions.add(new PaymentShippingOption(
-                "shippingId", "Free shipping", "CAD", "0.00", /*selected=*/true));
+        shippingOptions.add(new PaymentShippingOption("shippingId", "Free shipping",
+                new PaymentCurrencyAmount("CAD", "0.00"), /*selected=*/true));
 
         // Populate address errors.
         Bundle bundledShippingAddressErrors = new Bundle();
@@ -178,11 +178,10 @@
                 shippingOption.getString(PaymentShippingOption.EXTRA_SHIPPING_OPTION_ID));
         Assert.assertEquals("Free shipping",
                 shippingOption.getString(PaymentShippingOption.EXTRA_SHIPPING_OPTION_LABEL));
-        Assert.assertEquals("CAD",
-                shippingOption.getString(
-                        PaymentShippingOption.EXTRA_SHIPPING_OPTION_AMOUNT_CURRENCY));
-        Assert.assertEquals("0.00",
-                shippingOption.getString(PaymentShippingOption.EXTRA_SHIPPING_OPTION_AMOUNT_VALUE));
+        Bundle amount =
+                shippingOption.getBundle(PaymentShippingOption.EXTRA_SHIPPING_OPTION_AMOUNT);
+        Assert.assertEquals("CAD", amount.getString(PaymentCurrencyAmount.EXTRA_CURRENCY));
+        Assert.assertEquals("0.00", amount.getString(PaymentCurrencyAmount.EXTRA_VALUE));
         Assert.assertTrue(
                 shippingOption.getBoolean(PaymentShippingOption.EXTRA_SHIPPING_OPTION_SELECTED));
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/WebPaymentIntentHelperTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/WebPaymentIntentHelperTest.java
index 1bb7226..1de474a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/WebPaymentIntentHelperTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/WebPaymentIntentHelperTest.java
@@ -72,7 +72,7 @@
         extras.putString(WebPaymentIntentHelper.EXTRA_RESPONSE_PAYER_NAME, "John Smith");
         extras.putString(WebPaymentIntentHelper.EXTRA_RESPONSE_PAYER_PHONE, "4169158200");
         extras.putString(WebPaymentIntentHelper.EXTRA_RESPONSE_PAYER_EMAIL, "JohnSmith@google.com");
-        extras.putString(PaymentShippingOption.EXTRA_SHIPPING_OPTION_ID, "shippingId");
+        extras.putString(WebPaymentIntentHelper.EXTRA_SHIPPING_OPTION_ID, "shippingId");
 
         // Redact the entry with missingField key.
         extras.remove(missingField);
@@ -111,8 +111,8 @@
                 /*requestPayerPhone=*/true, /*requestShipping=*/true, /*shippingType=*/"delivery");
 
         List<PaymentShippingOption> shippingOptions = new ArrayList<PaymentShippingOption>();
-        shippingOptions.add(new PaymentShippingOption(
-                "shippingId", "Free shipping", "USD", "0", /*selected=*/true));
+        shippingOptions.add(new PaymentShippingOption("shippingId", "Free shipping",
+                new PaymentCurrencyAmount("USD", "0"), /*selected=*/true));
 
         Intent intent = WebPaymentIntentHelper.createPayIntent("package.name", "activity.name",
                 "payment.request.id", "merchant.name", "schemeless.origin",
@@ -181,11 +181,10 @@
                 shippingOption.getString(PaymentShippingOption.EXTRA_SHIPPING_OPTION_ID));
         Assert.assertEquals("Free shipping",
                 shippingOption.getString(PaymentShippingOption.EXTRA_SHIPPING_OPTION_LABEL));
-        Assert.assertEquals("USD",
-                shippingOption.getString(
-                        PaymentShippingOption.EXTRA_SHIPPING_OPTION_AMOUNT_CURRENCY));
-        Assert.assertEquals("0",
-                shippingOption.getString(PaymentShippingOption.EXTRA_SHIPPING_OPTION_AMOUNT_VALUE));
+        Bundle amount =
+                shippingOption.getBundle(PaymentShippingOption.EXTRA_SHIPPING_OPTION_AMOUNT);
+        Assert.assertEquals("USD", amount.getString(PaymentCurrencyAmount.EXTRA_CURRENCY));
+        Assert.assertEquals("0", amount.getString(PaymentCurrencyAmount.EXTRA_VALUE));
         Assert.assertTrue(
                 shippingOption.getBoolean(PaymentShippingOption.EXTRA_SHIPPING_OPTION_SELECTED));
     }
@@ -682,7 +681,7 @@
     @Feature({"Payments"})
     public void parsePaymentResponseMissingShippingOptionTest() throws Throwable {
         Intent intent = createPaymentResponseWithMissingField(
-                PaymentShippingOption.EXTRA_SHIPPING_OPTION_ID);
+                WebPaymentIntentHelper.EXTRA_SHIPPING_OPTION_ID);
         mErrorString = null;
         WebPaymentIntentHelper.parsePaymentResponse(Activity.RESULT_OK, intent,
                 new PaymentOptions(/*requestPayerName=*/false, /*requestPayerEmail=*/false,
@@ -766,7 +765,7 @@
         extras.putString(WebPaymentIntentHelper.EXTRA_RESPONSE_PAYER_NAME, "John Smith");
         extras.putString(WebPaymentIntentHelper.EXTRA_RESPONSE_PAYER_PHONE, "4169158200");
         extras.putString(WebPaymentIntentHelper.EXTRA_RESPONSE_PAYER_EMAIL, "JohnSmith@google.com");
-        extras.putString(PaymentShippingOption.EXTRA_SHIPPING_OPTION_ID, "shippingId");
+        extras.putString(WebPaymentIntentHelper.EXTRA_SHIPPING_OPTION_ID, "shippingId");
         intent.putExtras(extras);
         mErrorString = null;
         WebPaymentIntentHelper.parsePaymentResponse(Activity.RESULT_OK, intent,
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
index baaf36b2..cdd39f35 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
@@ -15,7 +15,6 @@
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.net.Uri;
-import android.os.Build;
 import android.support.test.InstrumentationRegistry;
 import android.view.KeyEvent;
 import android.view.ViewGroup;
@@ -41,7 +40,7 @@
 import org.chromium.base.test.params.ParameterizedRunner;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.DisableIf;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.FlakyTest;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
@@ -554,8 +553,7 @@
 
     @Test
     @SmallTest
-    @DisableIf.
-    Build(message = "https://crbug.com/1100967", sdk_is_less_than = Build.VERSION_CODES.M)
+    @DisabledTest(message = "Flaky - https://crbug.com/1100967")
     @Features.
     EnableFeatures({ChromeFeatureList.OMNIBOX_ENABLE_CLIPBOARD_PROVIDER_IMAGE_SUGGESTIONS})
     public void testImageSearch() throws InterruptedException, Exception {
@@ -636,8 +634,7 @@
 
     @Test
     @SmallTest
-    @DisableIf.
-    Build(message = "https://crbug.com/1100967", sdk_is_less_than = Build.VERSION_CODES.M)
+    @DisabledTest(message = "Flaky - https://crbug.com/1100967")
     @Features.
     EnableFeatures({ChromeFeatureList.OMNIBOX_ENABLE_CLIPBOARD_PROVIDER_IMAGE_SUGGESTIONS})
     public void testImageSearch_OnlyTrustedIntentCanPost() throws InterruptedException, Exception {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareDelegateImplIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareDelegateImplIntegrationTest.java
index cbe6ddf..5ba55bc4 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareDelegateImplIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareDelegateImplIntegrationTest.java
@@ -126,7 +126,9 @@
                 }
             };
 
-            new ShareDelegateImpl(mActivityTestRule.getActivity().getBottomSheetController(),
+            new ShareDelegateImpl(mActivityTestRule.getActivity()
+                                          .getRootUiCoordinatorForTesting()
+                                          .getBottomSheetController(),
                     mActivityTestRule.getActivity().getActivityTabProvider(), delegate, false)
                     .share(mActivityTestRule.getActivity().getActivityTab(), false);
         });
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountPickerBottomSheetTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountPickerBottomSheetTest.java
index d33c017..17b2411d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountPickerBottomSheetTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountPickerBottomSheetTest.java
@@ -104,8 +104,7 @@
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             AccountPickerBottomSheetCoordinator accountPickerBottomSheetCoordinator =
                     new AccountPickerBottomSheetCoordinator(mActivityTestRule.getActivity(),
-                            mActivityTestRule.getActivity().getBottomSheetController(),
-                            mAccountPickerDelegateMock);
+                            getBottomSheetController(), mAccountPickerDelegateMock);
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -159,8 +158,7 @@
     public void testDismissCollapsedSheet() {
         buildAndShowAccountPickerBottomSheet();
         onView(withText(ACCOUNT_NAME1)).check(matches(isDisplayed()));
-        BottomSheetController controller =
-                mActivityTestRule.getActivity().getBottomSheetController();
+        BottomSheetController controller = getBottomSheetController();
         Assert.assertTrue(controller.isSheetOpen());
         Assert.assertEquals(2, mFakeProfileDataSource.getNumberOfObservers());
         onView(isRoot()).perform(pressBack());
@@ -173,8 +171,7 @@
     public void testDismissExpandedSheet() {
         buildAndShowAccountPickerBottomSheet();
         onView(withText(FULL_NAME1)).perform(click());
-        BottomSheetController controller =
-                mActivityTestRule.getActivity().getBottomSheetController();
+        BottomSheetController controller = getBottomSheetController();
         Assert.assertTrue(controller.isSheetOpen());
         Assert.assertEquals(2, mFakeProfileDataSource.getNumberOfObservers());
         onView(isRoot()).perform(pressBack());
@@ -308,4 +305,10 @@
                 .perform(click());
         verify(mAccountPickerDelegateMock).addAccount();
     }
+
+    private BottomSheetController getBottomSheetController() {
+        return mActivityTestRule.getActivity()
+                .getRootUiCoordinatorForTesting()
+                .getBottomSheetController();
+    }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabUmaTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabUmaTest.java
index e53cdce..a9039a9 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabUmaTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabUmaTest.java
@@ -75,7 +75,10 @@
         BrowserControlsVisibilityDelegate visibilityDelegate =
                 new BrowserControlsVisibilityDelegate(BrowserControlsState.BOTH) {};
         return new TabbedModeTabDelegateFactory(mActivityTestRule.getActivity(), visibilityDelegate,
-                new ObservableSupplierImpl<ShareDelegate>(), null);
+                new ObservableSupplierImpl<ShareDelegate>(), null,
+                mActivityTestRule.getActivity()
+                        .getRootUiCoordinatorForTesting()
+                        .getBottomSheetController());
     }
 
     private Tab createLazilyLoadedTab(boolean show) throws ExecutionException {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelMergingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelMergingTest.java
index 7df25b1..123f3149 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelMergingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelMergingTest.java
@@ -16,6 +16,7 @@
 
 import androidx.test.filters.LargeTest;
 
+import java.util.Collections;
 import org.hamcrest.Matchers;
 import org.junit.Assert;
 import org.junit.Before;
@@ -539,4 +540,48 @@
         mActivity1.finishAndRemoveTask();
         mActivity2.finishAndRemoveTask();
     }
+
+    @Test
+    @LargeTest
+    @EnableFeatures(ChromeFeatureList.ANDROID_MULTIPLE_DISPLAY)
+    @DisableIf.Build(sdk_is_less_than = VERSION_CODES.P)
+    public void testMergeOnMultiDisplay_OnDisplayChanged() throws TimeoutException {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivity1.saveState();
+            mActivity2.saveState();
+        });
+        MultiInstanceManager m1 = mActivity1.getMultiInstanceMangerForTesting();
+        MultiInstanceManager m2 = mActivity2.getMultiInstanceMangerForTesting();
+
+        // Ensure Activity 1 is resumed on the front.
+        Intent intent = new Intent(mActivity1, mActivity1.getClass());
+        intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+        mActivity1.startActivity(intent);
+        waitForActivityStateChange(ActivityState.RESUMED, mActivity2, false);
+        waitForActivityStateChange(ActivityState.RESUMED, mActivity1, true);
+
+        MultiInstanceManager.setTestDisplayIds(Collections.singletonList(0));
+        m1.setCurrentDisplayIdForTesting(0);
+        m2.setCurrentDisplayIdForTesting(1);
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            m1.getDisplayListenerForTesting().onDisplayChanged(1);
+            m2.getDisplayListenerForTesting().onDisplayChanged(1);
+        });
+
+        CriteriaHelper.pollUiThread(() -> {
+            Criteria.checkThat("Total tab count incorrect.",
+                mActivity1.getTabModelSelector().getTotalTabCount(), Matchers.is(7));
+        });
+
+        CriteriaHelper.pollUiThread(() -> {
+            Criteria.checkThat("CTA should not be destroyed", mActivity1State,
+                Matchers.not(ActivityState.DESTROYED));
+            Criteria.checkThat("CTA2 should be destroyed", mActivity2State,
+                Matchers.is(ActivityState.DESTROYED));
+        });
+
+        mActivity1.finishAndRemoveTask();
+        mActivity2.finishAndRemoveTask();
+    }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/widget/ScrimTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/ScrimTest.java
index 55247d72..7f97eba 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/widget/ScrimTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/ScrimTest.java
@@ -50,7 +50,7 @@
         final ChromeTabbedActivity activity = mActivityTestRule.getActivity();
 
         ThreadUtils.runOnUiThreadBlocking(() -> {
-            mSheetController = activity.getBottomSheetController();
+            mSheetController = activity.getRootUiCoordinatorForTesting().getBottomSheetController();
             mScrim = activity.getScrim();
         });
     }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/IntentHeadersRecorderTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/IntentHeadersRecorderTest.java
deleted file mode 100644
index 45c73c1..0000000
--- a/chrome/android/junit/src/org/chromium/chrome/browser/IntentHeadersRecorderTest.java
+++ /dev/null
@@ -1,134 +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.
-
-package org.chromium.chrome.browser;
-
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.annotation.Config;
-
-import org.chromium.base.metrics.RecordHistogram;
-import org.chromium.base.metrics.test.ShadowRecordHistogram;
-import org.chromium.base.test.BaseRobolectricTestRunner;
-
-/**
- * Tests for {@link IntentHeadersRecorder}.
- */
-@RunWith(BaseRobolectricTestRunner.class)
-@Config(manifest = Config.NONE,
-        shadows = {ShadowRecordHistogram.class})
-public class IntentHeadersRecorderTest {
-    private static final String SAFE_HEADER = "Safe-Header";
-    private static final String UNSAFE_HEADER = "Unsafe-Header";
-
-    @Mock public IntentHeadersRecorder.HeaderClassifier mClassifier;
-    private IntentHeadersRecorder mRecorder;
-
-    @Before
-    public void setUp() {
-        ShadowRecordHistogram.reset();
-        MockitoAnnotations.initMocks(this);
-
-        doReturn(true)
-                .when(mClassifier)
-                .isCorsSafelistedHeader(eq(SAFE_HEADER), anyString(), anyBoolean());
-        doReturn(false)
-                .when(mClassifier)
-                .isCorsSafelistedHeader(eq(UNSAFE_HEADER), anyString(), anyBoolean());
-
-        mRecorder = new IntentHeadersRecorder(mClassifier);
-    }
-
-    @Test
-    public void noHeaders_firstParty() {
-        mRecorder.report(true);
-        assertUma(1, 0, 0, 0, 0, 0);
-    }
-
-    @Test
-    public void noHeaders_thirdParty() {
-        mRecorder.report(false);
-        assertUma(0, 0, 0, 1, 0, 0);
-    }
-
-    @Test
-    public void safeHeaders_firstParty() {
-        mRecorder.recordHeader(SAFE_HEADER, "", true);
-        mRecorder.report(true);
-        assertUma(0, 1, 0, 0, 0, 0);
-    }
-
-    @Test
-    public void safeHeaders_thirdParty() {
-        mRecorder.recordHeader(SAFE_HEADER, "", false);
-        mRecorder.report(false);
-        assertUma(0, 0, 0, 0, 1, 0);
-    }
-
-    @Test
-    public void unsafeHeaders_firstParty() {
-        mRecorder.recordHeader(UNSAFE_HEADER, "", true);
-        mRecorder.report(true);
-        assertUma(0, 0, 1, 0, 0, 0);
-    }
-
-    @Test
-    public void unsafeHeaders_thirdParty() {
-        mRecorder.recordHeader(UNSAFE_HEADER, "", false);
-        mRecorder.report(false);
-        assertUma(0, 0, 0, 0, 0, 1);
-    }
-
-    @Test
-    public void mixedHeaders_firstParty() {
-        mRecorder.recordHeader(SAFE_HEADER, "", true);
-        mRecorder.recordHeader(UNSAFE_HEADER, "", true);
-        mRecorder.report(true);
-        assertUma(0, 0, 1, 0, 0, 0);
-    }
-
-    @Test
-    public void mixedHeaders_thirdParty() {
-        mRecorder.recordHeader(SAFE_HEADER, "", false);
-        mRecorder.recordHeader(UNSAFE_HEADER, "", false);
-        mRecorder.report(false);
-        assertUma(0, 0, 0, 0, 0, 1);
-    }
-
-    private void assertUma(int fpNoHeaders, int fpSafeHeaders, int fpUnsafeHeaders,
-                           int tpNoHeaders, int tpSafeHeaders, int tpUnsafeHeaders) {
-        Assert.assertEquals("first party no headers", fpNoHeaders,
-                RecordHistogram.getHistogramValueCountForTesting("Android.IntentHeaders",
-                        IntentHeadersRecorder.IntentHeadersResult.FIRST_PARTY_NO_HEADERS));
-
-        Assert.assertEquals("first party safe headers", fpSafeHeaders,
-                RecordHistogram.getHistogramValueCountForTesting("Android.IntentHeaders",
-                        IntentHeadersRecorder.IntentHeadersResult.FIRST_PARTY_ONLY_SAFE_HEADERS));
-
-        Assert.assertEquals("first party unsafe headers", fpUnsafeHeaders,
-                RecordHistogram.getHistogramValueCountForTesting("Android.IntentHeaders",
-                        IntentHeadersRecorder.IntentHeadersResult.FIRST_PARTY_UNSAFE_HEADERS));
-
-        Assert.assertEquals("third party no headers", tpNoHeaders,
-                RecordHistogram.getHistogramValueCountForTesting("Android.IntentHeaders",
-                        IntentHeadersRecorder.IntentHeadersResult.THIRD_PARTY_NO_HEADERS));
-
-        Assert.assertEquals("third party safe headers", tpSafeHeaders,
-                RecordHistogram.getHistogramValueCountForTesting("Android.IntentHeaders",
-                        IntentHeadersRecorder.IntentHeadersResult.THIRD_PARTY_ONLY_SAFE_HEADERS));
-
-        Assert.assertEquals("third party unsafe headers", tpUnsafeHeaders,
-                RecordHistogram.getHistogramValueCountForTesting("Android.IntentHeaders",
-                        IntentHeadersRecorder.IntentHeadersResult.THIRD_PARTY_UNSAFE_HEADERS));
-    }
-}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/feed/v2/FakeLinearLayoutManager.java b/chrome/android/junit/src/org/chromium/chrome/browser/feed/v2/FakeLinearLayoutManager.java
new file mode 100644
index 0000000..840fdd12
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/feed/v2/FakeLinearLayoutManager.java
@@ -0,0 +1,66 @@
+// Copyright 2020 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.feed.v2;
+
+import android.content.Context;
+import android.view.View;
+
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** A fake version of LinearLayoutManager. */
+public class FakeLinearLayoutManager extends LinearLayoutManager {
+    private final List<View> mChildViews;
+    private int mFirstVisiblePosition = RecyclerView.NO_POSITION;
+    private int mLastVisiblePosition = RecyclerView.NO_POSITION;
+    private int mItemCount = RecyclerView.NO_POSITION;
+
+    public FakeLinearLayoutManager(Context context) {
+        super(context);
+        mChildViews = new ArrayList<>();
+    }
+
+    public void setFirstVisiblePosition(int firstVisiblePosition) {
+        mFirstVisiblePosition = firstVisiblePosition;
+    }
+
+    public void setLastVisiblePosition(int lastVisiblePosition) {
+        mLastVisiblePosition = lastVisiblePosition;
+    }
+
+    public void setItemCount(int itemCount) {
+        mItemCount = itemCount;
+    }
+
+    public void addChildToPosition(int position, View child) {
+        mChildViews.add(position, child);
+    }
+
+    @Override
+    public int findFirstVisibleItemPosition() {
+        return mFirstVisiblePosition;
+    }
+
+    @Override
+    public int findLastVisibleItemPosition() {
+        return mLastVisiblePosition;
+    }
+
+    @Override
+    public int getItemCount() {
+        return mItemCount;
+    }
+
+    @Override
+    public View findViewByPosition(int i) {
+        if (i < 0 || i >= mChildViews.size()) {
+            return null;
+        }
+        return mChildViews.get(i);
+    }
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/feed/v2/FeedStreamSurfaceTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/feed/v2/FeedStreamSurfaceTest.java
index 99df0b6..bafd9da 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/feed/v2/FeedStreamSurfaceTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/feed/v2/FeedStreamSurfaceTest.java
@@ -4,10 +4,15 @@
 
 package org.chromium.chrome.browser.feed.v2;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -16,6 +21,8 @@
 import android.view.View;
 import android.widget.TextView;
 
+import androidx.recyclerview.widget.RecyclerView;
+
 import com.google.protobuf.ByteString;
 
 import org.junit.Before;
@@ -23,16 +30,20 @@
 import org.junit.Test;
 import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatchers;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.Robolectric;
 import org.robolectric.annotation.Config;
 
+import org.chromium.base.Callback;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.JniMocker;
 import org.chromium.chrome.browser.help.HelpAndFeedback;
 import org.chromium.chrome.browser.native_page.NativePageNavigationDelegate;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.MockTab;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.chrome.browser.xsurface.FeedActionsHandler;
@@ -45,6 +56,8 @@
 import org.chromium.ui.mojom.WindowOpenDisposition;
 
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
 
 /** Unit tests for {@link FeedStreamSurface}. */
 @RunWith(BaseRobolectricTestRunner.class)
@@ -52,8 +65,11 @@
 public class FeedStreamSurfaceTest {
     private static final String TEST_DATA = "test";
     private static final String TEST_URL = "https://www.chromium.org";
+    private static final int LOAD_MORE_TRIGGER_LOOKAHEAD = 5;
     private FeedStreamSurface mFeedStreamSurface;
     private Activity mActivity;
+    private RecyclerView mRecyclerView;
+    private FakeLinearLayoutManager mLayoutManager;
 
     @Mock
     private SnackbarManager mSnackbarManager;
@@ -65,6 +81,13 @@
     private NativePageNavigationDelegate mPageNavigationDelegate;
     @Mock
     private HelpAndFeedback mHelpAndFeedback;
+    @Mock
+    Profile mProfileMock;
+    @Mock
+    private FeedServiceBridge.Natives mFeedServiceBridgeJniMock;
+
+    @Captor
+    private ArgumentCaptor<Map<String, String>> mMapCaptor;
 
     @Rule
     public JniMocker mocker = new JniMocker();
@@ -81,8 +104,18 @@
         MockitoAnnotations.initMocks(this);
         mActivity = Robolectric.buildActivity(Activity.class).get();
         mocker.mock(FeedStreamSurfaceJni.TEST_HOOKS, mFeedStreamSurfaceJniMock);
+        mocker.mock(FeedServiceBridgeJni.TEST_HOOKS, mFeedServiceBridgeJniMock);
+
+        when(mFeedServiceBridgeJniMock.getLoadMoreTriggerLookahead())
+                .thenReturn(LOAD_MORE_TRIGGER_LOOKAHEAD);
+
+        Profile.setLastUsedProfileForTesting(mProfileMock);
         mFeedStreamSurface = new FeedStreamSurface(mActivity, false, mSnackbarManager,
                 mPageNavigationDelegate, mBottomSheetController, mHelpAndFeedback);
+
+        mRecyclerView = (RecyclerView) mFeedStreamSurface.getView();
+        mLayoutManager = new FakeLinearLayoutManager(mActivity);
+        mRecyclerView.setLayoutManager(mLayoutManager);
     }
 
     @Test
@@ -402,6 +435,33 @@
         verify(mPageNavigationDelegate)
                 .openUrl(ArgumentMatchers.eq(WindowOpenDisposition.OFF_THE_RECORD), any());
     }
+
+    @Test
+    @SmallTest
+    public void testSendFeedback() {
+        final String testUrl = "https://www.chromium.org";
+        final String testTitle = "Chromium based browsers for the win!";
+        final String xSurfaceCardTitle = "Card Title";
+        final String cardTitle = "CardTitle";
+        final String cardUrl = "CardUrl";
+        // Arrange.
+        Map<String, String> productSpecificDataMap = new HashMap<>();
+        productSpecificDataMap.put(FeedStreamSurface.XSURFACE_CARD_URL, testUrl);
+        productSpecificDataMap.put(xSurfaceCardTitle, testTitle);
+
+        // Act.
+        mFeedStreamSurface.sendFeedback(productSpecificDataMap);
+
+        // Assert.
+        verify(mHelpAndFeedback)
+                .showFeedback(any(), any(), eq(testUrl), eq(FeedStreamSurface.FEEDBACK_REPORT_TYPE),
+                        mMapCaptor.capture(), eq(FeedStreamSurface.FEEDBACK_CONTEXT));
+
+        // Check that the map contents are as expected.
+        assertThat(mMapCaptor.getValue()).containsEntry(cardUrl, testUrl);
+        assertThat(mMapCaptor.getValue()).containsEntry(cardTitle, testTitle);
+    }
+
     @Test
     @SmallTest
     public void testShowSnackbar() {
@@ -464,6 +524,66 @@
                         .getNativeView(null));
     }
 
+    @Test
+    @SmallTest
+    public void testLoadMoreOnDismissal() {
+        final int itemCount = 10;
+
+        // loadMore not triggered due to last visible item not falling into lookahead range.
+        mLayoutManager.setLastVisiblePosition(itemCount - LOAD_MORE_TRIGGER_LOOKAHEAD - 1);
+        mLayoutManager.setItemCount(itemCount);
+        mFeedStreamSurface.commitDismissal(0);
+        verify(mFeedStreamSurfaceJniMock, never())
+                .loadMore(anyLong(), any(FeedStreamSurface.class), any(Callback.class));
+
+        // loadMore triggered.
+        mLayoutManager.setLastVisiblePosition(itemCount - LOAD_MORE_TRIGGER_LOOKAHEAD + 1);
+        mLayoutManager.setItemCount(itemCount);
+        mFeedStreamSurface.commitDismissal(0);
+        verify(mFeedStreamSurfaceJniMock)
+                .loadMore(anyLong(), any(FeedStreamSurface.class), any(Callback.class));
+    }
+
+    @Test
+    @SmallTest
+    public void testLoadMoreOnNavigateNewTab() {
+        final int itemCount = 10;
+
+        // loadMore not triggered due to last visible item not falling into lookahead range.
+        mLayoutManager.setLastVisiblePosition(itemCount - LOAD_MORE_TRIGGER_LOOKAHEAD - 1);
+        mLayoutManager.setItemCount(itemCount);
+        mFeedStreamSurface.navigateNewTab("");
+        verify(mFeedStreamSurfaceJniMock, never())
+                .loadMore(anyLong(), any(FeedStreamSurface.class), any(Callback.class));
+
+        // loadMore triggered.
+        mLayoutManager.setLastVisiblePosition(itemCount - LOAD_MORE_TRIGGER_LOOKAHEAD + 1);
+        mLayoutManager.setItemCount(itemCount);
+        mFeedStreamSurface.navigateNewTab("");
+        verify(mFeedStreamSurfaceJniMock)
+                .loadMore(anyLong(), any(FeedStreamSurface.class), any(Callback.class));
+    }
+
+    @Test
+    @SmallTest
+    public void testLoadMoreOnNavigateIncognitoTab() {
+        final int itemCount = 10;
+
+        // loadMore not triggered due to last visible item not falling into lookahead range.
+        mLayoutManager.setLastVisiblePosition(itemCount - LOAD_MORE_TRIGGER_LOOKAHEAD - 1);
+        mLayoutManager.setItemCount(itemCount);
+        mFeedStreamSurface.navigateIncognitoTab("");
+        verify(mFeedStreamSurfaceJniMock, never())
+                .loadMore(anyLong(), any(FeedStreamSurface.class), any(Callback.class));
+
+        // loadMore triggered.
+        mLayoutManager.setLastVisiblePosition(itemCount - LOAD_MORE_TRIGGER_LOOKAHEAD + 1);
+        mLayoutManager.setItemCount(itemCount);
+        mFeedStreamSurface.navigateIncognitoTab("");
+        verify(mFeedStreamSurfaceJniMock)
+                .loadMore(anyLong(), any(FeedStreamSurface.class), any(Callback.class));
+    }
+
     private SliceUpdate createSliceUpdateForExistingSlice(String sliceId) {
         return SliceUpdate.newBuilder().setSliceId(sliceId).build();
     }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/feed/v2/FeedStreamTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/feed/v2/FeedStreamTest.java
index e2191c7..a7d252f 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/feed/v2/FeedStreamTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/feed/v2/FeedStreamTest.java
@@ -15,12 +15,10 @@
 import static org.chromium.chrome.browser.feed.shared.stream.Stream.POSITION_NOT_KNOWN;
 
 import android.app.Activity;
-import android.content.Context;
 import android.util.TypedValue;
 import android.view.View;
 import android.widget.FrameLayout;
 
-import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
 import org.junit.Before;
@@ -39,52 +37,10 @@
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 
-import java.util.ArrayList;
-import java.util.List;
-
 /** Unit tests for {@link FeedStream}. */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
 public class FeedStreamTest {
-    private class FakeLinearLayoutManager extends LinearLayoutManager {
-        private final List<View> mChildViews;
-        private int mFirstVisiblePosition = RecyclerView.NO_POSITION;
-        private int mLastVisiblePosition = RecyclerView.NO_POSITION;
-        private int mItemCount = RecyclerView.NO_POSITION;
-
-        public FakeLinearLayoutManager(Context context) {
-            super(context);
-            mChildViews = new ArrayList<>();
-        }
-
-        @Override
-        public int findFirstVisibleItemPosition() {
-            return mFirstVisiblePosition;
-        }
-
-        @Override
-        public int findLastVisibleItemPosition() {
-            return mLastVisiblePosition;
-        }
-
-        @Override
-        public int getItemCount() {
-            return mItemCount;
-        }
-
-        @Override
-        public View findViewByPosition(int i) {
-            if (i < 0 || i >= mChildViews.size()) {
-                return null;
-            }
-            return mChildViews.get(i);
-        }
-
-        private void addChildToPosition(int position, View child) {
-            mChildViews.add(position, child);
-        }
-    }
-
     private static final int LOAD_MORE_TRIGGER_LOOKAHEAD = 5;
     private Activity mActivity;
     private RecyclerView mRecyclerView;
@@ -125,8 +81,8 @@
 
     @Test
     public void testIsChildAtPositionVisible() {
-        mLayoutManager.mFirstVisiblePosition = 0;
-        mLayoutManager.mLastVisiblePosition = 1;
+        mLayoutManager.setFirstVisiblePosition(0);
+        mLayoutManager.setLastVisiblePosition(1);
         assertThat(mFeedStream.isChildAtPositionVisible(-2)).isFalse();
         assertThat(mFeedStream.isChildAtPositionVisible(-1)).isFalse();
         assertThat(mFeedStream.isChildAtPositionVisible(0)).isTrue();
@@ -141,13 +97,13 @@
 
     @Test
     public void testIsChildAtPositionVisible_validTop() {
-        mLayoutManager.mFirstVisiblePosition = 0;
+        mLayoutManager.setFirstVisiblePosition(0);
         assertThat(mFeedStream.isChildAtPositionVisible(0)).isFalse();
     }
 
     @Test
     public void testIsChildAtPositionVisible_validBottom() {
-        mLayoutManager.mLastVisiblePosition = 1;
+        mLayoutManager.setLastVisiblePosition(1);
         assertThat(mFeedStream.isChildAtPositionVisible(0)).isFalse();
     }
 
@@ -158,15 +114,15 @@
 
     @Test
     public void testGetChildTopAt_noChild() {
-        mLayoutManager.mFirstVisiblePosition = 0;
-        mLayoutManager.mLastVisiblePosition = 1;
+        mLayoutManager.setFirstVisiblePosition(0);
+        mLayoutManager.setLastVisiblePosition(1);
         assertThat(mFeedStream.getChildTopAt(0)).isEqualTo(POSITION_NOT_KNOWN);
     }
 
     @Test
     public void testGetChildTopAt() {
-        mLayoutManager.mFirstVisiblePosition = 0;
-        mLayoutManager.mLastVisiblePosition = 1;
+        mLayoutManager.setFirstVisiblePosition(0);
+        mLayoutManager.setLastVisiblePosition(1);
         View view = new FrameLayout(mActivity);
         mLayoutManager.addChildToPosition(0, view);
 
@@ -194,15 +150,15 @@
                 .loadMore(anyLong(), any(FeedStreamSurface.class), any(Callback.class));
 
         // loadMore not triggered due to last visible item not falling into lookahead range.
-        mLayoutManager.mLastVisiblePosition = itemCount - LOAD_MORE_TRIGGER_LOOKAHEAD - 1;
-        mLayoutManager.mItemCount = itemCount;
+        mLayoutManager.setLastVisiblePosition(itemCount - LOAD_MORE_TRIGGER_LOOKAHEAD - 1);
+        mLayoutManager.setItemCount(itemCount);
         mFeedStream.checkScrollingForLoadMore(triggerDistance / 2);
         verify(mFeedStreamSurfaceJniMock, never())
                 .loadMore(anyLong(), any(FeedStreamSurface.class), any(Callback.class));
 
         // loadMore triggered.
-        mLayoutManager.mLastVisiblePosition = itemCount - LOAD_MORE_TRIGGER_LOOKAHEAD + 1;
-        mLayoutManager.mItemCount = itemCount;
+        mLayoutManager.setLastVisiblePosition(itemCount - LOAD_MORE_TRIGGER_LOOKAHEAD + 1);
+        mLayoutManager.setItemCount(itemCount);
         mFeedStream.checkScrollingForLoadMore(triggerDistance / 2);
         verify(mFeedStreamSurfaceJniMock)
                 .loadMore(anyLong(), any(FeedStreamSurface.class), any(Callback.class));
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/native_page/NativePageFactoryTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/native_page/NativePageFactoryTest.java
index 34f7c68..74cc462 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/native_page/NativePageFactoryTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/native_page/NativePageFactoryTest.java
@@ -87,7 +87,7 @@
 
     private static class MockNativePageBuilder extends NativePageFactory.NativePageBuilder {
         private MockNativePageBuilder() {
-            super(null, null);
+            super(null, null, null);
         }
 
         @Override
@@ -161,7 +161,7 @@
 
     @Before
     public void setUp() {
-        mNativePageFactory = new NativePageFactory(null);
+        mNativePageFactory = new NativePageFactory(null, null);
         mNativePageFactory.setNativePageBuilderForTesting(new MockNativePageBuilder());
     }
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/send_tab_to_self/SendTabToSelfShareActivityTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/send_tab_to_self/SendTabToSelfShareActivityTest.java
index c497449..c114696 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/send_tab_to_self/SendTabToSelfShareActivityTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/send_tab_to_self/SendTabToSelfShareActivityTest.java
@@ -95,10 +95,9 @@
         when(mNavigationController.getVisibleEntry()).thenReturn(mNavigationEntry);
 
         // Setup the mocked object chain to get the bottom controller.
-        when(mChromeActivity.getBottomSheetController()).thenReturn(mBottomSheetController);
-
         SendTabToSelfShareActivity shareActivity = new SendTabToSelfShareActivity();
         SendTabToSelfShareActivity.setBottomSheetContentForTesting(mBottomSheetContent);
+        SendTabToSelfShareActivity.setBottomSheetControllerForTesting(mBottomSheetController);
         shareActivity.handleAction(mChromeActivity);
         verify(mBottomSheetController).requestShowContent(any(BottomSheetContent.class), eq(true));
     }
diff --git a/chrome/app/BUILD.gn b/chrome/app/BUILD.gn
index b3e3970..632640a 100644
--- a/chrome/app/BUILD.gn
+++ b/chrome/app/BUILD.gn
@@ -130,7 +130,7 @@
     "//content/public/app",
     "//content/public/common",
     "//content/public/common:service_names",
-    "//pdf",
+    "//pdf:pdf_ppapi",
     "//ppapi/buildflags",
     "//printing/buildflags",
     "//services/service_manager/embedder",
diff --git a/chrome/app/nearby_share_strings.grdp b/chrome/app/nearby_share_strings.grdp
index 6d55d10..ad48cea 100644
--- a/chrome/app/nearby_share_strings.grdp
+++ b/chrome/app/nearby_share_strings.grdp
@@ -31,6 +31,24 @@
   </message>
 
   <!-- Notification strings -->
+  <message name="IDS_NEARBY_NOTIFICATION_CONNECTION_REQUEST_MESSAGE" desc="Text shown as the message of a notfication when a nearby device requests a connection via Nearby Share.">
+    <ph name="DEVICE_NAME">$1<ex>Ted's Pixel 2</ex></ph> is sharing <ph name="ATTACHMENTS">$2<ex>3 items</ex></ph> with you.
+  </message>
+  <message name="IDS_NEARBY_NOTIFICATION_CONNECTION_REQUEST_TITLE" desc="Text shown as the title of a notfication when a nearby device requests a connection via Nearby Share.">
+    Receive with Nearby Share?
+  </message>
+  <message name="IDS_NEARBY_NOTIFICATION_DECLINE_ACTION" desc="Text shown on the notification action to decline receiving data via Nearby Share.">
+    Decline
+  </message>
+  <message name="IDS_NEARBY_NOTIFICATION_ONBOARDING_MESSAGE" desc="Text shown as the message of a notification when a nearby device is attempting to share data via Nearby Share.">
+    Click to become visible so it can share with you
+  </message>
+  <message name="IDS_NEARBY_NOTIFICATION_ONBOARDING_TITLE" desc="Text shown as the title of a notification when a nearby device is attempting to share data via Nearby Share.">
+    Device nearby is sharing
+  </message>
+  <message name="IDS_NEARBY_NOTIFICATION_RECEIVE_ACTION" desc="Text shown on the notification action to accept receiving data via Nearby Share.">
+    Receive
+  </message>
   <message name="IDS_NEARBY_NOTIFICATION_RECEIVE_PROGRESS_TITLE" desc="Text shown as the title of a notfication when receiving data via Nearby Share.">
     Receiving <ph name="ATTACHMENTS">$1<ex>3 items</ex></ph> from <ph name="DEVICE_NAME">$2<ex>Ted's Pixel 2</ex></ph>
   </message>
diff --git a/chrome/app/nearby_share_strings_grdp/IDS_NEARBY_NOTIFICATION_CONNECTION_REQUEST_MESSAGE.png.sha1 b/chrome/app/nearby_share_strings_grdp/IDS_NEARBY_NOTIFICATION_CONNECTION_REQUEST_MESSAGE.png.sha1
new file mode 100644
index 0000000..a48676c
--- /dev/null
+++ b/chrome/app/nearby_share_strings_grdp/IDS_NEARBY_NOTIFICATION_CONNECTION_REQUEST_MESSAGE.png.sha1
@@ -0,0 +1 @@
+9209f2f079bc7065d47dc8bd88c23259231657fc
\ No newline at end of file
diff --git a/chrome/app/nearby_share_strings_grdp/IDS_NEARBY_NOTIFICATION_CONNECTION_REQUEST_TITLE.png.sha1 b/chrome/app/nearby_share_strings_grdp/IDS_NEARBY_NOTIFICATION_CONNECTION_REQUEST_TITLE.png.sha1
new file mode 100644
index 0000000..71b8fd0
--- /dev/null
+++ b/chrome/app/nearby_share_strings_grdp/IDS_NEARBY_NOTIFICATION_CONNECTION_REQUEST_TITLE.png.sha1
@@ -0,0 +1 @@
+c5cb0dbc758de36234177b0894a9213f8ddff090
\ No newline at end of file
diff --git a/chrome/app/nearby_share_strings_grdp/IDS_NEARBY_NOTIFICATION_DECLINE_ACTION.png.sha1 b/chrome/app/nearby_share_strings_grdp/IDS_NEARBY_NOTIFICATION_DECLINE_ACTION.png.sha1
new file mode 100644
index 0000000..66300e03
--- /dev/null
+++ b/chrome/app/nearby_share_strings_grdp/IDS_NEARBY_NOTIFICATION_DECLINE_ACTION.png.sha1
@@ -0,0 +1 @@
+80bea6cf110eb551ba8fb69a13cef3cae2626c2b
\ No newline at end of file
diff --git a/chrome/app/nearby_share_strings_grdp/IDS_NEARBY_NOTIFICATION_ONBOARDING_MESSAGE.png.sha1 b/chrome/app/nearby_share_strings_grdp/IDS_NEARBY_NOTIFICATION_ONBOARDING_MESSAGE.png.sha1
new file mode 100644
index 0000000..854b805
--- /dev/null
+++ b/chrome/app/nearby_share_strings_grdp/IDS_NEARBY_NOTIFICATION_ONBOARDING_MESSAGE.png.sha1
@@ -0,0 +1 @@
+d2e8d544b457ae39db7ccfe9013c658469db9b31
\ No newline at end of file
diff --git a/chrome/app/nearby_share_strings_grdp/IDS_NEARBY_NOTIFICATION_ONBOARDING_TITLE.png.sha1 b/chrome/app/nearby_share_strings_grdp/IDS_NEARBY_NOTIFICATION_ONBOARDING_TITLE.png.sha1
new file mode 100644
index 0000000..47f1a930
--- /dev/null
+++ b/chrome/app/nearby_share_strings_grdp/IDS_NEARBY_NOTIFICATION_ONBOARDING_TITLE.png.sha1
@@ -0,0 +1 @@
+697cbc8bdcf71535a636e178dc764a58ed7ba4d7
\ No newline at end of file
diff --git a/chrome/app/nearby_share_strings_grdp/IDS_NEARBY_NOTIFICATION_RECEIVE_ACTION.png.sha1 b/chrome/app/nearby_share_strings_grdp/IDS_NEARBY_NOTIFICATION_RECEIVE_ACTION.png.sha1
new file mode 100644
index 0000000..401cde43
--- /dev/null
+++ b/chrome/app/nearby_share_strings_grdp/IDS_NEARBY_NOTIFICATION_RECEIVE_ACTION.png.sha1
@@ -0,0 +1 @@
+b262d909935b40a04e27f7bae02a7d583ecb24fb
\ No newline at end of file
diff --git a/chrome/app/version_assembly/BUILD.gn b/chrome/app/version_assembly/BUILD.gn
index 4bb932a..f4025492 100644
--- a/chrome/app/version_assembly/BUILD.gn
+++ b/chrome/app/version_assembly/BUILD.gn
@@ -18,17 +18,20 @@
 }
 
 # Generates the manifest for chrome.exe. This is the normal manifest stuff plus
-# opting in to the segment heap (Windows 10 2004 and above), and the version
-# information.
+# opting in to the segment heap (Windows 10 2004 and above) if enabled, and the
+# version information.
 windows_manifest("chrome_exe_manifest") {
   sources = [
     as_invoker_manifest,
     common_controls_manifest,
     default_compatibility_manifest,
-    segment_heap_manifest,
     version_assembly_output_file,
   ]
 
+  if (enable_segment_heap) {
+    sources += [ segment_heap_manifest ]
+  }
+
   deps = [ ":chrome_exe_version_manifest" ]
 }
 
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index f0bd789..46751eb 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1486,12 +1486,9 @@
     "sessions/session_tab_helper_factory.h",
     "sessions/tab_restore_service_factory.cc",
     "sessions/tab_restore_service_factory.h",
+    "sharesheet/share_action.h",
     "sharesheet/sharesheet_action_cache.cc",
     "sharesheet/sharesheet_action_cache.h",
-    "sharesheet/sharesheet_service.cc",
-    "sharesheet/sharesheet_service.h",
-    "sharesheet/sharesheet_service_factory.cc",
-    "sharesheet/sharesheet_service_factory.h",
     "sharing/ack_message_handler.cc",
     "sharing/ack_message_handler.h",
     "sharing/click_to_call/feature.cc",
@@ -2381,7 +2378,6 @@
       "android/feedback/system_info_feedback_source.cc",
       "android/foreign_session_helper.cc",
       "android/foreign_session_helper.h",
-      "android/headers_classifier.cc",
       "android/historical_tab_saver.cc",
       "android/history/browsing_history_bridge.cc",
       "android/history/browsing_history_bridge.h",
@@ -2420,6 +2416,7 @@
       "android/instantapps/instant_apps_infobar_delegate.h",
       "android/instantapps/instant_apps_settings.cc",
       "android/instantapps/instant_apps_settings.h",
+      "android/intent_handler.cc",
       "android/intent_helper.cc",
       "android/intent_helper.h",
       "android/large_icon_bridge.cc",
@@ -3281,6 +3278,8 @@
       "metrics/desktop_session_duration/desktop_session_duration_observer.h",
       "metrics/desktop_session_duration/desktop_session_duration_tracker.cc",
       "metrics/desktop_session_duration/desktop_session_duration_tracker.h",
+      "metrics/desktop_session_duration/touch_mode_stats_tracker.cc",
+      "metrics/desktop_session_duration/touch_mode_stats_tracker.h",
       "metrics/first_web_contents_profiler.cc",
       "metrics/first_web_contents_profiler.h",
       "metrics/incognito_observer_desktop.cc",
@@ -3313,8 +3312,6 @@
       "nearby_sharing/instantmessaging/stream_parser.h",
       "nearby_sharing/instantmessaging/token_fetcher.cc",
       "nearby_sharing/instantmessaging/token_fetcher.h",
-      "nearby_sharing/logging/log_buffer.h",
-      "nearby_sharing/logging/logging.h",
       "nearby_sharing/nearby_connection.h",
       "nearby_sharing/nearby_connections_manager.h",
       "nearby_sharing/nearby_connections_manager_impl.cc",
@@ -3539,8 +3536,13 @@
       "serial/serial_chooser_context.h",
       "serial/serial_chooser_context_factory.cc",
       "serial/serial_chooser_context_factory.h",
+      "sharesheet/sharesheet_controller.h",
+      "sharesheet/sharesheet_service.cc",
+      "sharesheet/sharesheet_service.h",
       "sharesheet/sharesheet_service_delegate.cc",
       "sharesheet/sharesheet_service_delegate.h",
+      "sharesheet/sharesheet_service_factory.cc",
+      "sharesheet/sharesheet_service_factory.h",
       "sharing/click_to_call/click_to_call_context_menu_observer.cc",
       "sharing/click_to_call/click_to_call_context_menu_observer.h",
       "sharing/click_to_call/click_to_call_metrics.cc",
@@ -3739,6 +3741,7 @@
       "//chrome/browser/nearby_sharing/certificates",
       "//chrome/browser/nearby_sharing/instantmessaging/proto",
       "//chrome/browser/nearby_sharing/logging",
+      "//chrome/browser/nearby_sharing/logging:util",
       "//chrome/browser/nearby_sharing/proto",
       "//chrome/browser/nearby_sharing/scheduling",
       "//chrome/browser/policy:path_parser",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 63920c9..d1f6a08 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -1783,10 +1783,18 @@
 // cache is implemented.
 const FeatureEntry::FeatureParam kBackForwardCache_ForceCaching[] = {
     {"TimeToLiveInBackForwardCacheInSeconds", "300"},
-    {"should_ignore_blocklists", "true"}};
+    {"should_ignore_blocklists", "true"},
+    {"enable_same_site", "true"}};
+
+// With this, back-forward cache will be enabled on eligible pages when doing
+// same-site navigations (instead of only cross-site navigations).
+const FeatureEntry::FeatureParam kBackForwardCache_SameSite[] = {
+    {"enable_same_site", "true"}};
 
 const FeatureEntry::FeatureVariation kBackForwardCacheVariations[] = {
-    {"force caching all pages", kBackForwardCache_ForceCaching,
+    {"same-site support (experimental)", kBackForwardCache_SameSite,
+     base::size(kBackForwardCache_SameSite), nullptr},
+    {"force caching all pages (experimental)", kBackForwardCache_ForceCaching,
      base::size(kBackForwardCache_ForceCaching), nullptr},
 };
 
@@ -3847,6 +3855,11 @@
     {"download-later", flag_descriptions::kDownloadLaterName,
      flag_descriptions::kDownloadLaterDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(download::features::kDownloadLater)},
+
+    {"download-later-debug-on-wifi",
+     flag_descriptions::kDownloadLaterDebugOnWifiName,
+     flag_descriptions::kDownloadLaterDebugOnWifiNameDescription, kOsAndroid,
+     SINGLE_VALUE_TYPE(download::switches::kDownloadLaterDebugOnWifi)},
 #endif
 
     {"enable-new-download-backend",
@@ -3938,6 +3951,11 @@
      flag_descriptions::kUsePreferredIntervalForVideoDescription, kOsDesktop,
      FEATURE_VALUE_TYPE(features::kUsePreferredIntervalForVideo)},
 
+    {"force-preferred-interval-for-video",
+     flag_descriptions::kForcePreferredIntervalForVideoName,
+     flag_descriptions::kForcePreferredIntervalForVideoDescription, kOsDesktop,
+     FEATURE_VALUE_TYPE(features::kForcePreferredIntervalForVideo)},
+
     {"split-partially-occluded-quads",
      flag_descriptions::kSplitPartiallyOccludedQuadsName,
      flag_descriptions::kSplitPartiallyOccludedQuadsDescription, kOsAll,
@@ -4631,11 +4649,6 @@
      flag_descriptions::kAppServiceAdaptiveIconDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(features::kAppServiceAdaptiveIcon)},
 
-    {"app-service-instance-registry",
-     flag_descriptions::kAppServiceInstanceRegistryName,
-     flag_descriptions::kAppServiceInstanceRegistryDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(features::kAppServiceInstanceRegistry)},
-
     {"app-service-intent-handling",
      flag_descriptions::kAppServiceIntentHandlingName,
      flag_descriptions::kAppServiceIntentHandlingDescription, kOsCrOS,
diff --git a/chrome/browser/android/feed/v2/feed_service_factory.cc b/chrome/browser/android/feed/v2/feed_service_factory.cc
index c67dab7..c056563 100644
--- a/chrome/browser/android/feed/v2/feed_service_factory.cc
+++ b/chrome/browser/android/feed/v2/feed_service_factory.cc
@@ -14,6 +14,7 @@
 #include "chrome/browser/android/feed/v2/refresh_task_scheduler_impl.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/history/history_service_factory.h"
+#include "chrome/browser/offline_pages/offline_page_model_factory.h"
 #include "chrome/browser/offline_pages/prefetch/prefetch_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_key.h"
@@ -68,6 +69,7 @@
   DependsOn(HistoryServiceFactory::GetInstance());
   DependsOn(background_task::BackgroundTaskSchedulerFactory::GetInstance());
   DependsOn(offline_pages::PrefetchServiceFactory::GetInstance());
+  DependsOn(offline_pages::OfflinePageModelFactory::GetInstance());
 }
 
 FeedServiceFactory::~FeedServiceFactory() = default;
@@ -118,6 +120,8 @@
       HistoryServiceFactory::GetForProfile(profile,
                                            ServiceAccessType::IMPLICIT_ACCESS),
       prefetch_service,
+      offline_pages::OfflinePageModelFactory::GetForKey(
+          profile->GetProfileKey()),
       storage_partition->GetURLLoaderFactoryForBrowserProcess(),
       background_task_runner, api_key, chrome_info);
 }
diff --git a/chrome/browser/android/feed/v2/feed_stream_surface.cc b/chrome/browser/android/feed/v2/feed_stream_surface.cc
index 1184d318..b16903d 100644
--- a/chrome/browser/android/feed/v2/feed_stream_surface.cc
+++ b/chrome/browser/android/feed/v2/feed_stream_surface.cc
@@ -76,6 +76,21 @@
   Java_FeedStreamSurface_onStreamUpdated(env, java_ref_, j_data);
 }
 
+void FeedStreamSurface::ReplaceDataStoreEntry(base::StringPiece key,
+                                              base::StringPiece data) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  Java_FeedStreamSurface_replaceDataStoreEntry(
+      env, java_ref_, base::android::ConvertUTF8ToJavaString(env, key),
+      base::android::ToJavaByteArray(
+          env, reinterpret_cast<const uint8_t*>(data.data()), data.size()));
+}
+
+void FeedStreamSurface::RemoveDataStoreEntry(base::StringPiece key) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  Java_FeedStreamSurface_removeDataStoreEntry(
+      env, java_ref_, base::android::ConvertUTF8ToJavaString(env, key));
+}
+
 void FeedStreamSurface::LoadMore(JNIEnv* env,
                                  const JavaParamRef<jobject>& obj,
                                  const JavaParamRef<jobject>& callback_obj) {
diff --git a/chrome/browser/android/feed/v2/feed_stream_surface.h b/chrome/browser/android/feed/v2/feed_stream_surface.h
index 6b4624f..d1528cee 100644
--- a/chrome/browser/android/feed/v2/feed_stream_surface.h
+++ b/chrome/browser/android/feed/v2/feed_stream_surface.h
@@ -28,6 +28,9 @@
 
   // SurfaceInterface implementation.
   void StreamUpdate(const feedui::StreamUpdate& update) override;
+  void ReplaceDataStoreEntry(base::StringPiece key,
+                             base::StringPiece data) override;
+  void RemoveDataStoreEntry(base::StringPiece key) override;
 
   void OnStreamUpdated(const feedui::StreamUpdate& stream_update);
 
diff --git a/chrome/browser/android/headers_classifier.cc b/chrome/browser/android/headers_classifier.cc
deleted file mode 100644
index 88e4bac..0000000
--- a/chrome/browser/android/headers_classifier.cc
+++ /dev/null
@@ -1,38 +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 "base/android/jni_string.h"
-#include "base/hash/hash.h"
-#include "base/metrics/histogram_functions.h"
-#include "base/strings/string_util.h"
-#include "chrome/android/chrome_jni_headers/IntentHeadersRecorder_jni.h"
-#include "services/network/public/cpp/cors/cors.h"
-
-using base::android::JavaParamRef;
-
-namespace chrome {
-namespace android {
-
-jboolean JNI_IntentHeadersRecorder_IsCorsSafelistedHeader(
-    JNIEnv* env,
-    const JavaParamRef<jstring>& j_header_name,
-    const JavaParamRef<jstring>& j_header_value,
-    jboolean is_first_party) {
-  std::string header_name(ConvertJavaStringToUTF8(env, j_header_name));
-  std::string header_value(ConvertJavaStringToUTF8(env, j_header_value));
-
-  if (network::cors::IsCorsSafelistedHeader(header_name, header_value))
-    return true;
-
-  if (is_first_party) {
-    base::UmaHistogramSparse(
-        "Android.IntentNonSafelistedHeaderNames",
-        base::PersistentHash(base::ToLowerASCII(header_name)));
-  }
-
-  return false;
-}
-
-}  // namespace android
-}  // namespace chrome
diff --git a/chrome/browser/android/intent_handler.cc b/chrome/browser/android/intent_handler.cc
new file mode 100644
index 0000000..43be685
--- /dev/null
+++ b/chrome/browser/android/intent_handler.cc
@@ -0,0 +1,26 @@
+// 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 "base/android/jni_string.h"
+#include "base/strings/string_util.h"
+#include "chrome/android/chrome_jni_headers/IntentHandler_jni.h"
+#include "services/network/public/cpp/cors/cors.h"
+
+using base::android::JavaParamRef;
+
+namespace chrome {
+namespace android {
+
+jboolean JNI_IntentHandler_IsCorsSafelistedHeader(
+    JNIEnv* env,
+    const JavaParamRef<jstring>& j_header_name,
+    const JavaParamRef<jstring>& j_header_value) {
+  std::string header_name(ConvertJavaStringToUTF8(env, j_header_name));
+  std::string header_value(ConvertJavaStringToUTF8(env, j_header_value));
+
+  return network::cors::IsCorsSafelistedHeader(header_name, header_value);
+}
+
+}  // namespace android
+}  // namespace chrome
diff --git a/chrome/browser/android/resource_id.h b/chrome/browser/android/resource_id.h
index 438fcb8..8a86e37c 100644
--- a/chrome/browser/android/resource_id.h
+++ b/chrome/browser/android/resource_id.h
@@ -68,9 +68,9 @@
 DECLARE_RESOURCE_ID(IDR_ANDROID_AUTOFILL_CC_SCAN_NEW,
                     R.drawable.ic_photo_camera_black)
 DECLARE_RESOURCE_ID(IDR_ANDROID_AUTOFILL_HTTP_WARNING,
-                    R.drawable.ic_info_outline_grey)
+                    R.drawable.ic_info_outline_grey_16dp)
 DECLARE_RESOURCE_ID(IDR_ANDROID_AUTOFILL_HTTPS_INVALID_WARNING,
-                    R.drawable.ic_warning_red)
+                    R.drawable.ic_warning_red_16dp)
 
 // We display settings and edit icon for keyboard accessory using Android's
 // |VectorDrawableCompat|. We do not display these icons for autofill popup.
diff --git a/chrome/browser/apps/app_service/arc_apps.cc b/chrome/browser/apps/app_service/arc_apps.cc
index 827ad13..78ab415 100644
--- a/chrome/browser/apps/app_service/arc_apps.cc
+++ b/chrome/browser/apps/app_service/arc_apps.cc
@@ -1377,10 +1377,6 @@
 }
 
 void ArcApps::CloseTasks(const std::string& app_id) {
-  if (!base::FeatureList::IsEnabled(features::kAppServiceInstanceRegistry)) {
-    return;
-  }
-
   if (!base::Contains(app_id_to_task_ids_, app_id)) {
     return;
   }
diff --git a/chrome/browser/apps/app_service/extension_apps_chromeos.cc b/chrome/browser/apps/app_service/extension_apps_chromeos.cc
index 391fd74c..8176b0a 100644
--- a/chrome/browser/apps/app_service/extension_apps_chromeos.cc
+++ b/chrome/browser/apps/app_service/extension_apps_chromeos.cc
@@ -778,10 +778,6 @@
 
 bool ExtensionAppsChromeOs::ShouldRecordAppWindowActivity(
     extensions::AppWindow* app_window) {
-  if (!base::FeatureList::IsEnabled(features::kAppServiceInstanceRegistry)) {
-    return false;
-  }
-
   DCHECK(app_window);
 
   const extensions::Extension* extension = app_window->GetExtension();
diff --git a/chrome/browser/apps/app_service/lacros_apps.cc b/chrome/browser/apps/app_service/lacros_apps.cc
index c9f14eea..5349585 100644
--- a/chrome/browser/apps/app_service/lacros_apps.cc
+++ b/chrome/browser/apps/app_service/lacros_apps.cc
@@ -105,7 +105,7 @@
                         apps::mojom::LaunchSource launch_source,
                         int64_t display_id) {
   DCHECK_EQ(extension_misc::kLacrosAppId, app_id);
-  LacrosManager::Get()->Start();
+  LacrosManager::Get()->NewWindow();
 }
 
 void LacrosApps::GetMenuModel(const std::string& app_id,
diff --git a/chrome/browser/bluetooth/web_bluetooth_browsertest.cc b/chrome/browser/bluetooth/web_bluetooth_browsertest.cc
index 0fe600d1..8d21cfe2 100644
--- a/chrome/browser/bluetooth/web_bluetooth_browsertest.cc
+++ b/chrome/browser/bluetooth/web_bluetooth_browsertest.cc
@@ -114,12 +114,11 @@
   FakeBluetoothGattService(device::MockBluetoothDevice* device,
                            const std::string& identifier,
                            const device::BluetoothUUID& uuid)
-      : testing::NiceMock<device::MockBluetoothGattService>(device,
-                                                            identifier,
-                                                            uuid,
-                                                            /*is_primary=*/true,
-                                                            /*is_local=*/true) {
-  }
+      : testing::NiceMock<device::MockBluetoothGattService>(
+            device,
+            identifier,
+            uuid,
+            /*is_primary=*/true) {}
 
   // Move-only class
   FakeBluetoothGattService(const FakeBluetoothGattService&) = delete;
diff --git a/chrome/browser/browser_process_impl.h b/chrome/browser/browser_process_impl.h
index 5ac13a0e..7a061cb 100644
--- a/chrome/browser/browser_process_impl.h
+++ b/chrome/browser/browser_process_impl.h
@@ -225,7 +225,6 @@
   void CreateStatusTray();
   void CreateBackgroundModeManager();
   void CreateGCMDriver();
-  void CreatePhysicalWebDataSource();
 
   void ApplyDefaultBrowserPolicy();
 
diff --git a/chrome/browser/browsing_data/cookies_tree_model.cc b/chrome/browser/browsing_data/cookies_tree_model.cc
index ba0bd3b..1fbd942 100644
--- a/chrome/browser/browsing_data/cookies_tree_model.cc
+++ b/chrome/browser/browsing_data/cookies_tree_model.cc
@@ -676,7 +676,7 @@
 
     if (container) {
       container->service_worker_helper_->DeleteServiceWorkers(
-          usage_info_->origin.GetURL());
+          usage_info_->origin);
       container->service_worker_info_list_.erase(usage_info_);
     }
   }
diff --git a/chrome/browser/chrome_browser_main.cc b/chrome/browser/chrome_browser_main.cc
index 11f12554..81d9c90 100644
--- a/chrome/browser/chrome_browser_main.cc
+++ b/chrome/browser/chrome_browser_main.cc
@@ -247,7 +247,9 @@
 #if defined(OS_WIN) || defined(OS_MACOSX) || \
     (defined(OS_LINUX) && !defined(OS_CHROMEOS))
 #include "chrome/browser/metrics/desktop_session_duration/desktop_session_duration_tracker.h"
+#include "chrome/browser/metrics/desktop_session_duration/touch_mode_stats_tracker.h"
 #include "chrome/browser/profiles/profile_activity_metrics_recorder.h"
+#include "ui/base/pointer/touch_ui_controller.h"
 #endif
 
 #if BUILDFLAG(ENABLE_BACKGROUND_MODE)
@@ -956,6 +958,9 @@
     (defined(OS_LINUX) && !defined(OS_CHROMEOS))
   metrics::DesktopSessionDurationTracker::Initialize();
   ProfileActivityMetricsRecorder::Initialize();
+  TouchModeStatsTracker::Initialize(
+      metrics::DesktopSessionDurationTracker::Get(),
+      ui::TouchUiController::Get());
 #endif
   metrics::RendererUptimeTracker::Initialize();
 
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index c29a6a1..f07cfe6 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -1674,11 +1674,11 @@
 // effective URLs on the IO thread, and wind up killing processes that e.g.
 // request cookies for their actual URL. This whole function (and its
 // ExtensionsPart) should be removed once we add that ability to the IO thread.
-bool ChromeContentBrowserClient::ShouldLockToOrigin(
+bool ChromeContentBrowserClient::ShouldLockProcess(
     content::BrowserContext* browser_context,
     const GURL& effective_site_url) {
 #if BUILDFLAG(ENABLE_EXTENSIONS)
-  if (!ChromeContentBrowserClientExtensionsPart::ShouldLockToOrigin(
+  if (!ChromeContentBrowserClientExtensionsPart::ShouldLockProcess(
           browser_context, effective_site_url)) {
     return false;
   }
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index fbdf141..98f93a01 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -154,8 +154,8 @@
                                        const GURL& site_url) override;
   bool DoesSiteRequireDedicatedProcess(content::BrowserContext* browser_context,
                                        const GURL& effective_site_url) override;
-  bool ShouldLockToOrigin(content::BrowserContext* browser_context,
-                          const GURL& effective_site_url) override;
+  bool ShouldLockProcess(content::BrowserContext* browser_context,
+                         const GURL& effective_site_url) override;
   bool DoesWebUISchemeRequireProcessLock(base::StringPiece scheme) override;
   bool ShouldTreatURLSchemeAsFirstPartyWhenTopLevel(
       base::StringPiece scheme,
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 51400c7..c22990fb 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -3049,6 +3049,7 @@
     "extensions/signin_screen_policy_provider_unittest.cc",
     "extensions/wallpaper_private_api_unittest.cc",
     "external_metrics_unittest.cc",
+    "file_manager/app_service_file_tasks_unittest.cc",
     "file_manager/documents_provider_root_manager_unittest.cc",
     "file_manager/file_tasks_notifier_unittest.cc",
     "file_manager/file_tasks_unittest.cc",
@@ -3525,6 +3526,7 @@
     "//components/prefs:test_support",
     "//components/renderer_context_menu",
     "//components/resources",
+    "//components/services/app_service/public/cpp:test_support",
     "//components/session_manager/core",
     "//components/signin/public/identity_manager:test_support",
     "//components/sync",
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 48d9496f..d2a2b48 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
@@ -10,7 +10,6 @@
 #include "ash/public/cpp/window_pin_type.h"
 #include "ash/public/cpp/window_properties.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/arc_util.h"
 #include "components/arc/metrics/arc_metrics_constants.h"
 #include "ui/aura/env.h"
diff --git a/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.cc b/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.cc
index 315f5cb..18ccaae 100644
--- a/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.cc
+++ b/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.cc
@@ -60,6 +60,9 @@
 
 void AXTreeSourceArc::NotifyAccessibilityEvent(AXEventData* event_data) {
   root_id_.reset();
+  tree_map_.clear();
+  parent_map_.clear();
+  computed_bounds_.clear();
 
   window_id_ = event_data->window_id;
   is_notification_ = event_data->notification_key.has_value();
diff --git a/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.h b/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.h
index 971a935e..db2763b 100644
--- a/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.h
+++ b/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.h
@@ -146,7 +146,7 @@
                AccessibilityInfoDataWrapper* info_data2) const override;
   AccessibilityInfoDataWrapper* GetNull() const override;
 
-  // AXActionHandler:
+  // AXActionHandlerBase:
   void PerformAction(const ui::AXActionData& data) override;
 
   // Maps an AccessibilityInfoDataWrapper ID to its tree data.
diff --git a/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc_unittest.cc b/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc_unittest.cc
index c3f0705..adba50c 100644
--- a/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc_unittest.cc
+++ b/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc_unittest.cc
@@ -1227,4 +1227,37 @@
   CallNotifyAccessibilityEvent(event.get());
 }
 
+TEST_F(AXTreeSourceArcTest, EnsureNodeIdMapCleared) {
+  auto event = AXEventData::New();
+  event->source_id = 1;
+  event->task_id = 1;
+
+  event->window_data = std::vector<mojom::AccessibilityWindowInfoDataPtr>();
+  event->window_data->push_back(AXWindowInfoData::New());
+  AXWindowInfoData* root_window = event->window_data->back().get();
+  root_window->window_id = 2;
+  root_window->root_node_id = 1;
+
+  event->node_data.push_back(AXNodeInfoData::New());
+  AXNodeInfoData* node = event->node_data.back().get();
+  node->id = 1;
+
+  event->event_type = AXEventType::VIEW_SELECTED;
+  CallNotifyAccessibilityEvent(event.get());
+
+  // Ensures that the first event is dropped while handling it.
+  EXPECT_EQ(0, GetDispatchedEventCount(ax::mojom::Event::kFocus));
+  EXPECT_EQ(0, GetDispatchedEventCount(ax::mojom::Event::kValueChanged));
+
+  event->event_type = AXEventType::WINDOW_CONTENT_CHANGED;
+  // Swaps ids of node and root_window.
+  event->source_id = 2;
+  root_window->window_id = 1;
+  root_window->root_node_id = 2;
+  node->id = 2;
+
+  // If the previous node id mapping remains, this will enter infinite loop.
+  CallNotifyAccessibilityEvent(event.get());
+}
+
 }  // namespace arc
diff --git a/chrome/browser/chromeos/arc/tracing/arc_app_performance_tracing_test_helper.cc b/chrome/browser/chromeos/arc/tracing/arc_app_performance_tracing_test_helper.cc
index e690fe1..a4af339cd 100644
--- a/chrome/browser/chromeos/arc/tracing/arc_app_performance_tracing_test_helper.cc
+++ b/chrome/browser/chromeos/arc/tracing/arc_app_performance_tracing_test_helper.cc
@@ -48,7 +48,7 @@
   views::Widget* widget = new views::Widget();
   widget->Init(std::move(params));
   // Set ARC id before showing the window to be recognized in
-  // ArcAppWindowLauncherController.
+  // AppServiceAppWindowLauncherController.
   exo::SetShellApplicationId(widget->GetNativeWindow(), window_app_id);
   exo::SetShellMainSurface(widget->GetNativeWindow(), new exo::Surface());
   widget->Show();
diff --git a/chrome/browser/chromeos/crosapi/lacros_manager.cc b/chrome/browser/chromeos/crosapi/lacros_manager.cc
index 8962a542..63f9d79 100644
--- a/chrome/browser/chromeos/crosapi/lacros_manager.cc
+++ b/chrome/browser/chromeos/crosapi/lacros_manager.cc
@@ -116,7 +116,7 @@
   load_complete_callback_ = std::move(callback);
 }
 
-void LacrosManager::Start() {
+void LacrosManager::NewWindow() {
   if (!lacros_util::IsLacrosAllowed())
     return;
 
@@ -131,25 +131,20 @@
     return;
   }
 
-  // If it is on launching Lacros, wait for its async operation's completion.
-  if (state_ == State::STARTING) {
-    ++num_pending_start_;
+  if (state_ == State::STOPPED) {
+    // If lacros-chrome is not running, launch it.
+    bool succeeded = Start();
+    LOG_IF(ERROR, !succeeded)
+        << "lacros-chrome failed to launch. Cannot open a window";
     return;
   }
 
-  if (state_ == State::RUNNING) {
-    StartForeground(true);
-    return;
-  }
-
-  DCHECK_EQ(state_, State::STOPPED);
-  DCHECK(!lacros_process_.IsValid());
-  state_ = State::STARTING;
-  StartForeground(false);
+  DCHECK(lacros_chrome_service_.is_connected());
+  lacros_chrome_service_->NewWindow(base::DoNothing());
 }
 
-void LacrosManager::StartForeground(bool already_running) {
-  DCHECK(state_ == State::STARTING || state_ == State::RUNNING);
+bool LacrosManager::Start() {
+  DCHECK_EQ(state_, State::STOPPED);
   DCHECK(!lacros_path_.empty());
 
   std::string chrome_path = lacros_path_.MaybeAsASCII() + "/chrome";
@@ -192,46 +187,40 @@
     argv.push_back("--log-file=" + LacrosLogPath().value());
   }
 
-  if (already_running) {
-    DCHECK_EQ(state_, State::RUNNING);
-    DCHECK(lacros_chrome_service_.is_connected());
-    // If Lacros is already running, then the new call to launch process spawns
-    // a new window but does not create a lasting process.
-    lacros_chrome_service_->NewWindow(base::DoNothing());
-  } else {
-    DCHECK_EQ(state_, State::STARTING);
-    // Set up Mojo channel.
-    base::CommandLine command_line(argv);
-    mojo::PlatformChannel channel;
-    channel.PrepareToPassRemoteEndpoint(&options, &command_line);
+  // Set up Mojo channel.
+  base::CommandLine command_line(argv);
+  mojo::PlatformChannel channel;
+  channel.PrepareToPassRemoteEndpoint(&options, &command_line);
 
-    base::RecordAction(base::UserMetricsAction("Lacros.Launch"));
-    // If lacros_process_ already exists, because it does not call waitpid(2),
-    // the process will never be collected.
-    // TODO(hidehiko): Fix the case by collecting the processes.
-    lacros_process_ = base::LaunchProcess(command_line, options);
-
-    // TODO(hidehiko): Clean up the set-up procedure.
-    // Replacing the "already_running" case by Mojo call allows us to
-    // simplify the code.
-    if (lacros_process_.IsValid()) {
-      channel.RemoteProcessLaunchAttempted();
-      mojo::OutgoingInvitation invitation;
-      mojo::Remote<mojo_base::mojom::Binder> binder(
-          mojo::PendingRemote<mojo_base::mojom::Binder>(
-              invitation.AttachMessagePipe(0), /*version=*/0));
-      mojo::OutgoingInvitation::Send(std::move(invitation),
-                                     lacros_process_.Handle(),
-                                     channel.TakeLocalEndpoint());
-      binder->Bind(lacros_chrome_service_.BindNewPipeAndPassReceiver());
-      lacros_chrome_service_.set_disconnect_handler(base::BindOnce(
-          &LacrosManager::OnMojoDisconnected, weak_factory_.GetWeakPtr()));
-      lacros_chrome_service_->RequestAshChromeServiceReceiver(
-          base::BindOnce(&LacrosManager::OnAshChromeServiceReceiverReceived,
-                         weak_factory_.GetWeakPtr()));
-    }
-    LOG(WARNING) << "Launched lacros-chrome with pid " << lacros_process_.Pid();
+  // Create the lacros-chrome subprocess.
+  base::RecordAction(base::UserMetricsAction("Lacros.Launch"));
+  // If lacros_process_ already exists, because it does not call waitpid(2),
+  // the process will never be collected.
+  lacros_process_ = base::LaunchProcess(command_line, options);
+  if (!lacros_process_.IsValid()) {
+    LOG(ERROR) << "Failed to launch lacros-chrome";
+    return false;
   }
+  state_ = State::STARTING;
+  LOG(WARNING) << "Launched lacros-chrome with pid " << lacros_process_.Pid();
+
+  // Invite the lacros-chrome to the mojo universe, and bind
+  // LacrosChromeService and AshChromeService interfaces to each other.
+  channel.RemoteProcessLaunchAttempted();
+  mojo::OutgoingInvitation invitation;
+  mojo::Remote<mojo_base::mojom::Binder> binder(
+      mojo::PendingRemote<mojo_base::mojom::Binder>(
+          invitation.AttachMessagePipe(0), /*version=*/0));
+  mojo::OutgoingInvitation::Send(std::move(invitation),
+                                 lacros_process_.Handle(),
+                                 channel.TakeLocalEndpoint());
+  binder->Bind(lacros_chrome_service_.BindNewPipeAndPassReceiver());
+  lacros_chrome_service_.set_disconnect_handler(base::BindOnce(
+      &LacrosManager::OnMojoDisconnected, weak_factory_.GetWeakPtr()));
+  lacros_chrome_service_->RequestAshChromeServiceReceiver(
+      base::BindOnce(&LacrosManager::OnAshChromeServiceReceiverReceived,
+                     weak_factory_.GetWeakPtr()));
+  return true;
 }
 
 void LacrosManager::OnAshChromeServiceReceiverReceived(
@@ -241,17 +230,6 @@
       std::make_unique<AshChromeServiceImpl>(std::move(pending_receiver));
   state_ = State::RUNNING;
   LOG(WARNING) << "Connection to lacros-chrome is established.";
-
-  // If Start() is called during launching lacros-chrome,
-  // re-call Start() which will trigger to open new windows.
-  // TODO(hidehiko): Simplify the logic. Introducing a Mojo API to control
-  // window opening helps it.
-  if (num_pending_start_ > 0) {
-    int num_pending_start = num_pending_start_;
-    num_pending_start_ = 0;
-    for (int i = 0; i < num_pending_start; ++i)
-      Start();
-  }
 }
 
 void LacrosManager::OnMojoDisconnected() {
diff --git a/chrome/browser/chromeos/crosapi/lacros_manager.h b/chrome/browser/chromeos/crosapi/lacros_manager.h
index 053be15..af85575 100644
--- a/chrome/browser/chromeos/crosapi/lacros_manager.h
+++ b/chrome/browser/chromeos/crosapi/lacros_manager.h
@@ -60,7 +60,7 @@
   // class, so there's no way for callers to handle such error cases properly.
   // This design often leads the flakiness behavior of the product and testing,
   // so should be avoided.
-  void Start();
+  void NewWindow();
 
  private:
   enum class State {
@@ -91,12 +91,10 @@
     TERMINATING,
   };
 
-  // Starting Lacros requires a hop to a background thread. The flow is
-  // Start(), then StartBackground() in (the anonymous namespace),
-  // then StartForeground().
-  // The parameter |already_running| refers to whether the Lacros binary is
-  // already launched and running.
-  void StartForeground(bool already_running);
+  // Starts the lacros-chrome process. Returns whether the subprocess is
+  // created. Note that the subprocess may be crashed immediately, even if this
+  // returns true. This can be called only in STOPPED state.
+  bool Start();
 
   // Called when PendingReceiver of AshChromeService is passed from
   // lacros-chrome.
@@ -121,8 +119,6 @@
 
   State state_ = State::NOT_INITIALIZED;
 
-  int num_pending_start_ = 0;
-
   // May be null in tests.
   scoped_refptr<component_updater::CrOSComponentManager> component_manager_;
 
diff --git a/chrome/browser/chromeos/crostini/crostini_manager.cc b/chrome/browser/chromeos/crostini/crostini_manager.cc
index e9a4903..54e8055 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager.cc
+++ b/chrome/browser/chromeos/crostini/crostini_manager.cc
@@ -1867,7 +1867,7 @@
     std::string desktop_file_id,
     const std::vector<std::string>& files,
     bool display_scaled,
-    BoolCallback callback) {
+    CrostiniSuccessCallback callback) {
   vm_tools::cicerone::LaunchContainerApplicationRequest request;
   request.set_owner_id(owner_id_);
   request.set_vm_name(container_id.vm_name);
@@ -3147,22 +3147,17 @@
 }
 
 void CrostiniManager::OnLaunchContainerApplication(
-    BoolCallback callback,
+    CrostiniSuccessCallback callback,
     base::Optional<vm_tools::cicerone::LaunchContainerApplicationResponse>
         response) {
   if (!response) {
     LOG(ERROR) << "Failed to launch application. Empty response.";
-    std::move(callback).Run(/*success=*/false);
+    std::move(callback).Run(/*success=*/false,
+                            "Failed to launch application. Empty response.");
     return;
   }
 
-  if (!response->success()) {
-    LOG(ERROR) << "Failed to launch application: "
-               << response->failure_reason();
-    std::move(callback).Run(/*success=*/false);
-    return;
-  }
-  std::move(callback).Run(/*success=*/true);
+  std::move(callback).Run(response->success(), response->failure_reason());
 }
 
 void CrostiniManager::OnGetContainerAppIcons(
diff --git a/chrome/browser/chromeos/crostini/crostini_manager.h b/chrome/browser/chromeos/crostini/crostini_manager.h
index 37d804c..43823f8 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager.h
+++ b/chrome/browser/chromeos/crostini/crostini_manager.h
@@ -370,7 +370,7 @@
                                   std::string desktop_file_id,
                                   const std::vector<std::string>& files,
                                   bool display_scaled,
-                                  BoolCallback callback);
+                                  CrostiniSuccessCallback callback);
 
   // Asynchronously gets app icons as specified by their desktop file ids.
   // |callback| is called after the method call finishes.
@@ -794,7 +794,7 @@
 
   // Callback for CrostiniManager::LaunchContainerApplication.
   void OnLaunchContainerApplication(
-      BoolCallback callback,
+      CrostiniSuccessCallback callback,
       base::Optional<vm_tools::cicerone::LaunchContainerApplicationResponse>
           response);
 
diff --git a/chrome/browser/chromeos/crostini/crostini_simple_types.h b/chrome/browser/chromeos/crostini/crostini_simple_types.h
index 9fbeaa7..6d1e206 100644
--- a/chrome/browser/chromeos/crostini/crostini_simple_types.h
+++ b/chrome/browser/chromeos/crostini/crostini_simple_types.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/callback.h"
 #include "base/files/file_path.h"
 #include "chromeos/dbus/concierge/concierge_service.pb.h"
 
@@ -83,6 +84,9 @@
   // the top of this enum.
 };
 
+using CrostiniSuccessCallback =
+    base::OnceCallback<void(bool success, const std::string& failure_reason)>;
+
 enum class InstallLinuxPackageProgressStatus {
   SUCCEEDED,
   FAILED,
diff --git a/chrome/browser/chromeos/crostini/crostini_terminal.cc b/chrome/browser/chromeos/crostini/crostini_terminal.cc
index c0cff7893..bf96dd22 100644
--- a/chrome/browser/chromeos/crostini/crostini_terminal.cc
+++ b/chrome/browser/chromeos/crostini/crostini_terminal.cc
@@ -45,28 +45,34 @@
 
 GURL GenerateVshInCroshUrl(Profile* profile,
                            const ContainerId& container_id,
+                           const std::string& cwd,
                            const std::vector<std::string>& terminal_args) {
   std::string vsh_crosh = base::StrCat({chrome::kChromeUIUntrustedTerminalURL,
                                         "html/terminal.html?command=vmshell"});
   std::string vm_name_param = net::EscapeQueryParamValue(
-      base::StringPrintf("--vm_name=%s", container_id.vm_name.c_str()), false);
+      base::StringPrintf("--vm_name=%s", container_id.vm_name.c_str()),
+      /*use_plus=*/true);
   std::string container_name_param = net::EscapeQueryParamValue(
       base::StringPrintf("--target_container=%s",
                          container_id.container_name.c_str()),
-      false);
+      /*use_plus=*/true);
   std::string owner_id_param = net::EscapeQueryParamValue(
       base::StringPrintf("--owner_id=%s",
                          CryptohomeIdForProfile(profile).c_str()),
-      false);
+      /*use_plus=*/true);
 
   std::vector<std::string> pieces = {vsh_crosh, vm_name_param,
                                      container_name_param, owner_id_param};
+  if (!cwd.empty()) {
+    pieces.push_back(net::EscapeQueryParamValue(
+        base::StringPrintf("--cwd=%s", cwd.c_str()), /*use_plus=*/true));
+  }
   if (!terminal_args.empty()) {
     // Separates the command args from the args we are passing into the
     // terminal to be executed.
     pieces.push_back("--");
     for (auto arg : terminal_args) {
-      pieces.push_back(net::EscapeQueryParamValue(arg, false));
+      pieces.push_back(net::EscapeQueryParamValue(arg, /*use_plus=*/true));
     }
   }
 
@@ -78,9 +84,10 @@
 Browser* LaunchTerminal(Profile* profile,
                         int64_t display_id,
                         const ContainerId& container_id,
+                        const std::string& cwd,
                         const std::vector<std::string>& terminal_args) {
   GURL vsh_in_crosh_url =
-      GenerateVshInCroshUrl(profile, container_id, terminal_args);
+      GenerateVshInCroshUrl(profile, container_id, cwd, terminal_args);
   return web_app::LaunchSystemWebApp(
       profile, web_app::SystemAppType::TERMINAL, vsh_in_crosh_url,
       web_app::CreateSystemWebAppLaunchParams(
diff --git a/chrome/browser/chromeos/crostini/crostini_terminal.h b/chrome/browser/chromeos/crostini/crostini_terminal.h
index 3dca389..43a477f 100644
--- a/chrome/browser/chromeos/crostini/crostini_terminal.h
+++ b/chrome/browser/chromeos/crostini/crostini_terminal.h
@@ -103,6 +103,7 @@
     Profile* profile,
     int64_t display_id = display::kInvalidDisplayId,
     const ContainerId& container_id = ContainerId::GetDefault(),
+    const std::string& cwd = "",
     const std::vector<std::string>& terminal_args = {});
 
 // Launches the terminal settings popup window.
diff --git a/chrome/browser/chromeos/crostini/crostini_util.cc b/chrome/browser/chromeos/crostini/crostini_util.cc
index e94973f..4018202 100644
--- a/chrome/browser/chromeos/crostini/crostini_util.cc
+++ b/chrome/browser/chromeos/crostini/crostini_util.cc
@@ -33,7 +33,6 @@
 #include "chrome/browser/ui/ash/launcher/app_service/app_service_app_window_crostini_tracker.h"
 #include "chrome/browser/ui/ash/launcher/app_service/app_service_app_window_launcher_controller.h"
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
-#include "chrome/browser/ui/ash/launcher/crostini_app_window_shelf_controller.h"
 #include "chrome/browser/ui/ash/launcher/shelf_spinner_controller.h"
 #include "chrome/browser/ui/ash/launcher/shelf_spinner_item_controller.h"
 #include "chrome/browser/ui/browser.h"
@@ -101,21 +100,35 @@
   base::UmaHistogramEnumeration(kCrostiniAppLaunchResultHistogram, reason);
 }
 
-void OnLaunchFailed(const std::string& app_id,
-                    crostini::CrostiniResult reason) {
+void OnApplicationLaunched(const std::string& app_id,
+                           crostini::CrostiniSuccessCallback callback,
+                           const crostini::CrostiniResult failure_result,
+                           bool success,
+                           const std::string& failure_reason) {
   // Remove the spinner so it doesn't stay around forever.
-  // TODO(timloh): Consider also displaying a notification of some sort.
+  // TODO(timloh): Consider also displaying a notification of some sort for
+  // failure.
   ChromeLauncherController* chrome_controller =
       ChromeLauncherController::instance();
   DCHECK(chrome_controller);
   chrome_controller->GetShelfSpinnerController()->CloseSpinner(app_id);
-  RecordAppLaunchResultHistogram(reason);
+  RecordAppLaunchResultHistogram(success ? crostini::CrostiniResult::SUCCESS
+                                         : failure_result);
+  std::move(callback).Run(success, failure_reason);
+}
+
+void OnLaunchFailed(
+    const std::string& app_id,
+    crostini::CrostiniSuccessCallback callback,
+    const std::string& failure_reason,
+    crostini::CrostiniResult result = crostini::CrostiniResult::UNKNOWN_ERROR) {
+  OnApplicationLaunched(app_id, std::move(callback), result, false,
+                        failure_reason);
 }
 
 void OnCrostiniRestarted(Profile* profile,
                          crostini::ContainerId container_id,
                          const std::string& app_id,
-                         Browser* browser,
                          base::OnceClosure callback,
                          crostini::CrostiniResult result) {
   if (crostini::MaybeShowCrostiniDialogBeforeLaunch(profile, result)) {
@@ -124,46 +137,42 @@
   }
 
   if (result != crostini::CrostiniResult::SUCCESS) {
-    OnLaunchFailed(app_id, result);
-    if (browser && browser->window())
-      browser->window()->Close();
-    return;
+    OnLaunchFailed(app_id, base::DoNothing(), "", result);
   }
   std::move(callback).Run();
 }
 
-void OnApplicationLaunched(crostini::LaunchCrostiniAppCallback callback,
-                           const std::string& app_id,
-                           bool success) {
-  if (!success) {
-    OnLaunchFailed(app_id, crostini::CrostiniResult::UNKNOWN_ERROR);
-  } else {
-    RecordAppLaunchResultHistogram(crostini::CrostiniResult::SUCCESS);
-  }
-  std::move(callback).Run(success, success ? "" : "Failed to launch " + app_id);
-}
-
 void OnSharePathForLaunchApplication(
     Profile* profile,
     const std::string& app_id,
     guest_os::GuestOsRegistryService::Registration registration,
+    int64_t display_id,
     const std::vector<std::string>& files,
     bool display_scaled,
-    crostini::LaunchCrostiniAppCallback callback,
+    crostini::CrostiniSuccessCallback callback,
     bool success,
     const std::string& failure_reason) {
   if (!success) {
-    OnLaunchFailed(app_id, crostini::CrostiniResult::UNKNOWN_ERROR);
-    return std::move(callback).Run(
-        success, success ? ""
-                         : "Failed to share paths to launch " + app_id + ":" +
-                               failure_reason);
+    return OnLaunchFailed(
+        app_id, std::move(callback),
+        "failed to share paths to launch " + app_id + ":" + failure_reason);
+  }
+  const crostini::ContainerId container_id(registration.VmName(),
+                                           registration.ContainerName());
+  if (app_id == kCrostiniTerminalSystemAppId) {
+    // Use first file as 'cwd'.
+    std::string cwd = !files.empty() ? files[0] : "";
+    if (!LaunchTerminal(profile, display_id, container_id, cwd)) {
+      return OnLaunchFailed(app_id, std::move(callback),
+                            "failed to launch terminal");
+    }
+    return OnApplicationLaunched(app_id, std::move(callback),
+                                 crostini::CrostiniResult::SUCCESS, true, "");
   }
   crostini::CrostiniManager::GetForProfile(profile)->LaunchContainerApplication(
-      crostini::ContainerId(registration.VmName(),
-                            registration.ContainerName()),
-      registration.DesktopFileId(), files, display_scaled,
-      base::BindOnce(OnApplicationLaunched, std::move(callback), app_id));
+      container_id, registration.DesktopFileId(), files, display_scaled,
+      base::BindOnce(OnApplicationLaunched, app_id, std::move(callback),
+                     crostini::CrostiniResult::UNKNOWN_ERROR));
 }
 
 void LaunchApplication(
@@ -173,23 +182,16 @@
     int64_t display_id,
     const std::vector<storage::FileSystemURL>& files,
     bool display_scaled,
-    crostini::LaunchCrostiniAppCallback callback) {
+    crostini::CrostiniSuccessCallback callback) {
   ChromeLauncherController* chrome_launcher_controller =
       ChromeLauncherController::instance();
   DCHECK(chrome_launcher_controller);
 
-  if (base::FeatureList::IsEnabled(features::kAppServiceInstanceRegistry)) {
-    AppServiceAppWindowLauncherController* app_service_controller =
-        chrome_launcher_controller->app_service_app_window_controller();
-    DCHECK(app_service_controller);
-    app_service_controller->app_service_crostini_tracker()
-        ->OnAppLaunchRequested(app_id, display_id);
-  } else {
-    CrostiniAppWindowShelfController* shelf_controller =
-        chrome_launcher_controller->crostini_app_window_shelf_controller();
-    DCHECK(shelf_controller);
-    shelf_controller->OnAppLaunchRequested(app_id, display_id);
-  }
+  AppServiceAppWindowLauncherController* app_service_controller =
+      chrome_launcher_controller->app_service_app_window_controller();
+  DCHECK(app_service_controller);
+  app_service_controller->app_service_crostini_tracker()->OnAppLaunchRequested(
+      app_id, display_id);
 
   // Share any paths not in crostini.  The user will see the spinner while this
   // is happening.
@@ -199,9 +201,9 @@
     base::FilePath path;
     if (!file_manager::util::ConvertFileSystemURLToPathInsideCrostini(
             profile, url, &path)) {
-      OnLaunchFailed(app_id, crostini::CrostiniResult::UNKNOWN_ERROR);
-      return std::move(callback).Run(
-          false, "Cannot share file with crostini: " + url.DebugString());
+      return OnLaunchFailed(
+          app_id, std::move(callback),
+          "Cannot share file with crostini: " + url.DebugString());
     }
     if (url.mount_filesystem_id() !=
         file_manager::util::GetCrostiniMountPointName(profile)) {
@@ -212,14 +214,16 @@
 
   if (paths_to_share.empty()) {
     OnSharePathForLaunchApplication(profile, app_id, std::move(registration),
-                                    std::move(files_to_launch), display_scaled,
-                                    std::move(callback), true, "");
+                                    display_id, std::move(files_to_launch),
+                                    display_scaled, std::move(callback), true,
+                                    "");
   } else {
     guest_os::GuestOsSharePath::GetForProfile(profile)->SharePaths(
         registration.VmName(), std::move(paths_to_share), /*persist=*/false,
         base::BindOnce(OnSharePathForLaunchApplication, profile, app_id,
-                       std::move(registration), std::move(files_to_launch),
-                       display_scaled, std::move(callback)));
+                       std::move(registration), display_id,
+                       std::move(files_to_launch), display_scaled,
+                       std::move(callback)));
   }
 }
 
@@ -284,12 +288,6 @@
                  kCrostiniDefaultVmName, kCrostiniDefaultContainerName));
 }
 
-void LaunchCrostiniApp(Profile* profile,
-                       const std::string& app_id,
-                       int64_t display_id) {
-  LaunchCrostiniApp(profile, app_id, display_id, {}, base::DoNothing());
-}
-
 void AddSpinner(crostini::CrostiniManager::RestartId restart_id,
                 const std::string& app_id,
                 Profile* profile) {
@@ -320,7 +318,7 @@
     int64_t display_id,
     const std::vector<storage::FileSystemURL>& files,
     base::Optional<guest_os::GuestOsRegistryService::Registration> registration,
-    LaunchCrostiniAppCallback callback) {
+    CrostiniSuccessCallback callback) {
   auto* crostini_manager = crostini::CrostiniManager::GetForProfile(profile);
   auto* registry_service =
       guest_os::GuestOsRegistryServiceFactory::GetForProfile(profile);
@@ -329,33 +327,44 @@
                                  registration->ContainerName());
 
   base::OnceClosure launch_closure;
-  Browser* browser = nullptr;
   if (app_id == kCrostiniTerminalSystemAppId) {
-    DCHECK(files.empty());
-    RecordAppLaunchHistogram(CrostiniAppLaunchAppType::kTerminal);
-
-    auto* browser = LaunchTerminal(profile, display_id, container_id);
-    if (browser == nullptr) {
-      RecordAppLaunchResultHistogram(crostini::CrostiniResult::UNKNOWN_ERROR);
-      return std::move(callback).Run(false, "failed to launch terminal");
+    // If terminal is launched with a 'cwd' file, we may need to launch the VM
+    // and share the path before launching terminal.
+    bool requires_share = false;
+    base::FilePath cwd;
+    if (!files.empty()) {
+      if (files[0].mount_filesystem_id() !=
+          file_manager::util::GetCrostiniMountPointName(profile)) {
+        requires_share = true;
+      } else {
+        file_manager::util::ConvertFileSystemURLToPathInsideCrostini(
+            profile, files[0], &cwd);
+      }
     }
-    RecordAppLaunchResultHistogram(crostini::CrostiniResult::SUCCESS);
-    return std::move(callback).Run(true, "");
-  } else {
-    RecordAppLaunchHistogram(CrostiniAppLaunchAppType::kRegisteredApp);
-    launch_closure =
-        base::BindOnce(&LaunchApplication, profile, app_id,
-                       std::move(*registration), display_id, std::move(files),
-                       registration->IsScaled(), std::move(callback));
+
+    if (!requires_share) {
+      RecordAppLaunchHistogram(CrostiniAppLaunchAppType::kTerminal);
+      if (!LaunchTerminal(profile, display_id, container_id, cwd.value())) {
+        RecordAppLaunchResultHistogram(crostini::CrostiniResult::UNKNOWN_ERROR);
+        return std::move(callback).Run(false, "failed to launch terminal");
+      }
+      RecordAppLaunchResultHistogram(crostini::CrostiniResult::SUCCESS);
+      return std::move(callback).Run(true, "");
+    }
   }
 
+  RecordAppLaunchHistogram(CrostiniAppLaunchAppType::kRegisteredApp);
+  launch_closure = base::BindOnce(
+      &LaunchApplication, profile, app_id, std::move(*registration), display_id,
+      std::move(files), registration->IsScaled(), std::move(callback));
+
   // Update the last launched time and Termina version.
   registry_service->AppLaunched(app_id);
   crostini_manager->UpdateLaunchMetricsForEnterpriseReporting();
 
   auto restart_id = crostini_manager->RestartCrostini(
       container_id, base::BindOnce(OnCrostiniRestarted, profile, container_id,
-                                   app_id, browser, std::move(launch_closure)));
+                                   app_id, std::move(launch_closure)));
 
   base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
       FROM_HERE, base::BindOnce(&AddSpinner, restart_id, app_id, profile),
@@ -366,7 +375,7 @@
                        const std::string& app_id,
                        int64_t display_id,
                        const std::vector<storage::FileSystemURL>& files,
-                       LaunchCrostiniAppCallback callback) {
+                       CrostiniSuccessCallback callback) {
   // Policies can change under us, and crostini may now be forbidden.
   if (!CrostiniFeatures::Get()->IsUIAllowed(profile)) {
     return std::move(callback).Run(false, "Crostini UI not allowed");
@@ -394,9 +403,9 @@
 
   if (crostini_manager->IsUncleanStartup()) {
     // Prompt for user-restart.
-    return ShowCrostiniRecoveryView(profile,
-                                    crostini::CrostiniUISurface::kAppList,
-                                    app_id, display_id, std::move(callback));
+    return ShowCrostiniRecoveryView(
+        profile, crostini::CrostiniUISurface::kAppList, app_id, display_id,
+        files, std::move(callback));
   }
 
   if (crostini_manager->ShouldPromptContainerUpgrade(
diff --git a/chrome/browser/chromeos/crostini/crostini_util.h b/chrome/browser/chromeos/crostini/crostini_util.h
index 9583ab8..0694878c 100644
--- a/chrome/browser/chromeos/crostini/crostini_util.h
+++ b/chrome/browser/chromeos/crostini/crostini_util.h
@@ -9,6 +9,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/bind_helpers.h"
 #include "base/callback.h"
 #include "base/files/file_path.h"
 #include "base/optional.h"
@@ -70,9 +71,6 @@
 std::ostream& operator<<(std::ostream& ostream,
                          const ContainerId& container_id);
 
-using LaunchCrostiniAppCallback =
-    base::OnceCallback<void(bool success, const std::string& failure_reason)>;
-
 // Checks if user profile is able to a crostini app with a given app_id.
 bool IsUninstallable(Profile* profile, const std::string& app_id);
 
@@ -90,20 +88,14 @@
 bool MaybeShowCrostiniDialogBeforeLaunch(Profile* profile,
                                          CrostiniResult result);
 
-// Launches the Crostini app with ID of |app_id| on the display with ID of
-// |display_id|. |app_id| should be a valid Crostini app list id.
-void LaunchCrostiniApp(Profile* profile,
-                       const std::string& app_id,
-                       int64_t display_id);
-
 // Launch a Crostini App with a given set of files, given as absolute paths in
 // the container. For apps which can only be launched with a single file,
 // launch multiple instances.
 void LaunchCrostiniApp(Profile* profile,
                        const std::string& app_id,
                        int64_t display_id,
-                       const std::vector<storage::FileSystemURL>& files,
-                       LaunchCrostiniAppCallback callback);
+                       const std::vector<storage::FileSystemURL>& files = {},
+                       CrostiniSuccessCallback callback = base::DoNothing());
 
 // Retrieves cryptohome_id from profile.
 std::string CryptohomeIdForProfile(Profile* profile);
@@ -179,7 +171,8 @@
                               CrostiniUISurface ui_surface,
                               const std::string& app_id,
                               int64_t display_id,
-                              LaunchCrostiniAppCallback callback);
+                              const std::vector<storage::FileSystemURL>& files,
+                              CrostiniSuccessCallback callback);
 
 // Add a newly created LXD container to the kCrostiniContainers pref
 void AddNewLxdContainerToPrefs(Profile* profile,
diff --git a/chrome/browser/chromeos/dbus/vm_applications_service_provider.cc b/chrome/browser/chromeos/dbus/vm_applications_service_provider.cc
index 51a30d4..0c3c94f 100644
--- a/chrome/browser/chromeos/dbus/vm_applications_service_provider.cc
+++ b/chrome/browser/chromeos/dbus/vm_applications_service_provider.cc
@@ -115,6 +115,7 @@
     crostini::LaunchTerminal(
         profile, display::kInvalidDisplayId,
         crostini::ContainerId(request.vm_name(), request.container_name()),
+        request.cwd(),
         std::vector<std::string>(request.params().begin(),
                                  request.params().end()));
   }
diff --git a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
index 43a7dc33..f777782 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
@@ -95,6 +95,7 @@
 #include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
 #include "chrome/browser/ui/ash/launcher/shelf_spinner_controller.h"
+#include "chrome/browser/ui/aura/accessibility/automation_manager_aura.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/browser_dialogs.h"
@@ -1960,51 +1961,6 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
-// AutotestPrivateInstallPluginVMFunction
-///////////////////////////////////////////////////////////////////////////////
-
-AutotestPrivateInstallPluginVMFunction::
-    ~AutotestPrivateInstallPluginVMFunction() = default;
-
-ExtensionFunction::ResponseAction
-AutotestPrivateInstallPluginVMFunction::Run() {
-  std::unique_ptr<api::autotest_private::InstallPluginVM::Params> params(
-      api::autotest_private::InstallPluginVM::Params::Create(*args_));
-  EXTENSION_FUNCTION_VALIDATE(params);
-  DVLOG(1) << "AutotestPrivateInstallPluginVMFunction " << params->image_url
-           << ", " << params->image_hash << ", " << params->license_key;
-
-  Profile* profile = Profile::FromBrowserContext(browser_context());
-  plugin_vm::PluginVmInstallerFactory::GetForProfile(profile)
-      ->SetFreeDiskSpaceForTesting(
-          plugin_vm::PluginVmInstallerFactory::GetForProfile(profile)
-              ->RequiredFreeDiskSpace());
-  plugin_vm::SetFakePluginVmPolicy(profile, params->image_url,
-                                   params->image_hash, params->license_key);
-
-  plugin_vm::ShowPluginVmInstallerView(profile);
-  auto* view = PluginVmInstallerView::GetActiveViewForTesting();
-  view->SetFinishedCallbackForTesting(base::BindOnce(
-      &AutotestPrivateInstallPluginVMFunction::OnInstallFinished, this));
-  // Start the installation.
-  view->AcceptDialog();
-
-  return RespondLater();
-}
-
-void AutotestPrivateInstallPluginVMFunction::OnInstallFinished(bool success) {
-  if (!success) {
-    Respond(Error("Error installing Plugin VM"));
-    return;
-  }
-
-  // Dismiss the dialog and start launching the VM.
-  PluginVmInstallerView::GetActiveViewForTesting()->AcceptDialog();
-
-  Respond(NoArguments());
-}
-
-///////////////////////////////////////////////////////////////////////////////
 // AutotestPrivateSetPluginVMPolicyFunction
 ///////////////////////////////////////////////////////////////////////////////
 
@@ -2270,8 +2226,13 @@
   if (js_printer.printer_make_and_model)
     printer.set_make_and_model(*js_printer.printer_make_and_model);
 
-  if (js_printer.printer_uri)
-    printer.set_uri(*js_printer.printer_uri);
+  if (js_printer.printer_uri) {
+    std::string message;
+    if (!printer.SetUri(*js_printer.printer_uri, &message)) {
+      LOG(ERROR) << message;
+      return RespondNow(Error("Incorrect URI: " + message));
+    }
+  }
 
   if (js_printer.printer_ppd) {
     const GURL ppd =
@@ -4589,6 +4550,22 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
+// AutotestPrivateDisableAutomationFunction
+//////////////////////////////////////////////////////////////////////////////
+
+AutotestPrivateDisableAutomationFunction::
+    AutotestPrivateDisableAutomationFunction() = default;
+
+AutotestPrivateDisableAutomationFunction::
+    ~AutotestPrivateDisableAutomationFunction() = default;
+
+ExtensionFunction::ResponseAction
+AutotestPrivateDisableAutomationFunction::Run() {
+  AutomationManagerAura::GetInstance()->Disable();
+  return RespondNow(NoArguments());
+}
+
+///////////////////////////////////////////////////////////////////////////////
 // AutotestPrivateAPI
 ///////////////////////////////////////////////////////////////////////////////
 
diff --git a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h
index eba486e..d2e7296 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h
@@ -399,18 +399,6 @@
   void CrostiniImported(crostini::CrostiniResult);
 };
 
-class AutotestPrivateInstallPluginVMFunction : public ExtensionFunction {
- public:
-  DECLARE_EXTENSION_FUNCTION("autotestPrivate.installPluginVM",
-                             AUTOTESTPRIVATE_INSTALLPLUGINVM)
-
- private:
-  ~AutotestPrivateInstallPluginVMFunction() override;
-  ResponseAction Run() override;
-
-  void OnInstallFinished(bool success);
-};
-
 class AutotestPrivateSetPluginVMPolicyFunction : public ExtensionFunction {
  public:
   DECLARE_EXTENSION_FUNCTION("autotestPrivate.setPluginVMPolicy",
@@ -1280,6 +1268,17 @@
   ResponseAction Run() override;
 };
 
+class AutotestPrivateDisableAutomationFunction : public ExtensionFunction {
+ public:
+  AutotestPrivateDisableAutomationFunction();
+  DECLARE_EXTENSION_FUNCTION("autotestPrivate.disableAutomation",
+                             AUTOTESTPRIVATE_DISABLEAUTOMATION)
+
+ private:
+  ~AutotestPrivateDisableAutomationFunction() override;
+  ResponseAction Run() override;
+};
+
 template <>
 KeyedService*
 BrowserContextKeyedAPIFactory<AutotestPrivateAPI>::BuildServiceInstanceFor(
diff --git a/chrome/browser/chromeos/extensions/printing/printing_api_utils.cc b/chrome/browser/chromeos/extensions/printing/printing_api_utils.cc
index 2925b7a..bd2acc0 100644
--- a/chrome/browser/chromeos/extensions/printing/printing_api_utils.cc
+++ b/chrome/browser/chromeos/extensions/printing/printing_api_utils.cc
@@ -89,7 +89,7 @@
   idl_printer.id = printer.id();
   idl_printer.name = printer.display_name();
   idl_printer.description = printer.description();
-  idl_printer.uri = printer.uri();
+  idl_printer.uri = printer.uri().GetNormalized();
   idl_printer.source = PrinterSourceToIdl(printer.source());
   idl_printer.is_default =
       DoesPrinterMatchDefaultPrinterRules(printer, default_printer_rules);
diff --git a/chrome/browser/chromeos/external_metrics_unittest.cc b/chrome/browser/chromeos/external_metrics_unittest.cc
index cde0a72e..89615618 100644
--- a/chrome/browser/chromeos/external_metrics_unittest.cc
+++ b/chrome/browser/chromeos/external_metrics_unittest.cc
@@ -46,8 +46,8 @@
 
 TEST_F(ExternalMetricsTest, HandleMissingFile) {
   Init();
-  ASSERT_TRUE(base::DeleteFile(
-      base::FilePath(external_metrics_->uma_events_file_), false));
+  ASSERT_TRUE(
+      base::DeleteFile(base::FilePath(external_metrics_->uma_events_file_)));
 
   EXPECT_EQ(0, external_metrics_->CollectEvents());
 }
diff --git a/chrome/browser/chromeos/file_manager/app_service_file_tasks.cc b/chrome/browser/chromeos/file_manager/app_service_file_tasks.cc
index c2792c3..bda7597 100644
--- a/chrome/browser/chromeos/file_manager/app_service_file_tasks.cc
+++ b/chrome/browser/chromeos/file_manager/app_service_file_tasks.cc
@@ -28,6 +28,7 @@
 #include "components/arc/intent_helper/intent_constants.h"
 #include "components/arc/mojom/file_system.mojom.h"
 #include "components/arc/mojom/intent_helper.mojom.h"
+#include "components/services/app_service/public/mojom/types.mojom.h"
 #include "content/public/browser/browser_thread.h"
 #include "extensions/browser/entry_info.h"
 #include "storage/browser/file_system/file_system_context.h"
diff --git a/chrome/browser/chromeos/file_manager/app_service_file_tasks_unittest.cc b/chrome/browser/chromeos/file_manager/app_service_file_tasks_unittest.cc
new file mode 100644
index 0000000..139fa74b
--- /dev/null
+++ b/chrome/browser/chromeos/file_manager/app_service_file_tasks_unittest.cc
@@ -0,0 +1,152 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/file_manager/app_service_file_tasks.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/apps/app_service/app_service_proxy.h"
+#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
+#include "chrome/browser/apps/app_service/app_service_test.h"
+#include "chrome/browser/chromeos/file_manager/file_tasks.h"
+#include "chrome/browser/chromeos/file_manager/path_util.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/services/app_service/public/cpp/intent_test_util.h"
+#include "components/services/app_service/public/mojom/types.mojom.h"
+#include "content/public/test/browser_task_environment.h"
+#include "extensions/browser/entry_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using extensions::api::file_manager_private::Verb;
+
+namespace {
+const char kAppIdText[] = "abcdefg";
+const char kAppIdImage[] = "gfedcba";
+const char kAppIdAny[] = "hijklmn";
+const char kMimeTypeText[] = "text/plain";
+const char kMimeTypeImage[] = "image/jpeg";
+const char kMimeTypeAny[] = "*/*";
+const char kActivityNameText[] = "some_text_activity";
+const char kActivityNameImage[] = "some_image_activity";
+const char kActivityNameAny[] = "some_any_file";
+}  // namespace
+
+namespace file_manager {
+namespace file_tasks {
+
+class AppServiceFileTasksTest : public testing::Test {
+ protected:
+  AppServiceFileTasksTest() {
+    scoped_feature_list_.InitAndEnableFeature(features::kIntentHandlingSharing);
+  }
+  void SetUp() override {
+    profile_ = std::make_unique<TestingProfile>();
+    app_service_test_.SetUp(profile_.get());
+    app_service_proxy_ =
+        apps::AppServiceProxyFactory::GetForProfile(profile_.get());
+    ASSERT_TRUE(app_service_proxy_);
+    AddApps();
+  }
+
+  Profile* profile() { return profile_.get(); }
+
+  void AddFakeAppWithIntentFilter(const std::string& app_id,
+                                  const std::string& mime_type,
+                                  const std::string& activity_name,
+                                  bool is_send_multiple) {
+    std::vector<apps::mojom::AppPtr> apps;
+    auto app = apps::mojom::App::New();
+    app->app_id = app_id;
+    app->app_type = apps::mojom::AppType::kArc;
+    auto intent_filter =
+        is_send_multiple
+            ? apps_util::CreateIntentFilterForSendMultiple(mime_type,
+                                                           activity_name)
+            : apps_util::CreateIntentFilterForSend(mime_type, activity_name);
+    app->intent_filters.push_back(std::move(intent_filter));
+    apps.push_back(std::move(app));
+    app_service_proxy_->AppRegistryCache().OnApps(std::move(apps));
+    app_service_test_.WaitForAppService();
+  }
+
+  void AddApps() {
+    AddFakeAppWithIntentFilter(kAppIdText, kMimeTypeText, kActivityNameText,
+                               /*is_send_multiple=*/false);
+    AddFakeAppWithIntentFilter(kAppIdImage, kMimeTypeImage, kActivityNameImage,
+                               /*is_send_multiple=*/false);
+    AddFakeAppWithIntentFilter(kAppIdAny, kMimeTypeAny, kActivityNameAny,
+                               /*is_send_multiple=*/true);
+  }
+
+  base::test::ScopedFeatureList scoped_feature_list_;
+  content::BrowserTaskEnvironment task_environment_;
+  std::unique_ptr<TestingProfile> profile_;
+  apps::AppServiceProxy* app_service_proxy_ = nullptr;
+  apps::AppServiceTest app_service_test_;
+};
+
+// Test that between an image app and text app, the text app can be
+// found for an text file entry.
+TEST_F(AppServiceFileTasksTest, FindAppServiceFileTasksText) {
+  // Find apps for a "text/plain" file.
+  std::vector<extensions::EntryInfo> entries;
+  entries.emplace_back(
+      util::GetMyFilesFolderForProfile(profile()).AppendASCII("foo.txt"),
+      kMimeTypeText, false);
+
+  // This test doesn't test file_urls, leave it empty.
+  std::vector<GURL> file_urls{GURL()};
+  std::vector<FullTaskDescriptor> tasks;
+  FindAppServiceTasks(profile(), entries, file_urls, &tasks);
+  ASSERT_EQ(1U, tasks.size());
+  EXPECT_EQ(kAppIdText, tasks[0].task_descriptor().app_id);
+  EXPECT_EQ(kActivityNameText, tasks[0].task_title());
+}
+
+// Test that between an image app and text app, the image app can be
+// found for an image file entry.
+TEST_F(AppServiceFileTasksTest, FindAppServiceFileTasksImage) {
+  // Find apps for a "image/jpeg" file.
+  std::vector<extensions::EntryInfo> entries;
+  entries.emplace_back(
+      util::GetMyFilesFolderForProfile(profile()).AppendASCII("bar.jpeg"),
+      kMimeTypeImage, false);
+
+  // This test doesn't test file_urls, leave it empty.
+  std::vector<GURL> file_urls{GURL()};
+  std::vector<FullTaskDescriptor> tasks;
+  FindAppServiceTasks(profile(), entries, file_urls, &tasks);
+  ASSERT_EQ(1U, tasks.size());
+  EXPECT_EQ(kAppIdImage, tasks[0].task_descriptor().app_id);
+  EXPECT_EQ(kActivityNameImage, tasks[0].task_title());
+}
+
+// Test that between an image app, text app and an app that can handle every
+// file, the app that can handle every file can be found for an image file entry
+// and text file entry.
+TEST_F(AppServiceFileTasksTest, FindAppServiceFileTasksMultiple) {
+  std::vector<extensions::EntryInfo> entries;
+  entries.emplace_back(
+      util::GetMyFilesFolderForProfile(profile()).AppendASCII("foo.txt"),
+      kMimeTypeText, false);
+  entries.emplace_back(
+      util::GetMyFilesFolderForProfile(profile()).AppendASCII("bar.jpeg"),
+      kMimeTypeImage, false);
+
+  // This test doesn't test file_urls, leave it empty.
+  std::vector<GURL> file_urls{GURL(), GURL()};
+  std::vector<FullTaskDescriptor> tasks;
+  FindAppServiceTasks(profile(), entries, file_urls, &tasks);
+  ASSERT_EQ(1U, tasks.size());
+  EXPECT_EQ(kAppIdAny, tasks[0].task_descriptor().app_id);
+  EXPECT_EQ(kActivityNameAny, tasks[0].task_title());
+}
+
+}  // namespace file_tasks
+}  // namespace file_manager.
diff --git a/chrome/browser/chromeos/file_manager/guest_os_file_tasks.cc b/chrome/browser/chromeos/file_manager/guest_os_file_tasks.cc
index 783d0565..3c65ed5 100644
--- a/chrome/browser/chromeos/file_manager/guest_os_file_tasks.cc
+++ b/chrome/browser/chromeos/file_manager/guest_os_file_tasks.cc
@@ -41,6 +41,8 @@
 namespace file_manager {
 namespace file_tasks {
 
+const char kGuestOsAppActionID[] = "open-with";
+
 namespace {
 
 // When MIME type detection is done; if we can't be properly determined then
diff --git a/chrome/browser/chromeos/file_manager/guest_os_file_tasks.h b/chrome/browser/chromeos/file_manager/guest_os_file_tasks.h
index 3273767..96dad7d6 100644
--- a/chrome/browser/chromeos/file_manager/guest_os_file_tasks.h
+++ b/chrome/browser/chromeos/file_manager/guest_os_file_tasks.h
@@ -26,7 +26,7 @@
 namespace file_tasks {
 
 // Guest OS apps all use the same action ID.
-constexpr char kGuestOsAppActionID[] = "open-with";
+extern const char kGuestOsAppActionID[];
 
 // Finds the Guest OS |app_ids| and |app_names| that can handle |entries|.
 // VisibleForTesting.  Called by |FindGuestOsTasks|.
diff --git a/chrome/browser/chromeos/file_system_provider/scoped_file_opener.cc b/chrome/browser/chromeos/file_system_provider/scoped_file_opener.cc
index c2a5a13..d3885090 100644
--- a/chrome/browser/chromeos/file_system_provider/scoped_file_opener.cc
+++ b/chrome/browser/chromeos/file_system_provider/scoped_file_opener.cc
@@ -26,7 +26,7 @@
         base::WrapRefCounted(new Runner(file_system, std::move(callback)));
     runner->abort_callback_ = file_system->OpenFile(
         file_path, mode,
-        base::Bind(&ScopedFileOpener::Runner::OnOpenFileCompleted, runner));
+        base::BindOnce(&ScopedFileOpener::Runner::OnOpenFileCompleted, runner));
     return runner;
   }
 
diff --git a/chrome/browser/chromeos/file_system_provider/scoped_file_opener_unittest.cc b/chrome/browser/chromeos/file_system_provider/scoped_file_opener_unittest.cc
index 72f0499..9cbe6f7f 100644
--- a/chrome/browser/chromeos/file_system_provider/scoped_file_opener_unittest.cc
+++ b/chrome/browser/chromeos/file_system_provider/scoped_file_opener_unittest.cc
@@ -32,8 +32,8 @@
                          OpenFileMode mode,
                          OpenFileCallback callback) override {
     open_callback_ = std::move(callback);
-    return base::Bind(&TestingProvidedFileSystem::AbortOpen,
-                      base::Unretained(this));
+    return base::BindOnce(&TestingProvidedFileSystem::AbortOpen,
+                          base::Unretained(this));
   }
 
   AbortCallback CloseFile(
@@ -46,8 +46,8 @@
 
   bool has_open_callback() const { return !!open_callback_; }
   OpenFileCallback open_callback() {
-    return base::Bind(&TestingProvidedFileSystem::CompleteOpen,
-                      base::Unretained(this));
+    return base::BindOnce(&TestingProvidedFileSystem::CompleteOpen,
+                          base::Unretained(this));
   }
   const std::vector<int> close_requests() const { return close_requests_; }
 
@@ -79,7 +79,7 @@
   {
     ScopedFileOpener file_opener(&file_system, base::FilePath(),
                                  OPEN_FILE_MODE_READ,
-                                 base::Bind(&LogOpen, &log));
+                                 base::BindOnce(&LogOpen, &log));
     base::RunLoop().RunUntilIdle();
     EXPECT_TRUE(file_system.has_open_callback());
   }
@@ -98,7 +98,7 @@
   {
     ScopedFileOpener file_opener(&file_system, base::FilePath(),
                                  OPEN_FILE_MODE_READ,
-                                 base::Bind(&LogOpen, &log));
+                                 base::BindOnce(&LogOpen, &log));
     base::RunLoop().RunUntilIdle();
     ASSERT_TRUE(file_system.has_open_callback());
     file_system.open_callback().Run(123, base::File::FILE_OK);
@@ -119,7 +119,7 @@
   {
     ScopedFileOpener file_opener(&file_system, base::FilePath(),
                                  OPEN_FILE_MODE_READ,
-                                 base::Bind(&LogOpen, &log));
+                                 base::BindOnce(&LogOpen, &log));
     base::RunLoop().RunUntilIdle();
     ASSERT_TRUE(file_system.has_open_callback());
     file_system.open_callback().Run(0, base::File::FILE_ERROR_ABORT);
diff --git a/chrome/browser/chromeos/guest_os/guest_os_registry_service.cc b/chrome/browser/chromeos/guest_os/guest_os_registry_service.cc
index 266c3cc..27636ef 100644
--- a/chrome/browser/chromeos/guest_os/guest_os_registry_service.cc
+++ b/chrome/browser/chromeos/guest_os/guest_os_registry_service.cc
@@ -30,6 +30,7 @@
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 #include "components/prefs/scoped_user_pref_update.h"
+#include "extensions/browser/api/file_handlers/mime_util.h"
 #include "ui/base/l10n/l10n_util.h"
 
 using vm_tools::apps::App;
@@ -364,6 +365,9 @@
 }
 
 std::set<std::string> GuestOsRegistryService::Registration::MimeTypes() const {
+  if (is_terminal_app_)
+    return std::set<std::string>(
+        {extensions::app_file_handler_util::kMimeTypeInodeDirectory});
   if (pref_.is_none())
     return {};
   return ListToStringSet(pref_.FindKeyOfType(guest_os::prefs::kAppMimeTypesKey,
diff --git a/chrome/browser/chromeos/login/challenge_response_auth_keys_loader.cc b/chrome/browser/chromeos/login/challenge_response_auth_keys_loader.cc
index 3a61080..7399c91 100644
--- a/chrome/browser/chromeos/login/challenge_response_auth_keys_loader.cc
+++ b/chrome/browser/chromeos/login/challenge_response_auth_keys_loader.cc
@@ -65,16 +65,16 @@
   return extension_ids;
 }
 
-content::BrowserContext* GetBrowserContext() {
+Profile* GetProfile() {
   return ProfileHelper::GetSigninProfile()->GetOriginalProfile();
 }
 
 extensions::ExtensionRegistry* GetExtensionRegistry() {
-  return extensions::ExtensionRegistry::Get(GetBrowserContext());
+  return extensions::ExtensionRegistry::Get(GetProfile());
 }
 
 extensions::ProcessManager* GetProcessManager() {
-  return extensions::ProcessManager::Get(GetBrowserContext());
+  return extensions::ProcessManager::Get(GetProfile());
 }
 
 // Loads the persistently stored information about the challenge-response keys
@@ -280,7 +280,7 @@
       const extensions::Extension* extension =
           GetExtensionRegistry()->GetInstalledExtension(extension_id);
       if (extension) {
-        OnExtensionInstalled(GetBrowserContext(), extension,
+        OnExtensionInstalled(GetProfile(), extension,
                              /*is_update=*/false);
       }
     }
@@ -362,13 +362,20 @@
 
 ChallengeResponseAuthKeysLoader::ChallengeResponseAuthKeysLoader()
     : maximum_extension_load_waiting_time_(
-          kDefaultMaximumExtensionLoadWaitingTime) {}
+          kDefaultMaximumExtensionLoadWaitingTime) {
+  profile_subscription_.Add(GetProfile());
+}
 
 ChallengeResponseAuthKeysLoader::~ChallengeResponseAuthKeysLoader() = default;
 
 void ChallengeResponseAuthKeysLoader::LoadAvailableKeys(
     const AccountId& account_id,
     LoadAvailableKeysCallback callback) {
+  if (profile_is_destroyed_) {
+    // Don't proceed during shutdown.
+    std::move(callback).Run(/*challenge_response_keys=*/{});
+    return;
+  }
   // Load the list of public keys of the cryptographic keys that can be used
   // for authenticating the user.
   std::vector<std::string> suitable_public_key_spki_items;
@@ -391,10 +398,21 @@
                      std::move(callback)));
 }
 
+void ChallengeResponseAuthKeysLoader::OnProfileWillBeDestroyed(
+    Profile* profile) {
+  profile_is_destroyed_ = true;
+  profile_subscription_.Remove(profile);
+}
+
 void ChallengeResponseAuthKeysLoader::ContinueLoadAvailableKeysExtensionsLoaded(
     const AccountId& account_id,
     const std::vector<std::string>& suitable_public_key_spki_items,
     LoadAvailableKeysCallback callback) {
+  if (profile_is_destroyed_) {
+    // Don't proceed during shutdown.
+    std::move(callback).Run(/*challenge_response_keys=*/{});
+    return;
+  }
   // Asynchronously poll all certificate providers to get the list of
   // currently available cryptographic keys.
   std::unique_ptr<CertificateProvider> cert_provider =
@@ -410,6 +428,11 @@
     const std::vector<std::string>& suitable_public_key_spki_items,
     LoadAvailableKeysCallback callback,
     net::ClientCertIdentityList /* cert_identities */) {
+  if (profile_is_destroyed_) {
+    // Don't proceed during shutdown.
+    std::move(callback).Run(/*challenge_response_keys=*/{});
+    return;
+  }
   CertificateProviderService* const cert_provider_service =
       GetCertificateProviderService();
   std::vector<ChallengeResponseKey> filtered_keys;
diff --git a/chrome/browser/chromeos/login/challenge_response_auth_keys_loader.h b/chrome/browser/chromeos/login/challenge_response_auth_keys_loader.h
index bec7568..ed47525 100644
--- a/chrome/browser/chromeos/login/challenge_response_auth_keys_loader.h
+++ b/chrome/browser/chromeos/login/challenge_response_auth_keys_loader.h
@@ -9,6 +9,9 @@
 #include <vector>
 
 #include "base/memory/weak_ptr.h"
+#include "base/scoped_observer.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_observer.h"
 #include "chromeos/login/auth/challenge_response_key.h"
 #include "net/ssl/client_cert_identity.h"
 
@@ -26,7 +29,7 @@
 // (which is the responsibility of this class) and for forwarding the challenge
 // requests to the component that talks to the cryptographic token (which is the
 // responsibility of CryptohomeKeyDelegateServiceProvider).
-class ChallengeResponseAuthKeysLoader final {
+class ChallengeResponseAuthKeysLoader final : public ProfileObserver {
  public:
   using LoadAvailableKeysCallback = base::OnceCallback<void(
       std::vector<ChallengeResponseKey> challenge_response_keys)>;
@@ -40,7 +43,7 @@
       delete;
   ChallengeResponseAuthKeysLoader& operator=(
       const ChallengeResponseAuthKeysLoader&) = delete;
-  ~ChallengeResponseAuthKeysLoader();
+  ~ChallengeResponseAuthKeysLoader() override;
 
   // Prepares the ChallengeResponseKey values containing the currently available
   // cryptographic keys that can be used to authenticate the given user. If
@@ -58,6 +61,9 @@
     maximum_extension_load_waiting_time_ = time;
   }
 
+  // ProfileObserver:
+  void OnProfileWillBeDestroyed(Profile* profile) override;
+
  private:
   // Asynchronous job which is scheduled by LoadAvailableKeys after all
   // necessary extensions are loaded.
@@ -77,6 +83,11 @@
 
   base::TimeDelta maximum_extension_load_waiting_time_;
 
+  // Whether the sign-in profile is destroyed.
+  bool profile_is_destroyed_ = false;
+
+  ScopedObserver<Profile, ProfileObserver> profile_subscription_{this};
+
   base::WeakPtrFactory<ChallengeResponseAuthKeysLoader> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/chromeos/login/challenge_response_auth_keys_loader_browsertest.cc b/chrome/browser/chromeos/login/challenge_response_auth_keys_loader_browsertest.cc
index b90bd26..c26b716e 100644
--- a/chrome/browser/chromeos/login/challenge_response_auth_keys_loader_browsertest.cc
+++ b/chrome/browser/chromeos/login/challenge_response_auth_keys_loader_browsertest.cc
@@ -67,7 +67,8 @@
   }
 
   void TearDownOnMainThread() override {
-    challenge_response_auth_keys_loader_.reset();
+    if (!should_delete_loader_after_shutdown_)
+      challenge_response_auth_keys_loader_.reset();
     OobeBaseTest::TearDownOnMainThread();
   }
 
@@ -171,6 +172,10 @@
     challenge_response_auth_keys_loader_.reset();
   }
 
+  void set_should_delete_loader_after_shutdown() {
+    should_delete_loader_after_shutdown_ = true;
+  }
+
  private:
   void WaitUntilPrefUpdated() {
     PrefChangeRegistrar pref_change_registrar;
@@ -202,6 +207,10 @@
   std::unique_ptr<ChallengeResponseAuthKeysLoader>
       challenge_response_auth_keys_loader_;
 
+  // Whether |challenge_response_auth_keys_loader_| should be destroyed after
+  // the browser shutdown, not before it.
+  bool should_delete_loader_after_shutdown_ = false;
+
   base::WeakPtrFactory<ChallengeResponseAuthKeysLoaderBrowserTest>
       weak_ptr_factory_{this};
 };
@@ -333,6 +342,27 @@
   DeleteChallengeResponseAuthKeysLoader();
 }
 
+// Tests the case when the load operation isn't completed by the time the
+// browser shuts down.
+IN_PROC_BROWSER_TEST_F(ChallengeResponseAuthKeysLoaderBrowserTest,
+                       AfterShutdown) {
+  RegisterChallengeResponseKey(/*with_extension_id=*/true);
+  InstallExtension(/*wait_on_extension_loaded=*/false);
+  CheckExtensionInstallPolicyApplied();
+
+  // Challenge Response Auth Keys can be loaded.
+  EXPECT_TRUE(
+      ChallengeResponseAuthKeysLoader::CanAuthenticateUser(account_id()));
+
+  // Start the key loading operation. Intentionally do not wait for its
+  // completion.
+  challenge_response_auth_keys_loader()->LoadAvailableKeys(account_id(),
+                                                           base::DoNothing());
+  // Postpone destroying the loader until after the browser shutdown. No crash
+  // should occur.
+  set_should_delete_loader_after_shutdown();
+}
+
 class ChallengeResponseExtensionLoadObserverTest
     : public ChallengeResponseAuthKeysLoaderBrowserTest,
       public extensions::ProcessManagerObserver {
diff --git a/chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher_impl_unittest.cc b/chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher_impl_unittest.cc
index 40ca131..bf797e4 100644
--- a/chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher_impl_unittest.cc
+++ b/chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher_impl_unittest.cc
@@ -111,6 +111,9 @@
                         ash::mojom::TouchCalibrationPtr calibration,
                         TouchCalibrationCallback callback) override {}
   void HighlightDisplay(int64_t id) override {}
+  void DragDisplayDelta(int64_t display_id,
+                        int32_t delta_x,
+                        int32_t delta_y) override {}
 
  private:
   mojo::Receiver<ash::mojom::CrosDisplayConfigController> receiver_;
diff --git a/chrome/browser/chromeos/login/screens/update_required_screen.cc b/chrome/browser/chromeos/login/screens/update_required_screen.cc
index de53404..32002619 100644
--- a/chrome/browser/chromeos/login/screens/update_required_screen.cc
+++ b/chrome/browser/chromeos/login/screens/update_required_screen.cc
@@ -22,6 +22,7 @@
 #include "chrome/browser/chromeos/login/wizard_controller.h"
 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
 #include "chrome/browser/ui/webui/chromeos/login/update_required_screen_handler.h"
+#include "chromeos/constants/chromeos_switches.h"
 #include "chromeos/network/network_handler.h"
 #include "chromeos/network/network_state_handler.h"
 #include "chromeos/settings/cros_settings_names.h"
@@ -108,7 +109,8 @@
     const chromeos::UpdateEngineClient::EolInfo& info) {
   //  TODO(crbug.com/1020616) : Handle if the device is left on this screen
   //  for long enough to reach Eol.
-  if (!info.eol_date.is_null() && info.eol_date <= clock_->Now()) {
+  if (chromeos::switches::IsAueReachedForUpdateRequiredForTest() ||
+      (!info.eol_date.is_null() && info.eol_date <= clock_->Now())) {
     EnsureScreenIsShown();
     if (view_)
       view_->SetUIState(UpdateRequiredView::EOL_REACHED);
diff --git a/chrome/browser/chromeos/login/security_token_login_browsertest.cc b/chrome/browser/chromeos/login/security_token_login_browsertest.cc
index 498c7336..fcb3279c 100644
--- a/chrome/browser/chromeos/login/security_token_login_browsertest.cc
+++ b/chrome/browser/chromeos/login/security_token_login_browsertest.cc
@@ -149,6 +149,7 @@
     MixinBasedInProcessBrowserTest::SetUpOnMainThread();
     cert_provider_extension_mixin_.test_certificate_provider_extension()
         ->set_require_pin(kCorrectPin);
+    WaitForLoginScreenWidgetShown();
   }
 
   // LocalStateMixin::Delegate:
@@ -183,6 +184,13 @@
         std::move(challenge_response_keys_value));
   }
 
+  void WaitForLoginScreenWidgetShown() {
+    base::RunLoop run_loop;
+    ash::LoginScreenTestApi::AddOnLockScreenShownCallback(
+        run_loop.QuitClosure());
+    run_loop.Run();
+  }
+
   // Unowned (referencing a global singleton)
   ChallengeResponseFakeCryptohomeClient* const cryptohome_client_;
   DeviceStateMixin device_state_mixin_{
@@ -194,8 +202,7 @@
                                      /*load_extension_immediately=*/true};
 };
 
-// TODO(crbug.com/1033936): Disabled due to flakiness.
-IN_PROC_BROWSER_TEST_F(SecurityTokenLoginTest, DISABLED_Basic) {
+IN_PROC_BROWSER_TEST_F(SecurityTokenLoginTest, Basic) {
   // The user pod is displayed with the challenge-response "start" button
   // instead of the password input field.
   EXPECT_TRUE(
diff --git a/chrome/browser/chromeos/login/wizard_controller_browsertest.cc b/chrome/browser/chromeos/login/wizard_controller_browsertest.cc
index 81ddcb0c..c8f27b6 100644
--- a/chrome/browser/chromeos/login/wizard_controller_browsertest.cc
+++ b/chrome/browser/chromeos/login/wizard_controller_browsertest.cc
@@ -1222,7 +1222,7 @@
             GetErrorScreen()->GetParentScreen());
   base::DictionaryValue device_state;
   device_state.SetString(policy::kDeviceStateMode,
-                         policy::kDeviceStateRestoreModeDisabled);
+                         policy::kDeviceStateModeDisabled);
   device_state.SetString(policy::kDeviceStateDisabledMessage, kDisabledMessage);
   g_browser_process->local_state()->Set(prefs::kServerBackedDeviceState,
                                         device_state);
@@ -2715,7 +2715,7 @@
             GetErrorScreen()->GetParentScreen());
   base::DictionaryValue device_state;
   device_state.SetString(policy::kDeviceStateMode,
-                         policy::kDeviceStateRestoreModeDisabled);
+                         policy::kDeviceStateModeDisabled);
   device_state.SetString(policy::kDeviceStateDisabledMessage, kDisabledMessage);
   g_browser_process->local_state()->Set(prefs::kServerBackedDeviceState,
                                         device_state);
diff --git a/chrome/browser/chromeos/plugin_vm/plugin_vm_test_helper.cc b/chrome/browser/chromeos/plugin_vm/plugin_vm_test_helper.cc
index 6b5ab947..8f4bf86 100644
--- a/chrome/browser/chromeos/plugin_vm/plugin_vm_test_helper.cc
+++ b/chrome/browser/chromeos/plugin_vm/plugin_vm_test_helper.cc
@@ -149,8 +149,8 @@
       std::make_unique<FakeShelfItemDelegate>(shelf_id);
   ChromeLauncherController* laucher_controller =
       ChromeLauncherController::instance();
-  // Similar logic to InternalAppWindowShelfController, for handling pins and
-  // spinners.
+  // Similar logic to AppServiceAppWindowLauncherController, for handling pins
+  // and spinners.
   if (laucher_controller->GetItem(shelf_id)) {
     laucher_controller->shelf_model()->SetShelfItemDelegate(
         shelf_id, std::move(delegate));
diff --git a/chrome/browser/chromeos/policy/auto_enrollment_client_impl.cc b/chrome/browser/chromeos/policy/auto_enrollment_client_impl.cc
index 0ec1fdf..14f69fb 100644
--- a/chrome/browser/chromeos/policy/auto_enrollment_client_impl.cc
+++ b/chrome/browser/chromeos/policy/auto_enrollment_client_impl.cc
@@ -89,7 +89,7 @@
     case em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED:
       return kDeviceStateRestoreModeReEnrollmentEnforced;
     case em::DeviceStateRetrievalResponse::RESTORE_MODE_DISABLED:
-      return kDeviceStateRestoreModeDisabled;
+      return kDeviceStateModeDisabled;
     case em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ZERO_TOUCH:
       return kDeviceStateRestoreModeReEnrollmentZeroTouch;
   }
@@ -115,7 +115,7 @@
       return kDeviceStateInitialModeEnrollmentZeroTouch;
     case em::DeviceInitialEnrollmentStateResponse::
         INITIAL_ENROLLMENT_MODE_DISABLED:
-      return kDeviceStateRestoreModeDisabled;
+      return kDeviceStateModeDisabled;
   }
 }
 
diff --git a/chrome/browser/chromeos/policy/auto_enrollment_client_impl_unittest.cc b/chrome/browser/chromeos/policy/auto_enrollment_client_impl_unittest.cc
index 8a44d02..ac6ad1a 100644
--- a/chrome/browser/chromeos/policy/auto_enrollment_client_impl_unittest.cc
+++ b/chrome/browser/chromeos/policy/auto_enrollment_client_impl_unittest.cc
@@ -727,7 +727,7 @@
   EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
   EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_DISABLED);
   VerifyCachedResult(true, kPowerLimit);
-  VerifyServerBackedState("example.com", kDeviceStateRestoreModeDisabled,
+  VerifyServerBackedState("example.com", kDeviceStateModeDisabled,
                           kDisabledMessage, kNotWithLicense);
 }
 
diff --git a/chrome/browser/chromeos/policy/minimum_version_policy_handler.cc b/chrome/browser/chromeos/policy/minimum_version_policy_handler.cc
index 402c2c0..35a88bd 100644
--- a/chrome/browser/chromeos/policy/minimum_version_policy_handler.cc
+++ b/chrome/browser/chromeos/policy/minimum_version_policy_handler.cc
@@ -23,6 +23,7 @@
 #include "chrome/browser/upgrade_detector/upgrade_detector.h"
 #include "chrome/common/pref_names.h"
 #include "chromeos/constants/chromeos_features.h"
+#include "chromeos/constants/chromeos_switches.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/network/network_handler.h"
 #include "chromeos/network/network_state_handler.h"
@@ -318,7 +319,8 @@
 
 void MinimumVersionPolicyHandler::OnFetchEolInfo(
     const chromeos::UpdateEngineClient::EolInfo info) {
-  if (info.eol_date.is_null() || info.eol_date > update_required_time_) {
+  if (!chromeos::switches::IsAueReachedForUpdateRequiredForTest() &&
+      (info.eol_date.is_null() || info.eol_date > update_required_time_)) {
     // End of life is not reached. Start update with |warning_time_|.
     eol_reached_ = false;
     HandleUpdateRequired(state_->warning());
diff --git a/chrome/browser/chromeos/policy/server_backed_device_state.cc b/chrome/browser/chromeos/policy/server_backed_device_state.cc
index f573848..94a96d1 100644
--- a/chrome/browser/chromeos/policy/server_backed_device_state.cc
+++ b/chrome/browser/chromeos/policy/server_backed_device_state.cc
@@ -20,16 +20,19 @@
 const char kDeviceStateDisabledMessage[] = "disabled_message";
 const char kDeviceStatePackagedLicense[] = "packaged_license";
 
+// Modes for a device after initial state determination.
+const char kDeviceStateInitialModeEnrollmentEnforced[] = "enrollment-enforced";
+const char kDeviceStateInitialModeEnrollmentZeroTouch[] =
+    "enrollment-zero-touch";
+// Modes for a device after secondary state determination (FRE).
 const char kDeviceStateRestoreModeReEnrollmentRequested[] =
     "re-enrollment-requested";
 const char kDeviceStateRestoreModeReEnrollmentEnforced[] =
     "re-enrollment-enforced";
-const char kDeviceStateRestoreModeDisabled[] = "disabled";
 const char kDeviceStateRestoreModeReEnrollmentZeroTouch[] =
     "re-enrollment-zero-touch";
-const char kDeviceStateInitialModeEnrollmentEnforced[] = "enrollment-enforced";
-const char kDeviceStateInitialModeEnrollmentZeroTouch[] =
-    "enrollment-zero-touch";
+// Modes for a device after either initial or secondary state determination.
+const char kDeviceStateModeDisabled[] = "disabled";
 
 DeviceStateMode GetDeviceStateMode() {
   std::string device_state_mode;
@@ -42,7 +45,7 @@
     return RESTORE_MODE_REENROLLMENT_REQUESTED;
   if (device_state_mode == kDeviceStateRestoreModeReEnrollmentEnforced)
     return RESTORE_MODE_REENROLLMENT_ENFORCED;
-  if (device_state_mode == kDeviceStateRestoreModeDisabled)
+  if (device_state_mode == kDeviceStateModeDisabled)
     return RESTORE_MODE_DISABLED;
   if (device_state_mode == kDeviceStateRestoreModeReEnrollmentZeroTouch)
     return RESTORE_MODE_REENROLLMENT_ZERO_TOUCH;
diff --git a/chrome/browser/chromeos/policy/server_backed_device_state.h b/chrome/browser/chromeos/policy/server_backed_device_state.h
index 107278a..2da2485f 100644
--- a/chrome/browser/chromeos/policy/server_backed_device_state.h
+++ b/chrome/browser/chromeos/policy/server_backed_device_state.h
@@ -13,16 +13,18 @@
 extern const char kDeviceStateDisabledMessage[];
 extern const char kDeviceStatePackagedLicense[];
 
+// String constants used to persist the initial state action in the
+// kDeviceStateMode dictionary entry.
+extern const char kDeviceStateInitialModeEnrollmentEnforced[];
+extern const char kDeviceStateInitialModeEnrollmentZeroTouch[];
 // String constants used to persist the restorative action in the
 // kDeviceStateMode dictionary entry.
 extern const char kDeviceStateRestoreModeReEnrollmentRequested[];
 extern const char kDeviceStateRestoreModeReEnrollmentEnforced[];
-extern const char kDeviceStateRestoreModeDisabled[];
 extern const char kDeviceStateRestoreModeReEnrollmentZeroTouch[];
-// The following constants are for an initial action but we do still use
-// the same dictionary entry.
-extern const char kDeviceStateInitialModeEnrollmentEnforced[];
-extern const char kDeviceStateInitialModeEnrollmentZeroTouch[];
+// String constants used to persist either the initial state action
+// or the restorative action in the kDeviceStateMode dictionary entry.
+extern const char kDeviceStateModeDisabled[];
 
 // Mode that a device needs to start in.
 enum DeviceStateMode {
diff --git a/chrome/browser/chromeos/printing/automatic_usb_printer_configurer_unittest.cc b/chrome/browser/chromeos/printing/automatic_usb_printer_configurer_unittest.cc
index 9a8ef43c..769a4655 100644
--- a/chrome/browser/chromeos/printing/automatic_usb_printer_configurer_unittest.cc
+++ b/chrome/browser/chromeos/printing/automatic_usb_printer_configurer_unittest.cc
@@ -24,21 +24,21 @@
 Printer CreateUsbPrinter(const std::string& id) {
   Printer printer;
   printer.set_id(id);
-  printer.set_uri("usb:printer");
+  printer.SetUri("usb://usb/printer");
   return printer;
 }
 
 Printer CreateIppUsbPrinter(const std::string& id) {
   Printer printer;
   printer.set_id(id);
-  printer.set_uri("ippusb:printer");
+  printer.SetUri("ippusb://usb/printer");
   return printer;
 }
 
 Printer CreateIppPrinter(const std::string& id) {
   Printer printer;
   printer.set_id(id);
-  printer.set_uri("ipp:printer");
+  printer.SetUri("ipp://usb/printer");
   return printer;
 }
 
diff --git a/chrome/browser/chromeos/printing/cups_printers_manager.cc b/chrome/browser/chromeos/printing/cups_printers_manager.cc
index 5cb734b..bf7aaee 100644
--- a/chrome/browser/chromeos/printing/cups_printers_manager.cc
+++ b/chrome/browser/chromeos/printing/cups_printers_manager.cc
@@ -38,7 +38,7 @@
 #include "chrome/common/pref_names.h"
 #include "chromeos/printing/cups_printer_status.h"
 #include "chromeos/printing/printing_constants.h"
-#include "chromeos/printing/uri_components.h"
+#include "chromeos/printing/uri.h"
 #include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
 #include "components/device_event_log/device_event_log.h"
 #include "components/policy/policy_constants.h"
@@ -51,15 +51,8 @@
 
 namespace chromeos {
 
-bool IsIppUri(base::StringPiece printer_uri) {
-  base::StringPiece::size_type separator_location =
-      printer_uri.find(url::kStandardSchemeSeparator);
-  if (separator_location == base::StringPiece::npos) {
-    return false;
-  }
-
-  base::StringPiece scheme_part = printer_uri.substr(0, separator_location);
-  return scheme_part == kIppScheme || scheme_part == kIppsScheme;
+bool IsIppUri(const Uri& uri) {
+  return (uri.GetScheme() == kIppScheme || uri.GetScheme() == kIppsScheme);
 }
 
 namespace {
@@ -348,9 +341,8 @@
       return;
     }
 
-    base::Optional<UriComponents> parsed_uri = ParseUri(printer->uri());
     // Behavior for querying a non-IPP uri is undefined and disallowed.
-    if (!parsed_uri || !IsIppUri(printer->uri())) {
+    if (IsIppUri(printer->uri())) {
       PRINTER_LOG(ERROR) << "Unable to complete printer status request. "
                          << "Printer uri is invalid. Printer id: "
                          << printer_id;
@@ -362,9 +354,10 @@
       return;
     }
 
-    const UriComponents& uri = parsed_uri.value();
     QueryIppPrinter(
-        uri.host(), uri.port(), uri.path(), uri.encrypted(),
+        printer->uri().GetHostEncoded(), printer->uri().GetPort(),
+        printer->uri().GetPathEncodedAsString(),
+        printer->uri().GetScheme() == kIppsScheme,
         base::BindOnce(&CupsPrintersManagerImpl::OnPrinterInfoFetched,
                        weak_ptr_factory_.GetWeakPtr(), printer_id,
                        std::move(cb)));
@@ -563,10 +556,10 @@
           // If the detected printer supports ipp-over-usb and we could not find
           // a ppd for it, then we switch to the ippusb scheme and mark it as
           // autoconf.
-          printer.set_uri(
-              base::StringPrintf("ippusb://%04x_%04x/ipp/print",
-                                 detected.ppd_search_data.usb_vendor_id,
-                                 detected.ppd_search_data.usb_product_id));
+          printer.SetUri(
+              Uri(base::StringPrintf("ippusb://%04x_%04x/ipp/print",
+                                     detected.ppd_search_data.usb_vendor_id,
+                                     detected.ppd_search_data.usb_product_id)));
           printer.mutable_ppd_reference()->autoconf = true;
           printers_.Insert(PrinterClass::kAutomatic, printer);
         } else {
diff --git a/chrome/browser/chromeos/printing/cups_printers_manager.h b/chrome/browser/chromeos/printing/cups_printers_manager.h
index 4aa044c..06807f5 100644
--- a/chrome/browser/chromeos/printing/cups_printers_manager.h
+++ b/chrome/browser/chromeos/printing/cups_printers_manager.h
@@ -12,6 +12,7 @@
 #include "base/memory/ref_counted.h"
 #include "chrome/browser/chromeos/printing/printer_installation_manager.h"
 #include "chromeos/printing/printer_configuration.h"
+#include "chromeos/printing/uri.h"
 #include "components/keyed_service/core/keyed_service.h"
 
 class PrefService;
@@ -33,7 +34,7 @@
 class UsbPrinterNotificationController;
 
 // Returns true if |printer_uri| is an IPP uri.
-bool IsIppUri(base::StringPiece printer_uri);
+bool IsIppUri(const Uri& printer_uri);
 
 // Top level manager of available CUPS printers in ChromeOS.  All functions
 // in this class must be called from a sequenced context.
diff --git a/chrome/browser/chromeos/printing/cups_printers_manager_unittest.cc b/chrome/browser/chromeos/printing/cups_printers_manager_unittest.cc
index 0438c5a..6258c2e7 100644
--- a/chrome/browser/chromeos/printing/cups_printers_manager_unittest.cc
+++ b/chrome/browser/chromeos/printing/cups_printers_manager_unittest.cc
@@ -434,7 +434,7 @@
                                                        const std::string& uri) {
   PrinterDetector::DetectedPrinter ret;
   ret.printer.set_id(id);
-  ret.printer.set_uri(uri);
+  ret.printer.SetUri(uri);
   return ret;
 }
 
@@ -446,7 +446,7 @@
 // Calls MakeDiscoveredPrinter with the USB protocol as the uri.
 PrinterDetector::DetectedPrinter MakeUsbDiscoveredPrinter(
     const std::string& id) {
-  return MakeDiscoveredPrinter(id, "usb:");
+  return MakeDiscoveredPrinter(id, "usb://host/path");
 }
 
 // Pseudo-constructor for inline creation of a DetectedPrinter that should (in
@@ -747,7 +747,7 @@
   manager_->PrinterInstalled(printer, /*is_automatic=*/false);
 
   Printer updated(printer);
-  updated.set_uri("different value");
+  updated.SetUri("ipp://different.value");
   EXPECT_FALSE(manager_->IsPrinterInstalled(updated));
 
   updated = printer;
@@ -774,7 +774,7 @@
 // Test that we can save non-discovered printers.
 TEST_F(CupsPrintersManagerTest, SavePrinterSucceedsOnManualPrinter) {
   Printer printer(kPrinterId);
-  printer.set_uri("manual uri");
+  printer.SetUri("ipp://manual.uri");
   manager_->SavePrinter(printer);
 
   auto saved_printers = manager_->GetPrinters(PrinterClass::kSaved);
@@ -800,7 +800,7 @@
   EXPECT_TRUE(manager_->IsPrinterInstalled(printer));
 
   Printer updated(printer);
-  updated.set_uri("different value");
+  updated.SetUri("ipps://different/value");
   EXPECT_FALSE(manager_->IsPrinterInstalled(updated));
 
   manager_->SavePrinter(updated);
@@ -816,7 +816,7 @@
 // Automatic USB Printer is configured automatically.
 TEST_F(CupsPrintersManagerTest, AutomaticUsbPrinterIsInstalledAutomatically) {
   auto automatic_printer = MakeAutomaticPrinter(kPrinterId);
-  automatic_printer.printer.set_uri("usb:");
+  automatic_printer.printer.SetUri("usb://host/path");
 
   usb_detector_->AddDetections({automatic_printer});
 
@@ -832,7 +832,7 @@
   UpdatePolicyValue(prefs::kUserNativePrintersAllowed, false);
 
   auto automatic_printer = MakeAutomaticPrinter(kPrinterId);
-  automatic_printer.printer.set_uri("usb:");
+  automatic_printer.printer.SetUri("usb://host/path");
 
   zeroconf_detector_->AddDetections({automatic_printer});
 
@@ -845,7 +845,7 @@
 // installed.
 TEST_F(CupsPrintersManagerTest, OtherNearbyPrintersNotInstalledAutomatically) {
   auto discovered_printer = MakeDiscoveredPrinter("Discovered");
-  discovered_printer.printer.set_uri("usb:");
+  discovered_printer.printer.SetUri("usb://host/path");
   auto automatic_printer = MakeAutomaticPrinter("Automatic");
 
   usb_detector_->AddDetections({discovered_printer});
@@ -861,7 +861,7 @@
 
 TEST_F(CupsPrintersManagerTest, DetectedUsbPrinterConfigurationNotification) {
   auto discovered_printer = MakeDiscoveredPrinter("Discovered");
-  discovered_printer.printer.set_uri("usb:");
+  discovered_printer.printer.SetUri("usb://host/path");
 
   usb_detector_->AddDetections({discovered_printer});
   task_environment_.RunUntilIdle();
@@ -877,7 +877,7 @@
 TEST_F(CupsPrintersManagerTest,
        DetectedZeroconfDiscoveredPrinterNoNotification) {
   auto discovered_printer = MakeDiscoveredPrinter("Discovered");
-  discovered_printer.printer.set_uri("ipp:");
+  discovered_printer.printer.SetUri("ipp://host");
 
   zeroconf_detector_->AddDetections({discovered_printer});
   task_environment_.RunUntilIdle();
@@ -937,18 +937,5 @@
                                      2, 1);
 }
 
-TEST_F(CupsPrintersManagerTest, IsIppUri) {
-  // IPP protocol
-  ASSERT_TRUE(IsIppUri("ipp://1.2.3.4"));
-  // IPPS protocol
-  ASSERT_TRUE(IsIppUri("ipps://1.2.3.4"));
-  // USB protocol
-  ASSERT_FALSE(IsIppUri("usb://1.2.3.4"));
-  // Malformed URI
-  ASSERT_FALSE(IsIppUri("ipp/1.2.3.4"));
-  // Empty URI
-  ASSERT_FALSE(IsIppUri(""));
-}
-
 }  // namespace
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/history/print_job_info_proto_conversions.cc b/chrome/browser/chromeos/printing/history/print_job_info_proto_conversions.cc
index df11e01..a78d9d9 100644
--- a/chrome/browser/chromeos/printing/history/print_job_info_proto_conversions.cc
+++ b/chrome/browser/chromeos/printing/history/print_job_info_proto_conversions.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/chromeos/printing/history/print_job_info_proto_conversions.h"
 
+#include <string>
+
 #include "base/optional.h"
 #include "chrome/browser/chromeos/printing/printer_error_codes.h"
 #include "printing/mojom/print.mojom.h"
@@ -133,7 +135,7 @@
 proto::Printer PrinterToProto(const chromeos::Printer& printer) {
   proto::Printer printer_proto;
   printer_proto.set_name(printer.display_name());
-  printer_proto.set_uri(printer.uri());
+  printer_proto.set_uri(printer.uri().GetNormalized());
   printer_proto.set_source(PrinterSourceToProto(printer.source()));
   return printer_proto;
 }
diff --git a/chrome/browser/chromeos/printing/history/print_job_info_proto_conversions_unittest.cc b/chrome/browser/chromeos/printing/history/print_job_info_proto_conversions_unittest.cc
index 8976259..5f46658 100644
--- a/chrome/browser/chromeos/printing/history/print_job_info_proto_conversions_unittest.cc
+++ b/chrome/browser/chromeos/printing/history/print_job_info_proto_conversions_unittest.cc
@@ -64,7 +64,7 @@
 
   chromeos::Printer printer;
   printer.set_display_name(kName);
-  printer.set_uri(kUri);
+  printer.SetUri(kUri);
   printer.set_source(chromeos::Printer::Source::SRC_POLICY);
 
   proto::PrintSettings settings;
diff --git a/chrome/browser/chromeos/printing/print_management/print_job_info_mojom_conversions.cc b/chrome/browser/chromeos/printing/print_management/print_job_info_mojom_conversions.cc
index 06ca63c..7d340284 100644
--- a/chrome/browser/chromeos/printing/print_management/print_job_info_mojom_conversions.cc
+++ b/chrome/browser/chromeos/printing/print_management/print_job_info_mojom_conversions.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/chromeos/printing/print_management/print_job_info_mojom_conversions.h"
 
+#include <utility>
+
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
 #include "chrome/browser/chromeos/printing/cups_print_job.h"
@@ -130,7 +132,7 @@
   print_job_mojom->number_of_pages = job.total_page_number();
   print_job_mojom->printer_name =
       base::UTF8ToUTF16(job.printer().display_name());
-  print_job_mojom->printer_uri = GURL(job.printer().uri());
+  print_job_mojom->printer_uri = GURL(job.printer().uri().GetNormalized());
   return print_job_mojom;
 }
 
diff --git a/chrome/browser/chromeos/printing/print_management/print_job_info_mojom_conversions_unittest.cc b/chrome/browser/chromeos/printing/print_management/print_job_info_mojom_conversions_unittest.cc
index 88156260..954aff33 100644
--- a/chrome/browser/chromeos/printing/print_management/print_job_info_mojom_conversions_unittest.cc
+++ b/chrome/browser/chromeos/printing/print_management/print_job_info_mojom_conversions_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/chromeos/printing/print_management/print_job_info_mojom_conversions.h"
 
+#include <memory>
+
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
 #include "chrome/browser/chromeos/printing/cups_print_job.h"
@@ -56,7 +58,7 @@
 std::unique_ptr<CupsPrintJob> CreateCupsPrintJob() {
   Printer printer;
   printer.set_display_name(kName);
-  printer.set_uri(kUri);
+  printer.SetUri(kUri);
   printer.set_id(kPrinterId);
 
   auto cups_print_job = std::make_unique<CupsPrintJob>(
diff --git a/chrome/browser/chromeos/printing/printer_configurer.cc b/chrome/browser/chromeos/printing/printer_configurer.cc
index 9228d0f..114210a8 100644
--- a/chrome/browser/chromeos/printing/printer_configurer.cc
+++ b/chrome/browser/chromeos/printing/printer_configurer.cc
@@ -111,7 +111,7 @@
                     PrinterSetupCallback callback) override {
     DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
     DCHECK(!printer.id().empty());
-    DCHECK(!printer.uri().empty());
+    DCHECK(printer.HasUri());
     PRINTER_LOG(USER) << printer.make_and_model() << " Printer setup requested";
     // Record if autoconf and a PPD are set.  crbug.com/814374.
     RecordValidPpdReference(printer);
@@ -130,7 +130,7 @@
                        << " Attempting autoconf setup";
     auto* client = DBusThreadManager::Get()->GetDebugDaemonClient();
     client->CupsAddAutoConfiguredPrinter(
-        printer.id(), printer.uri(),
+        printer.id(), printer.uri().GetNormalized(),
         base::BindOnce(&PrinterConfigurerImpl::OnAddedPrinter,
                        weak_factory_.GetWeakPtr(), printer,
                        std::move(callback)));
@@ -160,7 +160,7 @@
 
     PRINTER_LOG(EVENT) << printer.make_and_model() << " Manual printer setup";
     client->CupsAddManuallyConfiguredPrinter(
-        printer.id(), printer.uri(), ppd_contents,
+        printer.id(), printer.uri().GetNormalized(), ppd_contents,
         base::BindOnce(&PrinterConfigurerImpl::OnAddedPrinter,
                        weak_factory_.GetWeakPtr(), printer, std::move(cb)));
   }
@@ -205,7 +205,7 @@
   base::MD5Context ctx;
   base::MD5Init(&ctx);
   base::MD5Update(&ctx, printer.id());
-  base::MD5Update(&ctx, printer.uri());
+  base::MD5Update(&ctx, printer.uri().GetNormalized());
   base::MD5Update(&ctx, printer.ppd_reference().user_supplied_ppd_url);
   base::MD5Update(&ctx, printer.ppd_reference().effective_make_and_model);
   char autoconf = printer.ppd_reference().autoconf ? 1 : 0;
diff --git a/chrome/browser/chromeos/printing/printers_map_unittest.cc b/chrome/browser/chromeos/printing/printers_map_unittest.cc
index aa826e8..be3dee87 100644
--- a/chrome/browser/chromeos/printing/printers_map_unittest.cc
+++ b/chrome/browser/chromeos/printing/printers_map_unittest.cc
@@ -192,27 +192,27 @@
   PrintersMap printers_map;
 
   Printer ipp_printer = Printer("ipp");
-  ipp_printer.set_uri("ipp:printer");
+  ipp_printer.SetUri("ipp://printer");
   printers_map.Insert(PrinterClass::kSaved, ipp_printer);
 
   Printer ipps_printer = Printer("ipps");
-  ipps_printer.set_uri("ipps:printer");
+  ipps_printer.SetUri("ipps://printer");
   printers_map.Insert(PrinterClass::kAutomatic, ipps_printer);
 
   Printer usb_printer = Printer("usb");
-  usb_printer.set_uri("usb:printer");
+  usb_printer.SetUri("usb://printer/path");
   printers_map.Insert(PrinterClass::kDiscovered, usb_printer);
 
   Printer ippusb_printer = Printer("ippusb");
-  ippusb_printer.set_uri("ippusb:printer");
+  ippusb_printer.SetUri("ippusb://printer/path");
   printers_map.Insert(PrinterClass::kEnterprise, ippusb_printer);
 
   Printer http_printer = Printer("http");
-  http_printer.set_uri("http:printer");
+  http_printer.SetUri("http://printer");
   printers_map.Insert(PrinterClass::kDiscovered, http_printer);
 
   Printer https_printer = Printer("https");
-  https_printer.set_uri("https://printer");
+  https_printer.SetUri("https://printer");
   printers_map.Insert(PrinterClass::kDiscovered, https_printer);
 
   // Only HTTPS, IPPS, IPPUSB, and USB printers are returned.
@@ -242,23 +242,23 @@
   PrintersMap printers_map;
 
   Printer ipp_printer = Printer("ipp");
-  ipp_printer.set_uri("ipp:printer");
+  ipp_printer.SetUri("ipp://printer");
   printers_map.Insert(PrinterClass::kSaved, ipp_printer);
 
   Printer ipps_printer = Printer("ipps");
-  ipps_printer.set_uri("ipps:printer");
+  ipps_printer.SetUri("ipps://printer");
   printers_map.Insert(PrinterClass::kSaved, ipps_printer);
 
   Printer usb_printer = Printer("usb");
-  usb_printer.set_uri("usb:printer");
+  usb_printer.SetUri("usb://printer/path");
   printers_map.Insert(PrinterClass::kSaved, usb_printer);
 
   Printer ippusb_printer = Printer("ippusb");
-  ippusb_printer.set_uri("ippusb:printer");
+  ippusb_printer.SetUri("ippusb://printer/path");
   printers_map.Insert(PrinterClass::kSaved, ippusb_printer);
 
   Printer http_printer = Printer("http");
-  http_printer.set_uri("http:printer");
+  http_printer.SetUri("http://printer");
   printers_map.Insert(PrinterClass::kSaved, http_printer);
 
   // Only IPPS, IPPUSB, and USB printers are returned.
@@ -507,7 +507,7 @@
   PrintersMap printers_map;
   const std::string printer_id = "id";
   Printer ipps_printer = Printer(printer_id);
-  ipps_printer.set_uri("ipps:printer");
+  ipps_printer.SetUri("ipps://printer");
   printers_map.Insert(PrinterClass::kDiscovered, ipps_printer);
 
   CupsPrinterStatus saved_printer_status = CreatePrinterStatus(printer_id);
diff --git a/chrome/browser/chromeos/printing/server_printers_fetcher.cc b/chrome/browser/chromeos/printing/server_printers_fetcher.cc
index a96a400..f3acd78 100644
--- a/chrome/browser/chromeos/printing/server_printers_fetcher.cc
+++ b/chrome/browser/chromeos/printing/server_printers_fetcher.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/chromeos/printing/server_printers_fetcher.h"
 
 #include <string>
+#include <utility>
 
 #include "base/hash/md5.h"
 #include "base/logging.h"
@@ -194,19 +195,20 @@
     // * http://myprinter:123/abc =>  ipp://myprinter:123/abc
     // * http://myprinter/abc     =>  ipp://myprinter:80/abc
     // * https://myprinter/abc    =>  ipps://myprinter:443/abc
-    std::string url = "ipp";
-    if (server_url_.SchemeIs("https"))
-      url += "s";
-    url += "://";
-    url += server_url_.HostNoBrackets();
-    url += ":";
-    url += base::NumberToString(server_url_.EffectiveIntPort());
+    Uri url;
+    if (server_url_.SchemeIs("https")) {
+      url.SetScheme("ipps");
+    } else {
+      url.SetScheme("ipp");
+    }
+    url.SetHostEncoded(server_url_.HostNoBrackets());
+    url.SetPort(server_url_.EffectiveIntPort());
     // Save the server URI.
-    printer->set_print_server_uri(url);
+    printer->set_print_server_uri(url.GetNormalized());
     // Complete building the printer's URI.
-    url += "/printers/" + name;
-    printer->set_uri(url);
-    printer->set_id(ServerPrinterId(url));
+    url.SetPath({"printers", name});
+    printer->SetUri(url);
+    printer->set_id(ServerPrinterId(url.GetNormalized()));
   }
 
   const ServerPrintersFetcher* owner_;
diff --git a/chrome/browser/chromeos/printing/specifics_translation.cc b/chrome/browser/chromeos/printing/specifics_translation.cc
index 33157e0..21781284 100644
--- a/chrome/browser/chromeos/printing/specifics_translation.cc
+++ b/chrome/browser/chromeos/printing/specifics_translation.cc
@@ -8,6 +8,7 @@
 #include <vector>
 
 #include "base/check_op.h"
+#include "base/logging.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
 #include "base/time/time.h"
@@ -65,7 +66,11 @@
     printer->set_make_and_model(
         MakeAndModel(specifics.manufacturer(), specifics.model()));
   }
-  printer->set_uri(specifics.uri());
+
+  std::string message;
+  if (!printer->SetUri(specifics.uri(), &message))
+    LOG(WARNING) << message;
+
   printer->set_uuid(specifics.uuid());
   printer->set_print_server_uri(specifics.print_server_uri());
 
@@ -104,8 +109,8 @@
   if (!printer.make_and_model().empty())
     specifics->set_make_and_model(printer.make_and_model());
 
-  if (!printer.uri().empty())
-    specifics->set_uri(printer.uri());
+  if (printer.HasUri())
+    specifics->set_uri(printer.uri().GetNormalized());
 
   if (!printer.uuid().empty())
     specifics->set_uuid(printer.uuid());
diff --git a/chrome/browser/chromeos/printing/specifics_translation_unittest.cc b/chrome/browser/chromeos/printing/specifics_translation_unittest.cc
index fe92e62..df5dfcf 100644
--- a/chrome/browser/chromeos/printing/specifics_translation_unittest.cc
+++ b/chrome/browser/chromeos/printing/specifics_translation_unittest.cc
@@ -49,7 +49,7 @@
   EXPECT_EQ(kDisplayName, result->display_name());
   EXPECT_EQ(kDescription, result->description());
   EXPECT_EQ(kMakeAndModel, result->make_and_model());
-  EXPECT_EQ(kUri, result->uri());
+  EXPECT_EQ(kUri, result->uri().GetNormalized());
   EXPECT_EQ(kUuid, result->uuid());
 
   EXPECT_EQ(kEffectiveMakeAndModel,
@@ -63,7 +63,7 @@
   printer.set_display_name(kDisplayName);
   printer.set_description(kDescription);
   printer.set_make_and_model(kMakeAndModel);
-  printer.set_uri(kUri);
+  printer.SetUri(kUri);
   printer.set_uuid(kUuid);
 
   Printer::PpdReference ppd;
@@ -91,7 +91,7 @@
   printer.set_manufacturer(kManufacturer);
   printer.set_model(kModel);
   printer.set_make_and_model(kMakeAndModel);
-  printer.set_uri(kUri);
+  printer.SetUri(kUri);
   printer.set_uuid(kUuid);
 
   Printer::PpdReference ppd;
@@ -107,7 +107,7 @@
   EXPECT_EQ(kManufacturer, result->manufacturer());
   EXPECT_EQ(kModel, result->model());
   EXPECT_EQ(kMakeAndModel, result->make_and_model());
-  EXPECT_EQ(kUri, result->uri());
+  EXPECT_EQ(kUri, result->uri().GetNormalized());
   EXPECT_EQ(kUuid, result->uuid());
 
   EXPECT_TRUE(result->ppd_reference().effective_make_and_model.empty());
diff --git a/chrome/browser/chromeos/printing/synced_printers_manager_unittest.cc b/chrome/browser/chromeos/printing/synced_printers_manager_unittest.cc
index b0d6226..b4b1438 100644
--- a/chrome/browser/chromeos/printing/synced_printers_manager_unittest.cc
+++ b/chrome/browser/chromeos/printing/synced_printers_manager_unittest.cc
@@ -116,7 +116,7 @@
 TEST_F(SyncedPrintersManagerTest, UpdatePrinter) {
   manager_->UpdateSavedPrinter(Printer(kTestPrinterId));
   Printer updated_printer(kTestPrinterId);
-  updated_printer.set_uri(kTestUri);
+  updated_printer.SetUri(kTestUri);
 
   // Register observer so it only receives the update event.
   LoggingObserver observer(manager_.get());
@@ -125,7 +125,7 @@
 
   auto printers = manager_->GetSavedPrinters();
   ASSERT_EQ(1U, printers.size());
-  EXPECT_EQ(kTestUri, printers[0].uri());
+  EXPECT_EQ(kTestUri, printers[0].uri().GetNormalized());
 
   ExpectObservedPrinterIdsAre(observer.saved_printers(), {kTestPrinterId});
 }
diff --git a/chrome/browser/chromeos/printing/usb_printer_util.cc b/chrome/browser/chromeos/printing/usb_printer_util.cc
index 48b2caa..c3557fc 100644
--- a/chrome/browser/chromeos/printing/usb_printer_util.cc
+++ b/chrome/browser/chromeos/printing/usb_printer_util.cc
@@ -109,31 +109,6 @@
       base::BindOnce(OnClaimInterface, std::move(device), std::move(cb)));
 }
 
-// Escape URI strings the same way cups does it, so we end up with a URI cups
-// recognizes.  Cups hex-encodes '%', ' ', and anything not in the standard
-// ASCII range.  CUPS lets everything else through unchanged.
-//
-// TODO(justincarlson): Determine whether we should apply escaping at the
-// outgoing edge, when we send Printer information to CUPS, instead of
-// pre-escaping at the point the field is filled in.
-//
-// https://crbug.com/701606
-std::string CupsURIEscape(const std::string& uri_in) {
-  static const char kHexDigits[] = "0123456789ABCDEF";
-  std::vector<char> buf;
-  buf.reserve(uri_in.size());
-  for (char c : uri_in) {
-    if (c == ' ' || c == '%' || (c & 0x80)) {
-      buf.push_back('%');
-      buf.push_back(kHexDigits[(c >> 4) & 0xf]);
-      buf.push_back(kHexDigits[c & 0xf]);
-    } else {
-      buf.push_back(c);
-    }
-  }
-  return std::string(buf.data(), buf.size());
-}
-
 // Incorporate the bytes of |val| into the incremental hash carried in |ctx| in
 // big-endian order.  |val| must be a simple integer type
 template <typename T>
@@ -239,7 +214,7 @@
 
 // Gets the URI CUPS would use to refer to this USB device.  Assumes device
 // is a printer.
-std::string UsbPrinterUri(const UsbDeviceInfo& device_info) {
+Uri UsbPrinterUri(const UsbDeviceInfo& device_info) {
   // Note that serial may, for some devices, be empty or bogus (all zeros, non
   // unique, or otherwise not really a serial number), but having a non-unique
   // or empty serial field in the URI still lets us print, it just means we
@@ -249,9 +224,12 @@
   // don't supply a serial number, we don't have any reliable way to do that
   // differentiation.
   std::string serial = base::UTF16ToUTF8(GetSerialNumber(device_info));
-  return CupsURIEscape(
-      base::StringPrintf("usb://%04x/%04x?serial=%s", device_info.vendor_id,
-                         device_info.product_id, serial.c_str()));
+  Uri uri;
+  uri.SetScheme("usb");
+  uri.SetHost(base::StringPrintf("%04x", device_info.vendor_id));
+  uri.SetPath({base::StringPrintf("%04x", device_info.product_id)});
+  uri.SetQuery({{"serial", serial}});
+  return uri;
 }
 
 }  // namespace
@@ -314,7 +292,7 @@
   }
 
   printer->set_description(printer->display_name());
-  printer->set_uri(UsbPrinterUri(device_info));
+  printer->SetUri(UsbPrinterUri(device_info));
   printer->set_id(CreateUsbPrinterId(device_info));
   printer->set_supports_ippusb(UsbDeviceSupportsIppusb(device_info));
   return printer;
diff --git a/chrome/browser/chromeos/printing/zeroconf_printer_detector.cc b/chrome/browser/chromeos/printing/zeroconf_printer_detector.cc
index 6485d1b..b4382d4 100644
--- a/chrome/browser/chromeos/printing/zeroconf_printer_detector.cc
+++ b/chrome/browser/chromeos/printing/zeroconf_printer_detector.cc
@@ -155,17 +155,17 @@
   printer.set_display_name(service_description.instance_name());
   printer.set_description(metadata.note);
   printer.set_make_and_model(metadata.product);
-  const char* uri_protocol;
+  Uri uri;
   std::string rp = metadata.rp;
   if (service_type == ZeroconfPrinterDetector::kIppServiceName ||
       service_type == ZeroconfPrinterDetector::kIppEverywhereServiceName) {
-    uri_protocol = "ipp";
+    uri.SetScheme("ipp");
   } else if (service_type == ZeroconfPrinterDetector::kIppsServiceName ||
              service_type ==
                  ZeroconfPrinterDetector::kIppsEverywhereServiceName) {
-    uri_protocol = "ipps";
+    uri.SetScheme("ipps");
   } else if (service_type == ZeroconfPrinterDetector::kSocketServiceName) {
-    uri_protocol = "socket";
+    uri.SetScheme("socket");
     // Bonjour Printing Specification v1.2.1 section 9.2.2:
     // If the "rp" key is present in a Socket TXT record, the key/value MUST
     // be ignored.
@@ -177,9 +177,11 @@
                  << service_description.service_type();
     return false;
   }
-  printer.set_uri(base::StringPrintf(
-      "%s://%s/%s", uri_protocol,
-      service_description.address.ToString().c_str(), rp.c_str()));
+
+  if (!uri.SetHostEncoded(service_description.address.HostForURL()) ||
+      !uri.SetPort(service_description.address.port()) ||
+      !uri.SetPathEncoded("/" + rp) || !printer.SetUri(uri))
+    return false;
 
   // Per the IPP Everywhere Standard 5100.14-2013, section 4.2.1, IPP
   // everywhere-capable printers advertise services prefixed with "_print"
diff --git a/chrome/browser/chromeos/printing/zeroconf_printer_detector_unittest.cc b/chrome/browser/chromeos/printing/zeroconf_printer_detector_unittest.cc
index b944552..fd12c9e 100644
--- a/chrome/browser/chromeos/printing/zeroconf_printer_detector_unittest.cc
+++ b/chrome/browser/chromeos/printing/zeroconf_printer_detector_unittest.cc
@@ -97,8 +97,8 @@
       rp = "";
       break;
   }
-  printer.set_uri(base::StringPrintf("%s://%s.local:%d/%s", scheme.c_str(),
-                                     name.c_str(), port, rp.c_str()));
+  printer.SetUri(base::StringPrintf("%s://%s.local:%d/%s", scheme.c_str(),
+                                    name.c_str(), port, rp.c_str()));
 
   printer.set_uuid(base::StrCat({name, "_UUID"}));
   printer.set_display_name(name);
diff --git a/chrome/browser/chromeos/system/device_disabling_manager_unittest.cc b/chrome/browser/chromeos/system/device_disabling_manager_unittest.cc
index 89a2a25..011f3b0 100644
--- a/chrome/browser/chromeos/system/device_disabling_manager_unittest.cc
+++ b/chrome/browser/chromeos/system/device_disabling_manager_unittest.cc
@@ -177,8 +177,7 @@
 void DeviceDisablingManagerOOBETest::SetDeviceDisabled(bool disabled) {
   DictionaryPrefUpdate dict(&local_state_, prefs::kServerBackedDeviceState);
   if (disabled) {
-    dict->SetString(policy::kDeviceStateMode,
-                    policy::kDeviceStateRestoreModeDisabled);
+    dict->SetString(policy::kDeviceStateMode, policy::kDeviceStateModeDisabled);
   } else {
     dict->Remove(policy::kDeviceStateMode, nullptr);
   }
diff --git a/chrome/browser/download/android/download_manager_service.cc b/chrome/browser/download/android/download_manager_service.cc
index 3d3c362..cf79ed0 100644
--- a/chrome/browser/download/android/download_manager_service.cc
+++ b/chrome/browser/download/android/download_manager_service.cc
@@ -30,7 +30,9 @@
 #include "chrome/browser/download/simple_download_manager_coordinator_factory.h"
 #include "chrome/browser/flags/android/cached_feature_flags.h"
 #include "chrome/browser/flags/android/chrome_feature_list.h"
+#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/profiles/profile_key.h"
+#include "chrome/browser/profiles/profile_key_android.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/common/chrome_constants.h"
 #include "components/download/network/android/network_status_listener_android.h"
@@ -90,6 +92,10 @@
           OfflineItemUtils::ConvertDownloadRenameResultToRenameResult(result)));
 }
 
+bool IsReducedModeProfileKey(ProfileKey* profile_key) {
+  return profile_key == ProfileKeyStartupAccessor::GetInstance()->profile_key();
+}
+
 }  // namespace
 
 // static
@@ -199,7 +205,6 @@
 DownloadManagerService::DownloadManagerService()
     : is_manager_initialized_(false),
       is_pending_downloads_loaded_(false),
-      pending_get_downloads_actions_(NONE),
       original_coordinator_(nullptr),
       off_the_record_coordinator_(nullptr) {}
 
@@ -210,7 +215,8 @@
                                   bool is_profile_added) {
   java_ref_.Reset(env, obj);
   if (is_profile_added) {
-    OnProfileAdded(env, obj);
+    OnProfileAdded(
+        ProfileManager::GetActiveUserProfile()->GetOriginalProfile());
   } else {
     // In reduced mode, only non-incognito downloads should be loaded.
     ResetCoordinatorIfNeeded(
@@ -218,18 +224,23 @@
   }
 }
 
-void DownloadManagerService::OnProfileAdded(JNIEnv* env, jobject obj) {
-  Profile* profile =
-      ProfileManager::GetActiveUserProfile()->GetOriginalProfile();
-  InitializeForProfile(profile);
+void DownloadManagerService::OnProfileAdded(
+    JNIEnv* env,
+    jobject obj,
+    const JavaParamRef<jobject>& j_profile) {
+  OnProfileAdded(ProfileAndroid::FromProfileAndroid(j_profile));
+}
+
+void DownloadManagerService::OnProfileAdded(Profile* profile) {
+  InitializeForProfile(profile->GetProfileKey());
   observed_profiles_.Add(profile);
-  if (profile->HasOffTheRecordProfile())
-    InitializeForProfile(profile->GetOffTheRecordProfile());
+  for (Profile* otr : profile->GetAllOffTheRecordProfiles())
+    InitializeForProfile(otr->GetProfileKey());
 }
 
 void DownloadManagerService::OnOffTheRecordProfileCreated(
     Profile* off_the_record) {
-  InitializeForProfile(off_the_record);
+  InitializeForProfile(off_the_record->GetProfileKey());
 }
 
 void DownloadManagerService::OpenDownload(download::DownloadItem* download,
@@ -261,13 +272,14 @@
     JNIEnv* env,
     jobject obj,
     const JavaParamRef<jstring>& jdownload_guid,
-    bool is_off_the_record,
+    const JavaParamRef<jobject>& j_profile_key,
     jint source) {
   if (!is_manager_initialized_)
     return;
 
   std::string download_guid = ConvertJavaStringToUTF8(env, jdownload_guid);
-  download::DownloadItem* item = GetDownload(download_guid, is_off_the_record);
+  download::DownloadItem* item = GetDownload(
+      download_guid, ProfileKeyAndroid::FromProfileKeyAndroid(j_profile_key));
   if (!item)
     return;
 
@@ -278,11 +290,13 @@
     JNIEnv* env,
     jobject obj,
     const JavaParamRef<jstring>& jdownload_guid,
-    bool is_off_the_record,
+    const JavaParamRef<jobject>& j_profile_key,
     bool has_user_gesture) {
   std::string download_guid = ConvertJavaStringToUTF8(env, jdownload_guid);
-  if (is_pending_downloads_loaded_ || is_off_the_record) {
-    ResumeDownloadInternal(download_guid, is_off_the_record, has_user_gesture);
+  ProfileKey* profile_key =
+      ProfileKeyAndroid::FromProfileKeyAndroid(j_profile_key);
+  if (is_pending_downloads_loaded_ || profile_key->IsOffTheRecord()) {
+    ResumeDownloadInternal(download_guid, profile_key, has_user_gesture);
   } else {
     EnqueueDownloadAction(download_guid,
                           DownloadActionParams(RESUME, has_user_gesture));
@@ -293,11 +307,13 @@
     JNIEnv* env,
     jobject obj,
     const JavaParamRef<jstring>& jdownload_guid,
-    bool is_off_the_record,
+    const JavaParamRef<jobject>& j_profile_key,
     bool has_user_gesture) {
   std::string download_guid = ConvertJavaStringToUTF8(env, jdownload_guid);
-  if (is_pending_downloads_loaded_ || is_off_the_record)
-    RetryDownloadInternal(download_guid, is_off_the_record, has_user_gesture);
+  ProfileKey* profile_key =
+      ProfileKeyAndroid::FromProfileKeyAndroid(j_profile_key);
+  if (is_pending_downloads_loaded_ || profile_key->IsOffTheRecord())
+    RetryDownloadInternal(download_guid, profile_key, has_user_gesture);
   else
     EnqueueDownloadAction(download_guid, DownloadActionParams(RETRY));
 }
@@ -306,10 +322,12 @@
     JNIEnv* env,
     jobject obj,
     const JavaParamRef<jstring>& jdownload_guid,
-    bool is_off_the_record) {
+    const JavaParamRef<jobject>& j_profile_key) {
   std::string download_guid = ConvertJavaStringToUTF8(env, jdownload_guid);
-  if (is_pending_downloads_loaded_ || is_off_the_record)
-    PauseDownloadInternal(download_guid, is_off_the_record);
+  ProfileKey* profile_key =
+      ProfileKeyAndroid::FromProfileKeyAndroid(j_profile_key);
+  if (is_pending_downloads_loaded_ || profile_key->IsOffTheRecord())
+    PauseDownloadInternal(download_guid, profile_key);
   else
     EnqueueDownloadAction(download_guid, DownloadActionParams(PAUSE));
 }
@@ -318,32 +336,34 @@
     JNIEnv* env,
     jobject obj,
     const JavaParamRef<jstring>& jdownload_guid,
-    bool is_off_the_record) {
+    const JavaParamRef<jobject>& j_profile_key) {
   std::string download_guid = ConvertJavaStringToUTF8(env, jdownload_guid);
-  if (is_manager_initialized_ || is_off_the_record)
-    RemoveDownloadInternal(download_guid, is_off_the_record);
+  ProfileKey* profile_key =
+      ProfileKeyAndroid::FromProfileKeyAndroid(j_profile_key);
+  if (is_manager_initialized_ || profile_key->IsOffTheRecord())
+    RemoveDownloadInternal(download_guid, profile_key);
   else
     EnqueueDownloadAction(download_guid, DownloadActionParams(REMOVE));
 }
 
-void DownloadManagerService::GetAllDownloads(JNIEnv* env,
-                                             const JavaParamRef<jobject>& obj,
-                                             bool is_off_the_record) {
+void DownloadManagerService::GetAllDownloads(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& obj,
+    const JavaParamRef<jobject>& j_profile_key) {
+  ProfileKey* profile_key =
+      ProfileKeyAndroid::FromProfileKeyAndroid(j_profile_key);
   if (is_manager_initialized_) {
-    GetAllDownloadsInternal(is_off_the_record);
+    GetAllDownloadsInternal(profile_key);
     return;
   }
 
   // Full download manager is required for this call.
-  GetDownloadManager(is_off_the_record);
-  if (is_off_the_record)
-    pending_get_downloads_actions_ |= OFF_THE_RECORD;
-  else
-    pending_get_downloads_actions_ |= REGULAR;
+  GetDownloadManager(profile_key);
+  profiles_with_pending_get_downloads_actions_.push_back(profile_key);
 }
 
-void DownloadManagerService::GetAllDownloadsInternal(bool is_off_the_record) {
-  content::DownloadManager* manager = GetDownloadManager(is_off_the_record);
+void DownloadManagerService::GetAllDownloadsInternal(ProfileKey* profile_key) {
+  content::DownloadManager* manager = GetDownloadManager(profile_key);
   if (java_ref_.is_null() || !manager)
     return;
 
@@ -367,20 +387,22 @@
   }
 
   Java_DownloadManagerService_onAllDownloadsRetrieved(
-      env, java_ref_, j_download_item_list, is_off_the_record);
+      env, java_ref_, j_download_item_list,
+      ProfileKeyAndroid(profile_key).GetJavaObject());
 }
 
 void DownloadManagerService::CheckForExternallyRemovedDownloads(
     JNIEnv* env,
     const JavaParamRef<jobject>& obj,
-    bool is_off_the_record) {
+    const JavaParamRef<jobject>& j_profile_key) {
   // Once the DownloadManager is initlaized, DownloadHistory will check for the
   // removal of history files. If the history query is not yet complete, ignore
   // requests to check for externally removed downloads.
   if (!is_manager_initialized_)
     return;
 
-  content::DownloadManager* manager = GetDownloadManager(is_off_the_record);
+  content::DownloadManager* manager = GetDownloadManager(
+      ProfileKeyAndroid::FromProfileKeyAndroid(j_profile_key));
   if (!manager)
     return;
   manager->CheckForHistoryFilesRemoval();
@@ -390,9 +412,11 @@
     JNIEnv* env,
     const JavaParamRef<jobject>& obj,
     const JavaParamRef<jstring>& jdownload_guid,
-    bool is_off_the_record) {
+    const JavaParamRef<jobject>& j_profile_key) {
   std::string download_guid = ConvertJavaStringToUTF8(env, jdownload_guid);
-  download::DownloadItem* item = GetDownload(download_guid, is_off_the_record);
+  ProfileKey* profile_key =
+      ProfileKeyAndroid::FromProfileKeyAndroid(j_profile_key);
+  download::DownloadItem* item = GetDownload(download_guid, profile_key);
   if (item)
     item->SetLastAccessTime(base::Time::Now());
 }
@@ -401,10 +425,12 @@
     JNIEnv* env,
     jobject obj,
     const JavaParamRef<jstring>& jdownload_guid,
-    bool is_off_the_record) {
+    const JavaParamRef<jobject>& j_profile_key) {
   std::string download_guid = ConvertJavaStringToUTF8(env, jdownload_guid);
-  if (is_pending_downloads_loaded_ || is_off_the_record)
-    CancelDownloadInternal(download_guid, is_off_the_record);
+  ProfileKey* profile_key =
+      ProfileKeyAndroid::FromProfileKeyAndroid(j_profile_key);
+  if (is_pending_downloads_loaded_ || profile_key->IsOffTheRecord())
+    CancelDownloadInternal(download_guid, profile_key);
   else
     EnqueueDownloadAction(download_guid, DownloadActionParams(CANCEL));
 }
@@ -418,12 +444,12 @@
   }
   is_manager_initialized_ = true;
   OnPendingDownloadsLoaded();
-
-  // Respond to any requests to get all downloads.
-  if (pending_get_downloads_actions_ & REGULAR)
-    GetAllDownloadsInternal(false);
-  if (pending_get_downloads_actions_ & OFF_THE_RECORD)
-    GetAllDownloadsInternal(true);
+  while (!profiles_with_pending_get_downloads_actions_.empty()) {
+    ProfileKey* profile_key =
+        profiles_with_pending_get_downloads_actions_.back();
+    profiles_with_pending_get_downloads_actions_.pop_back();
+    GetAllDownloadsInternal(profile_key);
+  }
 }
 
 void DownloadManagerService::OnManagerGoingDown(
@@ -475,9 +501,9 @@
 
 void DownloadManagerService::ResumeDownloadInternal(
     const std::string& download_guid,
-    bool is_off_the_record,
+    ProfileKey* profile_key,
     bool has_user_gesture) {
-  download::DownloadItem* item = GetDownload(download_guid, is_off_the_record);
+  download::DownloadItem* item = GetDownload(download_guid, profile_key);
   if (!item) {
     OnResumptionFailed(download_guid);
     return;
@@ -494,9 +520,9 @@
 
 void DownloadManagerService::RetryDownloadInternal(
     const std::string& download_guid,
-    bool is_off_the_record,
+    ProfileKey* profile_key,
     bool has_user_gesture) {
-  content::DownloadManager* manager = GetDownloadManager(is_off_the_record);
+  content::DownloadManager* manager = GetDownloadManager(profile_key);
   if (!manager)
     return;
 
@@ -562,8 +588,8 @@
 
 void DownloadManagerService::CancelDownloadInternal(
     const std::string& download_guid,
-    bool is_off_the_record) {
-  download::DownloadItem* item = GetDownload(download_guid, is_off_the_record);
+    ProfileKey* profile_key) {
+  download::DownloadItem* item = GetDownload(download_guid, profile_key);
   if (item) {
     // Remove the observer first to avoid item->Cancel() causing re-entrance
     // issue.
@@ -574,16 +600,16 @@
 
 void DownloadManagerService::PauseDownloadInternal(
     const std::string& download_guid,
-    bool is_off_the_record) {
-  download::DownloadItem* item = GetDownload(download_guid, is_off_the_record);
+    ProfileKey* profile_key) {
+  download::DownloadItem* item = GetDownload(download_guid, profile_key);
   if (item)
     item->Pause();
 }
 
 void DownloadManagerService::RemoveDownloadInternal(
     const std::string& download_guid,
-    bool is_off_the_record) {
-  download::DownloadItem* item = GetDownload(download_guid, is_off_the_record);
+    ProfileKey* profile_key) {
+  download::DownloadItem* item = GetDownload(download_guid, profile_key);
   if (item)
     item->Remove();
 }
@@ -638,9 +664,9 @@
 
 download::DownloadItem* DownloadManagerService::GetDownload(
     const std::string& download_guid,
-    bool is_off_the_record) {
+    ProfileKey* profile_key) {
   download::SimpleDownloadManagerCoordinator* coordinator =
-      GetCoordinator(is_off_the_record);
+      GetCoordinator(profile_key);
   return coordinator ? coordinator->GetDownloadByGuid(download_guid) : nullptr;
 }
 
@@ -656,19 +682,27 @@
 
   download::AutoResumptionHandler::Get()->SetResumableDownloads(all_items);
 
+  ProfileKey* profile_key =
+      use_startup_accessor_profile_key_for_testing_
+          ? ProfileKeyStartupAccessor::GetInstance()->profile_key()
+          : ProfileManager::GetActiveUserProfile()
+                ->GetOriginalProfile()
+                ->GetProfileKey();
+
   for (auto iter = pending_actions_.begin(); iter != pending_actions_.end();
        ++iter) {
     DownloadActionParams params = iter->second;
     std::string download_guid = iter->first;
     switch (params.action) {
       case RESUME:
-        ResumeDownloadInternal(download_guid, false, params.has_user_gesture);
+        ResumeDownloadInternal(download_guid, profile_key,
+                               params.has_user_gesture);
         break;
       case PAUSE:
-        PauseDownloadInternal(download_guid, false);
+        PauseDownloadInternal(download_guid, profile_key);
         break;
       case CANCEL:
-        CancelDownloadInternal(download_guid, false);
+        CancelDownloadInternal(download_guid, profile_key);
         break;
       default:
         NOTREACHED();
@@ -679,14 +713,14 @@
 }
 
 content::DownloadManager* DownloadManagerService::GetDownloadManager(
-    bool is_off_the_record) {
-  Profile* profile = ProfileManager::GetActiveUserProfile();
-  if (is_off_the_record)
-    profile = profile->GetOffTheRecordProfile();
-
+    ProfileKey* profile_key) {
+  Profile* profile =
+      IsReducedModeProfileKey(profile_key)
+          ? ProfileManager::GetActiveUserProfile()
+          : ProfileManager::GetProfileFromProfileKey(profile_key);
   content::DownloadManager* manager =
       content::BrowserContext::GetDownloadManager(profile);
-  ResetCoordinatorIfNeeded(profile->GetProfileKey());
+  ResetCoordinatorIfNeeded(profile_key);
   return manager;
 }
 
@@ -699,6 +733,8 @@
 void DownloadManagerService::UpdateCoordinator(
     download::SimpleDownloadManagerCoordinator* new_coordinator,
     bool is_off_the_record) {
+  // TODO(https://crbug.com/1099577): Update to have separate coordinators per
+  // OTR profile.
   auto*& coordinator =
       is_off_the_record ? off_the_record_coordinator_ : original_coordinator_;
   if (!coordinator || coordinator != new_coordinator) {
@@ -710,9 +746,12 @@
 }
 
 download::SimpleDownloadManagerCoordinator*
-DownloadManagerService::GetCoordinator(bool is_off_the_record) {
-  return is_off_the_record ? off_the_record_coordinator_
-                           : original_coordinator_;
+DownloadManagerService::GetCoordinator(ProfileKey* profile_key) {
+  // TODO(https://crbug.com/1099577): Update to have separate coordinators per
+  // OTR profile.
+  bool use_original = use_startup_accessor_profile_key_for_testing_ ||
+                      !profile_key->IsOffTheRecord();
+  return use_original ? original_coordinator_ : off_the_record_coordinator_;
 }
 
 void DownloadManagerService::RenameDownload(
@@ -721,9 +760,11 @@
     const JavaParamRef<jstring>& id,
     const JavaParamRef<jstring>& name,
     const JavaParamRef<jobject>& j_callback,
-    bool is_off_the_record) {
+    const JavaParamRef<jobject>& j_profile_key) {
   std::string download_guid = ConvertJavaStringToUTF8(id);
-  download::DownloadItem* item = GetDownload(download_guid, is_off_the_record);
+  ProfileKey* profile_key =
+      ProfileKeyAndroid::FromProfileKeyAndroid(j_profile_key);
+  download::DownloadItem* item = GetDownload(download_guid, profile_key);
   if (!item) {
     base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
@@ -742,14 +783,16 @@
   item->Rename(base::FilePath(target_name), std::move(callback));
 }
 
-void DownloadManagerService::ChangeSchedule(JNIEnv* env,
-                                            const JavaParamRef<jobject>& obj,
-                                            const JavaParamRef<jstring>& id,
-                                            jboolean only_on_wifi,
-                                            jlong start_time,
-                                            jboolean is_off_the_record) {
+void DownloadManagerService::ChangeSchedule(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& obj,
+    const JavaParamRef<jstring>& id,
+    jboolean only_on_wifi,
+    jlong start_time,
+    const JavaParamRef<jobject>& j_profile_key) {
   std::string download_guid = ConvertJavaStringToUTF8(id);
-  download::DownloadItem* item = GetDownload(download_guid, is_off_the_record);
+  download::DownloadItem* item = GetDownload(
+      download_guid, ProfileKeyAndroid::FromProfileKeyAndroid(j_profile_key));
   if (!item)
     return;
 
@@ -774,6 +817,7 @@
   download::InProgressDownloadManager* in_progress_manager =
       DownloadManagerUtils::GetInProgressDownloadManager(
           ProfileKeyStartupAccessor::GetInstance()->profile_key());
+  UseStartupProfileKeyForTesting();
   std::vector<GURL> url_chain;
   url_chain.emplace_back(ConvertJavaStringToUTF8(env, jurl));
   base::FilePath target_path(ConvertJavaStringToUTF8(env, jtarget_path));
@@ -791,9 +835,9 @@
           base::nullopt /*download_schedule*/, nullptr));
 }
 
-void DownloadManagerService::InitializeForProfile(Profile* profile) {
+void DownloadManagerService::InitializeForProfile(ProfileKey* profile_key) {
   ResetCoordinatorIfNeeded(
-      DownloadStartupUtils::EnsureDownloadSystemInitialized(profile));
+      DownloadStartupUtils::EnsureDownloadSystemInitialized(profile_key));
 }
 
 // static
diff --git a/chrome/browser/download/android/download_manager_service.h b/chrome/browser/download/android/download_manager_service.h
index 07dc84e..2986b30 100644
--- a/chrome/browser/download/android/download_manager_service.h
+++ b/chrome/browser/download/android/download_manager_service.h
@@ -7,6 +7,7 @@
 
 #include <map>
 #include <string>
+#include <vector>
 
 #include "base/android/scoped_java_ref.h"
 #include "base/callback.h"
@@ -23,6 +24,7 @@
 
 using base::android::JavaParamRef;
 
+class Profile;
 class ProfileKey;
 
 namespace download {
@@ -57,7 +59,11 @@
 
   // Called when the profile is added to the ProfileManager and fully
   // initialized.
-  void OnProfileAdded(JNIEnv* env, jobject obj);
+  void OnProfileAdded(JNIEnv* env,
+                      jobject obj,
+                      const JavaParamRef<jobject>& j_profile);
+
+  void OnProfileAdded(Profile* profile);
 
   // Called to handle subsequent steps, after a download was determined as a OMA
   // download type.
@@ -71,7 +77,7 @@
   void OpenDownload(JNIEnv* env,
                     jobject obj,
                     const JavaParamRef<jstring>& jdownload_guid,
-                    bool is_off_the_record,
+                    const JavaParamRef<jobject>& j_profile_key,
                     jint source);
 
   // Called to resume downloading the item that has GUID equal to
@@ -79,14 +85,14 @@
   void ResumeDownload(JNIEnv* env,
                       jobject obj,
                       const JavaParamRef<jstring>& jdownload_guid,
-                      bool is_off_the_record,
+                      const JavaParamRef<jobject>& j_profile_key,
                       bool has_user_gesture);
 
   // Called to retry a download.
   void RetryDownload(JNIEnv* env,
                      jobject obj,
                      const JavaParamRef<jstring>& jdownload_guid,
-                     bool is_off_the_record,
+                     const JavaParamRef<jobject>& j_profile_key,
                      bool has_user_gesture);
 
   // Called to cancel a download item that has GUID equal to |jdownload_guid|.
@@ -94,20 +100,20 @@
   void CancelDownload(JNIEnv* env,
                       jobject obj,
                       const JavaParamRef<jstring>& jdownload_guid,
-                      bool is_off_the_record);
+                      const JavaParamRef<jobject>& j_profile_key);
 
   // Called to pause a download item that has GUID equal to |jdownload_guid|.
   // If the DownloadItem is not yet created, do nothing as it is already paused.
   void PauseDownload(JNIEnv* env,
                      jobject obj,
                      const JavaParamRef<jstring>& jdownload_guid,
-                     bool is_off_the_record);
+                     const JavaParamRef<jobject>& j_profile_key);
 
   // Called to remove a download item that has GUID equal to |jdownload_guid|.
   void RemoveDownload(JNIEnv* env,
                       jobject obj,
                       const JavaParamRef<jstring>& jdownload_guid,
-                      bool is_off_the_record);
+                      const JavaParamRef<jobject>& j_profile_key);
 
   // Called to rename a download item that has GUID equal to |id|.
   void RenameDownload(JNIEnv* env,
@@ -115,7 +121,7 @@
                       const JavaParamRef<jstring>& id,
                       const JavaParamRef<jstring>& name,
                       const JavaParamRef<jobject>& callback,
-                      bool is_off_the_record);
+                      const JavaParamRef<jobject>& j_profile_key);
 
   // Called to change the download schedule of a download item that has GUID
   // equal to |id|.
@@ -124,31 +130,32 @@
                       const JavaParamRef<jstring>& id,
                       jboolean only_on_wifi,
                       jlong start_time,
-                      jboolean is_off_the_record);
+                      const JavaParamRef<jobject>& j_profile_key);
 
   // Returns whether or not the given download can be opened by the browser.
   bool IsDownloadOpenableInBrowser(JNIEnv* env,
                                    jobject obj,
                                    const JavaParamRef<jstring>& jdownload_guid,
-                                   bool is_off_the_record);
+                                   const JavaParamRef<jobject>& j_profile_key);
 
   // Called to request that the DownloadManagerService return data about all
   // downloads in the user's history.
   void GetAllDownloads(JNIEnv* env,
                        const JavaParamRef<jobject>& obj,
-                       bool is_off_the_record);
+                       const JavaParamRef<jobject>& j_profile_key);
 
   // Called to check if the files associated with any downloads have been
   // removed by an external action.
-  void CheckForExternallyRemovedDownloads(JNIEnv* env,
-                                          const JavaParamRef<jobject>& obj,
-                                          bool is_off_the_record);
+  void CheckForExternallyRemovedDownloads(
+      JNIEnv* env,
+      const JavaParamRef<jobject>& obj,
+      const JavaParamRef<jobject>& j_profile_key);
 
   // Called to update the last access time associated with a download.
   void UpdateLastAccessTime(JNIEnv* env,
                             const JavaParamRef<jobject>& obj,
                             const JavaParamRef<jstring>& jdownload_guid,
-                            bool is_off_the_record);
+                            const JavaParamRef<jobject>& j_profile_key);
 
   // AllDownloadEventNotifier::Observer methods.
   void OnDownloadsInitialized(
@@ -184,7 +191,7 @@
 
   // Gets a download item from DownloadManager or InProgressManager.
   download::DownloadItem* GetDownload(const std::string& download_guid,
-                                      bool is_off_the_record);
+                                      ProfileKey* profile_key);
 
   // Helper method to record the interrupt reason UMA for the first background
   // download.
@@ -194,6 +201,10 @@
       const JavaParamRef<jstring>& jdownload_guid,
       jboolean download_started);
 
+  void UseStartupProfileKeyForTesting() {
+    use_startup_accessor_profile_key_for_testing_ = true;
+  }
+
  private:
   // For testing.
   friend class DownloadManagerServiceTest;
@@ -201,28 +212,28 @@
 
   // Helper function to start the download resumption.
   void ResumeDownloadInternal(const std::string& download_guid,
-                              bool is_off_the_record,
+                              ProfileKey* profile_key,
                               bool has_user_gesture);
 
   // Helper function to retry the download.
   void RetryDownloadInternal(const std::string& download_guid,
-                             bool is_off_the_record,
+                             ProfileKey* profile_key,
                              bool has_user_gesture);
 
   // Helper function to cancel a download.
   void CancelDownloadInternal(const std::string& download_guid,
-                              bool is_off_the_record);
+                              ProfileKey* profile_key);
 
   // Helper function to pause a download.
   void PauseDownloadInternal(const std::string& download_guid,
-                             bool is_off_the_record);
+                             ProfileKey* profile_key);
 
   // Helper function to remove a download.
   void RemoveDownloadInternal(const std::string& download_guid,
-                              bool is_off_the_record);
+                              ProfileKey* profile_key);
 
   // Helper function to send info about all downloads to the Java-side.
-  void GetAllDownloadsInternal(bool is_off_the_record);
+  void GetAllDownloadsInternal(ProfileKey* profile_key);
 
   // Called to notify the java side that download resumption failed.
   void OnResumptionFailed(const std::string& download_guid);
@@ -247,13 +258,13 @@
       bool is_off_the_record);
 
   // Called to get the content::DownloadManager instance.
-  content::DownloadManager* GetDownloadManager(bool is_off_the_record);
+  content::DownloadManager* GetDownloadManager(ProfileKey* profile_key);
 
   // Retrieves the SimpleDownloadManagerCoordinator this object is listening to.
   download::SimpleDownloadManagerCoordinator* GetCoordinator(
-      bool is_off_the_record);
+      ProfileKey* profile_key);
 
-  void InitializeForProfile(Profile* profile);
+  void InitializeForProfile(ProfileKey* profile_key);
 
   // Reference to the Java object.
   base::android::ScopedJavaGlobalRef<jobject> java_ref_;
@@ -261,12 +272,7 @@
   bool is_manager_initialized_;
   bool is_pending_downloads_loaded_;
 
-  enum PendingGetDownloadsFlags {
-    NONE = 0,
-    REGULAR = 1 << 0,
-    OFF_THE_RECORD = 1 << 1,
-  };
-  int pending_get_downloads_actions_;
+  std::vector<ProfileKey*> profiles_with_pending_get_downloads_actions_;
 
   enum DownloadAction { RESUME, RETRY, PAUSE, CANCEL, REMOVE, UNKNOWN };
 
@@ -292,6 +298,8 @@
 
   ScopedObserver<Profile, ProfileObserver> observed_profiles_{this};
 
+  bool use_startup_accessor_profile_key_for_testing_{false};
+
   download::SimpleDownloadManagerCoordinator* original_coordinator_;
   download::SimpleDownloadManagerCoordinator* off_the_record_coordinator_;
 
diff --git a/chrome/browser/download/android/download_manager_service_unittest.cc b/chrome/browser/download/android/download_manager_service_unittest.cc
index 3466611..1d585dfe 100644
--- a/chrome/browser/download/android/download_manager_service_unittest.cc
+++ b/chrome/browser/download/android/download_manager_service_unittest.cc
@@ -10,12 +10,14 @@
 #include "base/bind_helpers.h"
 #include "base/macros.h"
 #include "base/run_loop.h"
-#include "base/test/task_environment.h"
+#include "chrome/browser/profiles/profile_key_android.h"
+#include "chrome/test/base/testing_profile.h"
 #include "components/download/public/common/download_item.h"
 #include "components/download/public/common/download_url_parameters.h"
 #include "components/download/public/common/mock_download_item.h"
 #include "components/download/public/common/simple_download_manager_coordinator.h"
 #include "content/public/browser/download_manager.h"
+#include "content/public/test/browser_task_environment.h"
 #include "content/public/test/mock_download_manager.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -35,6 +37,7 @@
             this, &DownloadManagerServiceTest::GetDownloadByGuid));
     coordinator_.SetSimpleDownloadManager(&manager_, false);
     service_->UpdateCoordinator(&coordinator_, false);
+    service_->UseStartupProfileKeyForTesting();
   }
 
   void OnResumptionDone(bool success) {
@@ -46,12 +49,16 @@
     JNIEnv* env = base::android::AttachCurrentThread();
     service_->set_resume_callback_for_testing(base::Bind(
         &DownloadManagerServiceTest::OnResumptionDone, base::Unretained(this)));
+    ProfileKeyAndroid profile_key_android(profile_.GetProfileKey());
+
     service_->ResumeDownload(
         env, nullptr,
         JavaParamRef<jstring>(
             env,
             base::android::ConvertUTF8ToJavaString(env, download_guid).obj()),
-        false, false);
+        JavaParamRef<jobject>(env,
+                              profile_key_android.GetJavaObject().Release()),
+        false);
     EXPECT_FALSE(success_);
     service_->OnDownloadsInitialized(&coordinator_, false);
     while (!finished_)
@@ -69,11 +76,12 @@
     return download_.get();
   }
 
-  base::test::SingleThreadTaskEnvironment task_environment_;
+  content::BrowserTaskEnvironment task_environment_;
   DownloadManagerService* service_;
   download::SimpleDownloadManagerCoordinator coordinator_;
   std::unique_ptr<download::MockDownloadItem> download_;
   content::MockDownloadManager manager_;
+  TestingProfile profile_;
   bool finished_;
   bool success_;
 
diff --git a/chrome/browser/download/android/download_startup_utils.cc b/chrome/browser/download/android/download_startup_utils.cc
index 73dba1c..3141b03 100644
--- a/chrome/browser/download/android/download_startup_utils.cc
+++ b/chrome/browser/download/android/download_startup_utils.cc
@@ -28,15 +28,15 @@
       profiles.push_back(active_profile);
   }
   for (Profile* profile : profiles)
-    DownloadStartupUtils::EnsureDownloadSystemInitialized(profile);
+    DownloadStartupUtils::EnsureDownloadSystemInitialized(
+        profile->GetProfileKey());
 }
 
 // static
 ProfileKey* DownloadStartupUtils::EnsureDownloadSystemInitialized(
-    Profile* profile) {
-  ProfileKey* profile_key =
-      profile ? profile->GetProfileKey()
-              : ProfileKeyStartupAccessor::GetInstance()->profile_key();
+    ProfileKey* profile_key) {
+  if (!profile_key)
+    profile_key = ProfileKeyStartupAccessor::GetInstance()->profile_key();
   DownloadManagerUtils::GetInProgressDownloadManager(profile_key);
   return profile_key;
 }
diff --git a/chrome/browser/download/android/download_startup_utils.h b/chrome/browser/download/android/download_startup_utils.h
index e95bc94..9bfd68a2 100644
--- a/chrome/browser/download/android/download_startup_utils.h
+++ b/chrome/browser/download/android/download_startup_utils.h
@@ -7,16 +7,15 @@
 
 #include "base/macros.h"
 
-class Profile;
 class ProfileKey;
 
 // Native side of DownloadStartupUtils.java.
 class DownloadStartupUtils {
  public:
   // Ensures that the download system is initialized for the targeted profile.
-  // If |profile| is null, reduced mode will be assumed. The returned value is
-  // the ProfileKey that was used.
-  static ProfileKey* EnsureDownloadSystemInitialized(Profile* profile);
+  // If |profile_key| is null, reduced mode will be assumed. The returned value
+  // is the ProfileKey that was used.
+  static ProfileKey* EnsureDownloadSystemInitialized(ProfileKey* profile_key);
 
  private:
   DISALLOW_IMPLICIT_CONSTRUCTORS(DownloadStartupUtils);
diff --git a/chrome/browser/download/chrome_download_manager_delegate.cc b/chrome/browser/download/chrome_download_manager_delegate.cc
index c57b23bd..dc4fa71 100644
--- a/chrome/browser/download/chrome_download_manager_delegate.cc
+++ b/chrome/browser/download/chrome_download_manager_delegate.cc
@@ -1124,6 +1124,11 @@
   bool require_cellular = base::GetFieldTrialParamByFeatureAsBool(
       download::features::kDownloadLater,
       download::features::kDownloadLaterRequireCellular, true);
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          download::switches::kDownloadLaterDebugOnWifi)) {
+    require_cellular = false;
+  }
+
   bool on_cellular = network::NetworkConnectionTracker::IsConnectionCellular(
       network::mojom::ConnectionType(
           net::NetworkChangeNotifier::GetConnectionType()));
diff --git a/chrome/browser/download/download_item_model.cc b/chrome/browser/download/download_item_model.cc
index 84c3064..209970e 100644
--- a/chrome/browser/download/download_item_model.cc
+++ b/chrome/browser/download/download_item_model.cc
@@ -222,38 +222,8 @@
 }
 
 bool DownloadItemModel::MightBeMalicious() const {
-  if (!IsDangerous())
-    return false;
-  switch (download_->GetDangerType()) {
-    case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
-    case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
-    case download::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT:
-    case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST:
-    case download::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED:
-    case download::DOWNLOAD_DANGER_TYPE_ASYNC_SCANNING:
-    case download::DOWNLOAD_DANGER_TYPE_BLOCKED_PASSWORD_PROTECTED:
-    case download::DOWNLOAD_DANGER_TYPE_BLOCKED_TOO_LARGE:
-    case download::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_OPENED_DANGEROUS:
-    case download::DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_WARNING:
-    case download::DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_BLOCK:
-    case download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING:
-    case download::DOWNLOAD_DANGER_TYPE_BLOCKED_UNSUPPORTED_FILETYPE:
-      return true;
-
-    case download::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_SAFE:
-    case download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
-    case download::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
-    case download::DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
-    case download::DOWNLOAD_DANGER_TYPE_WHITELISTED_BY_POLICY:
-    case download::DOWNLOAD_DANGER_TYPE_MAX:
-      // We shouldn't get any of these due to the IsDangerous() test above.
-      NOTREACHED();
-      FALLTHROUGH;
-    case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE:
-      return false;
-  }
-  NOTREACHED();
-  return false;
+  return IsDangerous() && (download_->GetDangerType() !=
+                           download::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE);
 }
 
 // If you change this definition of malicious, also update
diff --git a/chrome/browser/download/download_shelf.cc b/chrome/browser/download/download_shelf.cc
index 3c22fd6..881710a 100644
--- a/chrome/browser/download/download_shelf.cc
+++ b/chrome/browser/download/download_shelf.cc
@@ -7,8 +7,8 @@
 #include <utility>
 
 #include "base/bind.h"
-#include "base/callback.h"
 #include "base/location.h"
+#include "base/optional.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "chrome/browser/download/download_core_service.h"
@@ -38,50 +38,6 @@
 // Delay before we show a transient download.
 const int64_t kDownloadShowDelayInSeconds = 2;
 
-void OnGetDownloadDoneForOfflineItem(
-    Profile* profile,
-    base::OnceCallback<void(DownloadUIModel::DownloadUIModelPtr)> callback,
-    const base::Optional<offline_items_collection::OfflineItem>& offline_item) {
-  if (!offline_item.has_value())
-    return;
-
-  OfflineItemModelManager* manager =
-      OfflineItemModelManagerFactory::GetForBrowserContext(profile);
-  DownloadUIModel::DownloadUIModelPtr model =
-      OfflineItemModel::Wrap(manager, offline_item.value());
-
-  std::move(callback).Run(std::move(model));
-}
-
-void GetDownload(
-    Profile* profile,
-    const offline_items_collection::ContentId& id,
-    base::OnceCallback<void(DownloadUIModel::DownloadUIModelPtr)> callback) {
-  if (OfflineItemUtils::IsDownload(id)) {
-    content::DownloadManager* download_manager =
-        content::BrowserContext::GetDownloadManager(profile);
-    if (!download_manager)
-      return;
-
-    download::DownloadItem* download =
-        download_manager->GetDownloadByGuid(id.id);
-    if (!download)
-      return;
-
-    DownloadUIModel::DownloadUIModelPtr model =
-        DownloadItemModel::Wrap(download);
-    std::move(callback).Run(std::move(model));
-  } else {
-    offline_items_collection::OfflineContentAggregator* aggregator =
-        OfflineContentAggregatorFactory::GetForKey(profile->GetProfileKey());
-    if (!aggregator)
-      return;
-
-    aggregator->GetItemById(id, base::BindOnce(&OnGetDownloadDoneForOfflineItem,
-                                               profile, std::move(callback)));
-  }
-}
-
 }  // namespace
 
 DownloadShelf::DownloadShelf(Browser* browser, Profile* profile)
@@ -184,7 +140,36 @@
 
 void DownloadShelf::ShowDownloadById(
     const offline_items_collection::ContentId& id) {
-  GetDownload(profile_, id,
-              base::BindOnce(&DownloadShelf::ShowDownload,
-                             weak_ptr_factory_.GetWeakPtr()));
+  if (OfflineItemUtils::IsDownload(id)) {
+    content::DownloadManager* download_manager =
+        content::BrowserContext::GetDownloadManager(profile_);
+    if (!download_manager)
+      return;
+
+    download::DownloadItem* download =
+        download_manager->GetDownloadByGuid(id.id);
+    if (!download)
+      return;
+
+    ShowDownload(DownloadItemModel::Wrap(download));
+  } else {
+    offline_items_collection::OfflineContentAggregator* aggregator =
+        OfflineContentAggregatorFactory::GetForKey(profile_->GetProfileKey());
+    if (!aggregator)
+      return;
+
+    aggregator->GetItemById(
+        id, base::BindOnce(&DownloadShelf::OnGetDownloadDoneForOfflineItem,
+                           weak_ptr_factory_.GetWeakPtr()));
+  }
+}
+
+void DownloadShelf::OnGetDownloadDoneForOfflineItem(
+    const base::Optional<offline_items_collection::OfflineItem>& item) {
+  if (!item.has_value())
+    return;
+
+  OfflineItemModelManager* manager =
+      OfflineItemModelManagerFactory::GetForBrowserContext(profile_);
+  ShowDownload(OfflineItemModel::Wrap(manager, item.value()));
 }
diff --git a/chrome/browser/download/download_shelf.h b/chrome/browser/download/download_shelf.h
index 103fbf4..9ae4dddc 100644
--- a/chrome/browser/download/download_shelf.h
+++ b/chrome/browser/download/download_shelf.h
@@ -14,8 +14,14 @@
 
 class Browser;
 
+namespace base {
+template <typename T>
+class Optional;
+}  // namespace base
+
 namespace offline_items_collection {
 struct ContentId;
+struct OfflineItem;
 }  // namespace offline_items_collection
 
 // This is an abstract base class for platform specific download shelf
@@ -82,6 +88,11 @@
   // Similar to ShowDownload() but refers to the download using an ID.
   void ShowDownloadById(const offline_items_collection::ContentId& id);
 
+  // Callback used by ShowDownloadById() to trigger ShowDownload() once |item|
+  // has been fetched.
+  void OnGetDownloadDoneForOfflineItem(
+      const base::Optional<offline_items_collection::OfflineItem>& item);
+
   Browser* const browser_;
   Profile* const profile_;
   bool should_show_on_unhide_;
diff --git a/chrome/browser/enterprise/connectors/common.cc b/chrome/browser/enterprise/connectors/common.cc
index c5d5daf4..58cc550 100644
--- a/chrome/browser/enterprise/connectors/common.cc
+++ b/chrome/browser/enterprise/connectors/common.cc
@@ -41,10 +41,9 @@
   }
 }
 
-ContentAnalysisResponse::Result::TriggeredRule::Action
-GetHighestPrecedenceAction(const ContentAnalysisResponse& response) {
-  auto action =
-      ContentAnalysisResponse::Result::TriggeredRule::ACTION_UNSPECIFIED;
+TriggeredRule::Action GetHighestPrecedenceAction(
+    const ContentAnalysisResponse& response) {
+  auto action = TriggeredRule::ACTION_UNSPECIFIED;
 
   for (const auto& result : response.results()) {
     if (!result.has_status() ||
@@ -58,34 +57,29 @@
   return action;
 }
 
-ContentAnalysisResponse::Result::TriggeredRule::Action
-GetHighestPrecedenceAction(
-    const ContentAnalysisResponse::Result::TriggeredRule::Action& action_1,
-    const ContentAnalysisResponse::Result::TriggeredRule::Action& action_2) {
+TriggeredRule::Action GetHighestPrecedenceAction(
+    const TriggeredRule::Action& action_1,
+    const TriggeredRule::Action& action_2) {
   // Don't use the enum's int values to determine precedence since that
   // may introduce bugs for new actions later.
   //
   // The current precedence is BLOCK > WARN > REPORT_ONLY > UNSPECIFIED
-  if (action_1 == ContentAnalysisResponse::Result::TriggeredRule::BLOCK ||
-      action_2 == ContentAnalysisResponse::Result::TriggeredRule::BLOCK) {
-    return ContentAnalysisResponse::Result::TriggeredRule::BLOCK;
+  if (action_1 == TriggeredRule::BLOCK || action_2 == TriggeredRule::BLOCK) {
+    return TriggeredRule::BLOCK;
   }
-  if (action_1 == ContentAnalysisResponse::Result::TriggeredRule::WARN ||
-      action_2 == ContentAnalysisResponse::Result::TriggeredRule::WARN) {
-    return ContentAnalysisResponse::Result::TriggeredRule::WARN;
+  if (action_1 == TriggeredRule::WARN || action_2 == TriggeredRule::WARN) {
+    return TriggeredRule::WARN;
   }
-  if (action_1 == ContentAnalysisResponse::Result::TriggeredRule::REPORT_ONLY ||
-      action_2 == ContentAnalysisResponse::Result::TriggeredRule::REPORT_ONLY) {
-    return ContentAnalysisResponse::Result::TriggeredRule::REPORT_ONLY;
+  if (action_1 == TriggeredRule::REPORT_ONLY ||
+      action_2 == TriggeredRule::REPORT_ONLY) {
+    return TriggeredRule::REPORT_ONLY;
   }
-  if (action_1 ==
-          ContentAnalysisResponse::Result::TriggeredRule::ACTION_UNSPECIFIED ||
-      action_2 ==
-          ContentAnalysisResponse::Result::TriggeredRule::ACTION_UNSPECIFIED) {
-    return ContentAnalysisResponse::Result::TriggeredRule::ACTION_UNSPECIFIED;
+  if (action_1 == TriggeredRule::ACTION_UNSPECIFIED ||
+      action_2 == TriggeredRule::ACTION_UNSPECIFIED) {
+    return TriggeredRule::ACTION_UNSPECIFIED;
   }
   NOTREACHED();
-  return ContentAnalysisResponse::Result::TriggeredRule::ACTION_UNSPECIFIED;
+  return TriggeredRule::ACTION_UNSPECIFIED;
 }
 
 }  // namespace enterprise_connectors
diff --git a/chrome/browser/enterprise/connectors/common.h b/chrome/browser/enterprise/connectors/common.h
index 5bd43c8..d4211f0 100644
--- a/chrome/browser/enterprise/connectors/common.h
+++ b/chrome/browser/enterprise/connectors/common.h
@@ -13,6 +13,9 @@
 
 namespace enterprise_connectors {
 
+// Alias to reduce verbosity when using TriggeredRule::Actions.
+using TriggeredRule = ContentAnalysisResponse::Result::TriggeredRule;
+
 // Keys used to read a connector's policy values.
 constexpr char kKeyServiceProvider[] = "service_provider";
 constexpr char kKeyEnable[] = "enable";
@@ -71,12 +74,11 @@
 const char* ConnectorPref(ReportingConnector connector);
 
 // Returns the highest precedence action in the given parameters.
-ContentAnalysisResponse::Result::TriggeredRule::Action
-GetHighestPrecedenceAction(const ContentAnalysisResponse& response);
-ContentAnalysisResponse::Result::TriggeredRule::Action
-GetHighestPrecedenceAction(
-    const ContentAnalysisResponse::Result::TriggeredRule::Action& action_1,
-    const ContentAnalysisResponse::Result::TriggeredRule::Action& action_2);
+TriggeredRule::Action GetHighestPrecedenceAction(
+    const ContentAnalysisResponse& response);
+TriggeredRule::Action GetHighestPrecedenceAction(
+    const TriggeredRule::Action& action_1,
+    const TriggeredRule::Action& action_2);
 
 }  // namespace enterprise_connectors
 
diff --git a/chrome/browser/enterprise/reporting/browser_report_generator_unittest.cc b/chrome/browser/enterprise/reporting/browser_report_generator_unittest.cc
index d1700e6..16e7d96 100644
--- a/chrome/browser/enterprise/reporting/browser_report_generator_unittest.cc
+++ b/chrome/browser/enterprise/reporting/browser_report_generator_unittest.cc
@@ -19,7 +19,6 @@
 #include "chrome/test/base/testing_profile_manager.h"
 #include "components/account_id/account_id.h"
 #include "components/enterprise/browser/reporting/browser_report_generator.h"
-#include "components/enterprise/browser/reporting/profile_report_generator.h"
 #include "content/public/browser/plugin_service.h"
 #include "content/public/common/webplugininfo.h"
 #include "content/public/test/browser_task_environment.h"
diff --git a/chrome/browser/enterprise/reporting/extension_info_unittest.cc b/chrome/browser/enterprise/reporting/extension_info_unittest.cc
index 19c3526..0877052 100644
--- a/chrome/browser/enterprise/reporting/extension_info_unittest.cc
+++ b/chrome/browser/enterprise/reporting/extension_info_unittest.cc
@@ -7,7 +7,6 @@
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_service_test_base.h"
 #include "chrome/test/base/testing_profile.h"
-#include "components/enterprise/browser/reporting/profile_report_generator.h"
 #include "extensions/common/extension_builder.h"
 #include "extensions/common/manifest_constants.h"
 #include "extensions/common/value_builder.h"
diff --git a/chrome/browser/enterprise/reporting/profile_report_generator_unittest.cc b/chrome/browser/enterprise/reporting/profile_report_generator_unittest.cc
index 1b087b8a..3b5cced 100644
--- a/chrome/browser/enterprise/reporting/profile_report_generator_unittest.cc
+++ b/chrome/browser/enterprise/reporting/profile_report_generator_unittest.cc
@@ -9,7 +9,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/util/values/values_util.h"
-#include "chrome/browser/enterprise/reporting/profile_report_generator_desktop.h"
+#include "chrome/browser/enterprise/reporting/reporting_delegate_factory_desktop.h"
 #include "chrome/browser/profiles/profile_attributes_storage.h"
 #include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
 #include "chrome/common/extensions/extension_constants.h"
@@ -54,7 +54,7 @@
 class ProfileReportGeneratorTest : public ::testing::Test {
  public:
   ProfileReportGeneratorTest()
-      : generator_(std::make_unique<ProfileReportGeneratorDesktop>()),
+      : generator_(&reporting_delegate_factory_),
         profile_manager_(TestingBrowserProcess::GetGlobal()) {}
   ~ProfileReportGeneratorTest() override = default;
 
@@ -134,6 +134,7 @@
   TestingProfile* profile() { return profile_; }
   TestingProfileManager* profile_manager() { return &profile_manager_; }
 
+  ReportingDelegateFactoryDesktop reporting_delegate_factory_;
   ProfileReportGenerator generator_;
 
  private:
diff --git a/chrome/browser/enterprise/reporting/report_generator.cc b/chrome/browser/enterprise/reporting/report_generator.cc
index b3f798a..4edd2a3 100644
--- a/chrome/browser/enterprise/reporting/report_generator.cc
+++ b/chrome/browser/enterprise/reporting/report_generator.cc
@@ -12,7 +12,6 @@
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/arc/arc_util.h"
-#include "chrome/browser/enterprise/reporting/browser_report_generator_desktop.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "components/policy/core/common/cloud/cloud_policy_util.h"
@@ -31,7 +30,8 @@
 namespace enterprise_reporting {
 
 ReportGenerator::ReportGenerator()
-    : browser_report_generator_(&delegate_factory_) {}
+    : report_request_queue_generator_(&delegate_factory_),
+      browser_report_generator_(&delegate_factory_) {}
 
 ReportGenerator::~ReportGenerator() = default;
 
diff --git a/chrome/browser/enterprise/reporting/report_request_queue_generator.cc b/chrome/browser/enterprise/reporting/report_request_queue_generator.cc
index cf89470..a90a2cc5 100644
--- a/chrome/browser/enterprise/reporting/report_request_queue_generator.cc
+++ b/chrome/browser/enterprise/reporting/report_request_queue_generator.cc
@@ -8,7 +8,6 @@
 #include "base/callback.h"
 #include "base/files/file_path.h"
 #include "base/metrics/histogram_functions.h"
-#include "chrome/browser/enterprise/reporting/profile_report_generator_desktop.h"
 
 namespace enterprise_reporting {
 namespace {
@@ -31,10 +30,10 @@
 
 }  // namespace
 
-ReportRequestQueueGenerator::ReportRequestQueueGenerator()
+ReportRequestQueueGenerator::ReportRequestQueueGenerator(
+    ReportingDelegateFactory* delegate_factory)
     : maximum_report_size_(kMaximumReportSize),
-      profile_report_generator_(
-          std::make_unique<ProfileReportGeneratorDesktop>()) {
+      profile_report_generator_(delegate_factory) {
 #if defined(OS_CHROMEOS)
   // For Chrome OS, policy information needn't be uploaded to DM server.
   profile_report_generator_.set_policies_enabled(false);
diff --git a/chrome/browser/enterprise/reporting/report_request_queue_generator.h b/chrome/browser/enterprise/reporting/report_request_queue_generator.h
index 936afb7..e9ef7b1 100644
--- a/chrome/browser/enterprise/reporting/report_request_queue_generator.h
+++ b/chrome/browser/enterprise/reporting/report_request_queue_generator.h
@@ -17,6 +17,8 @@
 
 namespace enterprise_reporting {
 
+class ReportingDelegateFactory;
+
 // Generate a report request queue that contains full profile information. The
 // request number in the queue is decided by the maximum report size setting.
 class ReportRequestQueueGenerator {
@@ -24,7 +26,8 @@
   using ReportRequests = std::queue<std::unique_ptr<ReportRequest>>;
 
  public:
-  ReportRequestQueueGenerator();
+  explicit ReportRequestQueueGenerator(
+      ReportingDelegateFactory* delegate_factory);
   ReportRequestQueueGenerator(const ReportRequestQueueGenerator&) = delete;
   ReportRequestQueueGenerator& operator=(const ReportRequestQueueGenerator&) =
       delete;
diff --git a/chrome/browser/enterprise/reporting/report_request_queue_generator_unittest.cc b/chrome/browser/enterprise/reporting/report_request_queue_generator_unittest.cc
index 41be4a0..e7b0cd73 100644
--- a/chrome/browser/enterprise/reporting/report_request_queue_generator_unittest.cc
+++ b/chrome/browser/enterprise/reporting/report_request_queue_generator_unittest.cc
@@ -11,7 +11,6 @@
 #include "base/test/bind_test_util.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "build/build_config.h"
-#include "chrome/browser/enterprise/reporting/browser_report_generator_desktop.h"
 #include "chrome/browser/enterprise/reporting/reporting_delegate_factory_desktop.h"
 #include "chrome/browser/profiles/profile_attributes_storage.h"
 #include "chrome/test/base/testing_browser_process.h"
@@ -46,7 +45,8 @@
 
   ReportRequestQueueGeneratorTest()
       : profile_manager_(TestingBrowserProcess::GetGlobal()),
-        browser_report_generator_(&reporting_delegate_factory_) {}
+        browser_report_generator_(&reporting_delegate_factory_),
+        report_request_queue_generator_(&reporting_delegate_factory_) {}
 
   ~ReportRequestQueueGeneratorTest() override = default;
 
diff --git a/chrome/browser/enterprise/reporting/reporting_delegate_factory_desktop.cc b/chrome/browser/enterprise/reporting/reporting_delegate_factory_desktop.cc
index 87fab97..5fac239 100644
--- a/chrome/browser/enterprise/reporting/reporting_delegate_factory_desktop.cc
+++ b/chrome/browser/enterprise/reporting/reporting_delegate_factory_desktop.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/enterprise/reporting/reporting_delegate_factory_desktop.h"
 
 #include "chrome/browser/enterprise/reporting/browser_report_generator_desktop.h"
+#include "chrome/browser/enterprise/reporting/profile_report_generator_desktop.h"
 
 namespace enterprise_reporting {
 
@@ -13,4 +14,9 @@
   return std::make_unique<BrowserReportGeneratorDesktop>();
 }
 
+std::unique_ptr<ProfileReportGenerator::Delegate>
+ReportingDelegateFactoryDesktop::GetProfileReportGeneratorDelegate() {
+  return std::make_unique<ProfileReportGeneratorDesktop>();
+}
+
 }  // namespace enterprise_reporting
diff --git a/chrome/browser/enterprise/reporting/reporting_delegate_factory_desktop.h b/chrome/browser/enterprise/reporting/reporting_delegate_factory_desktop.h
index f373bdf5..bf7d472 100644
--- a/chrome/browser/enterprise/reporting/reporting_delegate_factory_desktop.h
+++ b/chrome/browser/enterprise/reporting/reporting_delegate_factory_desktop.h
@@ -5,13 +5,15 @@
 #ifndef CHROME_BROWSER_ENTERPRISE_REPORTING_REPORTING_DELEGATE_FACTORY_DESKTOP_H_
 #define CHROME_BROWSER_ENTERPRISE_REPORTING_REPORTING_DELEGATE_FACTORY_DESKTOP_H_
 
-#include <memory>
-
-#include "components/enterprise/browser/reporting/browser_report_generator.h"
 #include "components/enterprise/browser/reporting/reporting_delegate_factory.h"
 
+#include <memory>
+
 namespace enterprise_reporting {
 
+class BrowserReportGenerator;
+class ProfileReportGenerator;
+
 // Desktop implementation of the reporting delegate factory. Creates desktop-
 // specific delegates for the enterprise reporting classes.
 class ReportingDelegateFactoryDesktop : public ReportingDelegateFactory {
@@ -25,6 +27,9 @@
 
   std::unique_ptr<BrowserReportGenerator::Delegate>
   GetBrowserReportGeneratorDelegate() override;
+
+  std::unique_ptr<ProfileReportGenerator::Delegate>
+  GetProfileReportGeneratorDelegate() override;
 };
 
 }  // namespace enterprise_reporting
diff --git a/chrome/browser/extensions/api/bluetooth_low_energy/bluetooth_low_energy_apitest.cc b/chrome/browser/extensions/api/bluetooth_low_energy/bluetooth_low_energy_apitest.cc
index 806848f..b216b0e 100644
--- a/chrome/browser/extensions/api/bluetooth_low_energy/bluetooth_low_energy_apitest.cc
+++ b/chrome/browser/extensions/api/bluetooth_low_energy/bluetooth_low_energy_apitest.cc
@@ -123,80 +123,67 @@
 
     event_router()->SetAdapterForTesting(mock_adapter_);
 
-    device0_.reset(
-        new testing::NiceMock<MockBluetoothDevice>(mock_adapter_,
-                                                   0,
-                                                   kTestLeDeviceName0,
-                                                   kTestLeDeviceAddress0,
-                                                   false /* paired */,
-                                                   true /* connected */));
+    device0_ = std::make_unique<testing::NiceMock<MockBluetoothDevice>>(
+        mock_adapter_, 0, kTestLeDeviceName0, kTestLeDeviceAddress0,
+        /*paired=*/false, /*connected=*/true);
 
-    device1_.reset(
-        new testing::NiceMock<MockBluetoothDevice>(mock_adapter_,
-                                                   0,
-                                                   kTestLeDeviceName1,
-                                                   kTestLeDeviceAddress1,
-                                                   false /* paired */,
-                                                   false /* connected */));
+    device1_ = std::make_unique<testing::NiceMock<MockBluetoothDevice>>(
+        mock_adapter_, 0, kTestLeDeviceName1, kTestLeDeviceAddress1,
+        /*paired=*/false, /*connected=*/true);
 
-    service0_.reset(new testing::NiceMock<MockBluetoothGattService>(
-        device0_.get(),
-        kTestServiceId0,
-        BluetoothUUID(kTestServiceUuid0),
-        true /* is_primary */,
-        false /* is_local */));
+    service0_ = std::make_unique<testing::NiceMock<MockBluetoothGattService>>(
+        device0_.get(), kTestServiceId0, BluetoothUUID(kTestServiceUuid0),
+        /*is_primary=*/true);
 
-    service1_.reset(new testing::NiceMock<MockBluetoothGattService>(
-        device0_.get(),
-        kTestServiceId1,
-        BluetoothUUID(kTestServiceUuid1),
-        false /* is_primary */,
-        false /* is_local */));
+    service1_ = std::make_unique<testing::NiceMock<MockBluetoothGattService>>(
+        device0_.get(), kTestServiceId1, BluetoothUUID(kTestServiceUuid1),
+        /*is_primary=*/false);
 
     // Assign characteristics some random properties and permissions. They don't
     // need to reflect what the characteristic is actually capable of, since
     // the JS API just passes values through from
     // device::BluetoothRemoteGattCharacteristic.
     std::vector<uint8_t> default_value;
-    chrc0_.reset(new testing::NiceMock<MockBluetoothGattCharacteristic>(
-        service0_.get(), kTestCharacteristicId0,
-        BluetoothUUID(kTestCharacteristicUuid0), false /* is_local */,
-        kTestCharacteristicProperties0,
-        BluetoothRemoteGattCharacteristic::PERMISSION_NONE));
+    chrc0_ =
+        std::make_unique<testing::NiceMock<MockBluetoothGattCharacteristic>>(
+            service0_.get(), kTestCharacteristicId0,
+            BluetoothUUID(kTestCharacteristicUuid0),
+            kTestCharacteristicProperties0,
+            BluetoothRemoteGattCharacteristic::PERMISSION_NONE);
     default_value.assign(kTestCharacteristicDefaultValue0,
                          (kTestCharacteristicDefaultValue0 +
                           sizeof(kTestCharacteristicDefaultValue0)));
     ON_CALL(*chrc0_, GetValue()).WillByDefault(ReturnRefOfCopy(default_value));
 
-    chrc1_.reset(new testing::NiceMock<MockBluetoothGattCharacteristic>(
-        service0_.get(), kTestCharacteristicId1,
-        BluetoothUUID(kTestCharacteristicUuid1), false /* is_local */,
-        kTestCharacteristicProperties1,
-        BluetoothRemoteGattCharacteristic::PERMISSION_NONE));
+    chrc1_ =
+        std::make_unique<testing::NiceMock<MockBluetoothGattCharacteristic>>(
+            service0_.get(), kTestCharacteristicId1,
+            BluetoothUUID(kTestCharacteristicUuid1),
+            kTestCharacteristicProperties1,
+            BluetoothRemoteGattCharacteristic::PERMISSION_NONE);
     default_value.assign(kTestCharacteristicDefaultValue1,
                          (kTestCharacteristicDefaultValue1 +
                           sizeof(kTestCharacteristicDefaultValue1)));
     ON_CALL(*chrc1_, GetValue()).WillByDefault(ReturnRefOfCopy(default_value));
 
-    chrc2_.reset(new testing::NiceMock<MockBluetoothGattCharacteristic>(
-        service1_.get(), kTestCharacteristicId2,
-        BluetoothUUID(kTestCharacteristicUuid2), false /* is_local */,
-        kTestCharacteristicProperties2,
-        BluetoothRemoteGattCharacteristic::PERMISSION_NONE));
+    chrc2_ =
+        std::make_unique<testing::NiceMock<MockBluetoothGattCharacteristic>>(
+            service1_.get(), kTestCharacteristicId2,
+            BluetoothUUID(kTestCharacteristicUuid2),
+            kTestCharacteristicProperties2,
+            BluetoothRemoteGattCharacteristic::PERMISSION_NONE);
 
-    desc0_.reset(new testing::NiceMock<MockBluetoothGattDescriptor>(
+    desc0_ = std::make_unique<testing::NiceMock<MockBluetoothGattDescriptor>>(
         chrc0_.get(), kTestDescriptorId0, BluetoothUUID(kTestDescriptorUuid0),
-        false /* is_local */,
-        BluetoothRemoteGattCharacteristic::PERMISSION_NONE));
+        BluetoothRemoteGattCharacteristic::PERMISSION_NONE);
     default_value.assign(
         kTestDescriptorDefaultValue0,
         (kTestDescriptorDefaultValue0 + sizeof(kTestDescriptorDefaultValue0)));
     ON_CALL(*desc0_, GetValue()).WillByDefault(ReturnRefOfCopy(default_value));
 
-    desc1_.reset(new testing::NiceMock<MockBluetoothGattDescriptor>(
+    desc1_ = std::make_unique<testing::NiceMock<MockBluetoothGattDescriptor>>(
         chrc0_.get(), kTestDescriptorId1, BluetoothUUID(kTestDescriptorUuid1),
-        false /* is_local */,
-        BluetoothRemoteGattCharacteristic::PERMISSION_NONE));
+        BluetoothRemoteGattCharacteristic::PERMISSION_NONE);
     default_value.assign(
         kTestDescriptorDefaultValue1,
         (kTestDescriptorDefaultValue1 + sizeof(kTestDescriptorDefaultValue1)));
diff --git a/chrome/browser/extensions/api/networking_cast_private/networking_cast_private_api.cc b/chrome/browser/extensions/api/networking_cast_private/networking_cast_private_api.cc
index a4601bb..f971b46 100644
--- a/chrome/browser/extensions/api/networking_cast_private/networking_cast_private_api.cc
+++ b/chrome/browser/extensions/api/networking_cast_private/networking_cast_private_api.cc
@@ -11,12 +11,6 @@
 #include "extensions/browser/api/extensions_api_client.h"
 #include "extensions/browser/api/networking_private/networking_cast_private_delegate.h"
 
-#if defined(OS_CHROMEOS)
-#include "chromeos/network/network_device_handler.h"
-#include "chromeos/network/network_handler.h"
-#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
-#endif
-
 namespace private_api = extensions::api::networking_private;
 namespace cast_api = extensions::api::networking_cast_private;
 
@@ -24,26 +18,6 @@
 
 namespace {
 
-#if defined(OS_CHROMEOS)
-// Parses TDLS status returned by network handler to networking_cast_private
-// TDLS status type.
-cast_api::TDLSStatus ParseTDLSStatus(const std::string& status) {
-  if (status == shill::kTDLSConnectedState)
-    return cast_api::TDLS_STATUS_CONNECTED;
-  if (status == shill::kTDLSNonexistentState)
-    return cast_api::TDLS_STATUS_NONEXISTENT;
-  if (status == shill::kTDLSDisabledState)
-    return cast_api::TDLS_STATUS_DISABLED;
-  if (status == shill::kTDLSDisconnectedState)
-    return cast_api::TDLS_STATUS_DISCONNECTED;
-  if (status == shill::kTDLSUnknownState)
-    return cast_api::TDLS_STATUS_UNKNOWN;
-
-  NOTREACHED() << "Unknown TDLS status " << status;
-  return cast_api::TDLS_STATUS_UNKNOWN;
-}
-#endif
-
 std::unique_ptr<NetworkingCastPrivateDelegate::Credentials> AsCastCredentials(
     api::networking_cast_private::VerificationProperties& properties) {
   return std::make_unique<NetworkingCastPrivateDelegate::Credentials>(
@@ -129,36 +103,9 @@
   std::unique_ptr<cast_api::SetWifiTDLSEnabledState::Params> params =
       cast_api::SetWifiTDLSEnabledState::Params::Create(*args_);
   EXTENSION_FUNCTION_VALIDATE(params);
-
-#if defined(OS_CHROMEOS)
-  chromeos::NetworkHandler::Get()->network_device_handler()->SetWifiTDLSEnabled(
-      params->ip_or_mac_address, params->enabled,
-      base::Bind(&NetworkingCastPrivateSetWifiTDLSEnabledStateFunction::Success,
-                 this),
-      base::Bind(&NetworkingCastPrivateSetWifiTDLSEnabledStateFunction::Failure,
-                 this));
-
-  // SetWifiTDLSEnabled might respond synchronously, e.g. in tests.
-  return did_respond() ? AlreadyResponded() : RespondLater();
-#else
   return RespondNow(Error("Not supported"));
-#endif
 }
 
-#if defined(OS_CHROMEOS)
-void NetworkingCastPrivateSetWifiTDLSEnabledStateFunction::Success(
-    const std::string& result) {
-  Respond(ArgumentList(cast_api::SetWifiTDLSEnabledState::Results::Create(
-      ParseTDLSStatus(result))));
-}
-
-void NetworkingCastPrivateSetWifiTDLSEnabledStateFunction::Failure(
-    const std::string& error,
-    std::unique_ptr<base::DictionaryValue> error_data) {
-  Respond(Error(error));
-}
-#endif
-
 NetworkingCastPrivateGetWifiTDLSStatusFunction::
     ~NetworkingCastPrivateGetWifiTDLSStatusFunction() {}
 
@@ -167,34 +114,7 @@
   std::unique_ptr<cast_api::GetWifiTDLSStatus::Params> params =
       cast_api::GetWifiTDLSStatus::Params::Create(*args_);
   EXTENSION_FUNCTION_VALIDATE(params);
-
-#if defined(OS_CHROMEOS)
-  chromeos::NetworkHandler::Get()->network_device_handler()->GetWifiTDLSStatus(
-      params->ip_or_mac_address,
-      base::Bind(&NetworkingCastPrivateGetWifiTDLSStatusFunction::Success,
-                 this),
-      base::Bind(&NetworkingCastPrivateGetWifiTDLSStatusFunction::Failure,
-                 this));
-
-  // GetWifiTDLSStatus might respond synchronously, e.g. in tests.
-  return did_respond() ? AlreadyResponded() : RespondLater();
-#else
   return RespondNow(Error("Not supported"));
-#endif
 }
 
-#if defined(OS_CHROMEOS)
-void NetworkingCastPrivateGetWifiTDLSStatusFunction::Success(
-    const std::string& result) {
-  Respond(ArgumentList(
-      cast_api::GetWifiTDLSStatus::Results::Create(ParseTDLSStatus(result))));
-}
-
-void NetworkingCastPrivateGetWifiTDLSStatusFunction::Failure(
-    const std::string& error,
-    std::unique_ptr<base::DictionaryValue> error_data) {
-  Respond(Error(error));
-}
-#endif
-
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/networking_cast_private/networking_cast_private_api.h b/chrome/browser/extensions/api/networking_cast_private/networking_cast_private_api.h
index daf23fba..94e603a 100644
--- a/chrome/browser/extensions/api/networking_cast_private/networking_cast_private_api.h
+++ b/chrome/browser/extensions/api/networking_cast_private/networking_cast_private_api.h
@@ -11,10 +11,6 @@
 #include "base/macros.h"
 #include "extensions/browser/extension_function.h"
 
-namespace base {
-class DictionaryValue;
-}
-
 namespace extensions {
 
 class NetworkingCastPrivateVerifyDestinationFunction
@@ -57,6 +53,7 @@
   DISALLOW_COPY_AND_ASSIGN(NetworkingCastPrivateVerifyAndEncryptDataFunction);
 };
 
+// Deprecated.
 class NetworkingCastPrivateSetWifiTDLSEnabledStateFunction
     : public ExtensionFunction {
  public:
@@ -70,17 +67,12 @@
   // ExtensionFunction:
   ResponseAction Run() override;
 
-#if defined(OS_CHROMEOS)
-  void Success(const std::string& result);
-  void Failure(const std::string& error,
-               std::unique_ptr<base::DictionaryValue> error_data);
-#endif
-
  private:
   DISALLOW_COPY_AND_ASSIGN(
       NetworkingCastPrivateSetWifiTDLSEnabledStateFunction);
 };
 
+// Deprecated.
 class NetworkingCastPrivateGetWifiTDLSStatusFunction
     : public ExtensionFunction {
  public:
@@ -94,12 +86,6 @@
   // ExtensionFunction:
   ResponseAction Run() override;
 
-#if defined(OS_CHROMEOS)
-  void Success(const std::string& result);
-  void Failure(const std::string& error,
-               std::unique_ptr<base::DictionaryValue> error_data);
-#endif
-
  private:
   DISALLOW_COPY_AND_ASSIGN(NetworkingCastPrivateGetWifiTDLSStatusFunction);
 };
diff --git a/chrome/browser/extensions/api/networking_cast_private/networking_cast_private_apitest.cc b/chrome/browser/extensions/api/networking_cast_private/networking_cast_private_apitest.cc
index b10347f..03cbc9b 100644
--- a/chrome/browser/extensions/api/networking_cast_private/networking_cast_private_apitest.cc
+++ b/chrome/browser/extensions/api/networking_cast_private/networking_cast_private_apitest.cc
@@ -103,7 +103,6 @@
     device_test->ClearDevices();
     device_test->AddDevice("/device/stub_wifi_device1", shill::kTypeWifi,
                            "stub_wifi_device");
-    device_test->SetTDLSState(shill::kTDLSConnectedState);
 
     chromeos::ShillServiceClient::TestInterface* service_test =
         dbus_manager->GetShillServiceClient()->GetTestInterface();
@@ -119,14 +118,6 @@
     ChromeNetworkingCastPrivateDelegate::SetFactoryCallbackForTest(nullptr);
   }
 
-  bool TdlsSupported() {
-#if defined(OS_CHROMEOS)
-    return true;
-#else
-    return false;
-#endif
-  }
-
  private:
   std::unique_ptr<ChromeNetworkingCastPrivateDelegate>
   CreateNetworkingCastPrivateDelegate() {
@@ -140,10 +131,7 @@
 };
 
 IN_PROC_BROWSER_TEST_F(NetworkingCastPrivateApiTest, Basic) {
-  const std::string arg =
-      base::StringPrintf("{\"tdlsSupported\": %d}", TdlsSupported());
-  EXPECT_TRUE(RunPlatformAppTestWithArg("networking_cast_private", arg.c_str()))
-      << message_;
+  EXPECT_TRUE(RunPlatformAppTest("networking_cast_private")) << message_;
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/networking_private/networking_private_apitest.cc b/chrome/browser/extensions/api/networking_private/networking_private_apitest.cc
index 1858e03..73919c3 100644
--- a/chrome/browser/extensions/api/networking_private/networking_private_apitest.cc
+++ b/chrome/browser/extensions/api/networking_private/networking_private_apitest.cc
@@ -38,6 +38,7 @@
 
 const char kFailure[] = "Failure";
 const char kSuccess[] = "Success";
+const char kOnline[] = "Online";
 const char kGuid[] = "SOME_GUID";
 
 class TestNetworkingPrivateDelegate : public NetworkingPrivateDelegate {
@@ -78,7 +79,7 @@
                      std::unique_ptr<base::DictionaryValue> properties,
                      const StringCallback& success_callback,
                      const FailureCallback& failure_callback) override {
-    StringResult(success_callback, failure_callback);
+    StringResult(success_callback, failure_callback, kSuccess);
   }
 
   void ForgetNetwork(const std::string& guid,
@@ -126,25 +127,11 @@
     VoidResult(success_callback, failure_callback);
   }
 
-  void SetWifiTDLSEnabledState(
-      const std::string& ip_or_mac_address,
-      bool enabled,
-      const StringCallback& success_callback,
-      const FailureCallback& failure_callback) override {
-    StringResult(success_callback, failure_callback);
-  }
-
-  void GetWifiTDLSStatus(const std::string& ip_or_mac_address,
-                         const StringCallback& success_callback,
-                         const FailureCallback& failure_callback) override {
-    StringResult(success_callback, failure_callback);
-  }
-
   void GetCaptivePortalStatus(
       const std::string& guid,
       const StringCallback& success_callback,
       const FailureCallback& failure_callback) override {
-    StringResult(success_callback, failure_callback);
+    StringResult(success_callback, failure_callback, kOnline);
   }
 
   void UnlockCellularSim(const std::string& guid,
@@ -238,11 +225,12 @@
   }
 
   void StringResult(const StringCallback& success_callback,
-                    const FailureCallback& failure_callback) {
+                    const FailureCallback& failure_callback,
+                    const std::string& result) {
     if (fail_) {
       failure_callback.Run(kFailure);
     } else {
-      success_callback.Run(kSuccess);
+      success_callback.Run(result);
     }
   }
 
@@ -479,14 +467,6 @@
   EXPECT_TRUE(RunNetworkingSubtest("verifyAndEncryptData")) << message_;
 }
 
-IN_PROC_BROWSER_TEST_F(NetworkingPrivateApiTest, SetWifiTDLSEnabledState) {
-  EXPECT_TRUE(RunNetworkingSubtest("setWifiTDLSEnabledState")) << message_;
-}
-
-IN_PROC_BROWSER_TEST_F(NetworkingPrivateApiTest, GetWifiTDLSStatus) {
-  EXPECT_TRUE(RunNetworkingSubtest("getWifiTDLSStatus")) << message_;
-}
-
 IN_PROC_BROWSER_TEST_F(NetworkingPrivateApiTest, GetCaptivePortalStatus) {
   EXPECT_TRUE(RunNetworkingSubtest("getCaptivePortalStatus")) << message_;
 }
@@ -588,14 +568,6 @@
   EXPECT_FALSE(RunNetworkingSubtest("verifyAndEncryptData")) << message_;
 }
 
-IN_PROC_BROWSER_TEST_F(NetworkingPrivateApiTestFail, SetWifiTDLSEnabledState) {
-  EXPECT_FALSE(RunNetworkingSubtest("setWifiTDLSEnabledState")) << message_;
-}
-
-IN_PROC_BROWSER_TEST_F(NetworkingPrivateApiTestFail, GetWifiTDLSStatus) {
-  EXPECT_FALSE(RunNetworkingSubtest("getWifiTDLSStatus")) << message_;
-}
-
 IN_PROC_BROWSER_TEST_F(NetworkingPrivateApiTestFail, GetCaptivePortalStatus) {
   EXPECT_FALSE(RunNetworkingSubtest("getCaptivePortalStatus")) << message_;
 }
diff --git a/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc b/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
index dca5c990..93e1ae6 100644
--- a/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
+++ b/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
@@ -819,17 +819,6 @@
 }
 
 IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest,
-                       SetWifiTDLSEnabledState) {
-  device_test_->SetTDLSState(shill::kTDLSConnectedState);
-  EXPECT_TRUE(RunNetworkingSubtest("setWifiTDLSEnabledState")) << message_;
-}
-
-IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, GetWifiTDLSStatus) {
-  device_test_->SetTDLSState(shill::kTDLSConnectedState);
-  EXPECT_TRUE(RunNetworkingSubtest("getWifiTDLSStatus")) << message_;
-}
-
-IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest,
                        GetCaptivePortalStatus) {
   SetupCellular();
 
diff --git a/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.cc b/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.cc
index 465f5cc3..2f1343ff 100644
--- a/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.cc
+++ b/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.cc
@@ -142,6 +142,7 @@
 const char SafeBrowsingPrivateEventRouter::kKeyContentType[] = "contentType";
 const char SafeBrowsingPrivateEventRouter::kKeyContentSize[] = "contentSize";
 const char SafeBrowsingPrivateEventRouter::kKeyTrigger[] = "trigger";
+const char SafeBrowsingPrivateEventRouter::kKeyEventResult[] = "result";
 
 const char SafeBrowsingPrivateEventRouter::kKeyPasswordReuseEvent[] =
     "passwordReuseEvent";
@@ -170,7 +171,10 @@
   event_router_ = EventRouter::Get(context_);
 }
 
-SafeBrowsingPrivateEventRouter::~SafeBrowsingPrivateEventRouter() {}
+SafeBrowsingPrivateEventRouter::~SafeBrowsingPrivateEventRouter() {
+  if (client_)
+    client_->RemoveObserver(this);
+}
 
 void SafeBrowsingPrivateEventRouter::OnPolicySpecifiedPasswordReuseDetected(
     const GURL& url,
@@ -555,7 +559,8 @@
     const std::string& trigger,
     safe_browsing::DeepScanAccessPoint access_point,
     const std::string& reason,
-    const int64_t content_size) {
+    const int64_t content_size,
+    safe_browsing::EventResult event_result) {
   if (!IsRealtimeReportingEnabled())
     return;
 
@@ -567,7 +572,8 @@
              const std::string& profile_user_name, const std::string& mime_type,
              const std::string& trigger,
              safe_browsing::DeepScanAccessPoint access_point,
-             const std::string& reason, const int64_t content_size) {
+             const std::string& reason, const int64_t content_size,
+             safe_browsing::EventResult event_result) {
             // Create a real-time event dictionary from the arguments and
             // report it.
             base::Value event(base::Value::Type::DICTIONARY);
@@ -583,10 +589,14 @@
             if (content_size >= 0)
               event.SetIntKey(kKeyContentSize, content_size);
             event.SetStringKey(kKeyTrigger, trigger);
+            event.SetStringKey(
+                kKeyEventResult,
+                safe_browsing::EventResultToString(event_result));
             return event;
           },
           url.spec(), file_name, download_digest_sha256, GetProfileUserName(),
-          mime_type, trigger, access_point, reason, content_size));
+          mime_type, trigger, access_point, reason, content_size,
+          event_result));
 }
 
 void SafeBrowsingPrivateEventRouter::OnDangerousDownloadWarning(
@@ -808,6 +818,8 @@
     return;
   }
 
+  client_->AddObserver(this);
+
   VLOG(1) << "Ready for safe browsing real-time event reporting.";
 }
 
@@ -867,9 +879,14 @@
   wrapper.SetStringKey("time", now_str);
   wrapper.SetKey(name, std::move(event_builder).Run());
 
-  // Show the report on chrome://safe-browsing, if appropriate.
-  safe_browsing::WebUIInfoSingleton::GetInstance()->AddToReportingEvents(
-      wrapper);
+  auto upload_callback = base::BindOnce(
+      [](base::Value wrapper, bool uploaded) {
+        // Show the report on chrome://safe-browsing, if appropriate.
+        wrapper.SetBoolKey("uploaded_successfully", uploaded);
+        safe_browsing::WebUIInfoSingleton::GetInstance()->AddToReportingEvents(
+            wrapper);
+      },
+      wrapper.Clone());
 
   base::Value event_list(base::Value::Type::LIST);
   event_list.Append(std::move(wrapper));
@@ -878,7 +895,7 @@
       policy::RealtimeReportingJobConfiguration::BuildReport(
           std::move(event_list),
           reporting::GetContext(Profile::FromBrowserContext(context_))),
-      base::DoNothing());
+      std::move(upload_callback));
 }
 
 std::string SafeBrowsingPrivateEventRouter::GetProfileUserName() const {
@@ -912,4 +929,14 @@
 #endif
 }
 
+void SafeBrowsingPrivateEventRouter::OnClientError(
+    policy::CloudPolicyClient* client) {
+  base::Value error_value(base::Value::Type::DICTIONARY);
+  error_value.SetStringKey(
+      "error", "An event got an error status and hasn't been reported");
+  error_value.SetIntKey("status", client->status());
+  safe_browsing::WebUIInfoSingleton::GetInstance()->AddToReportingEvents(
+      error_value);
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h b/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h
index da76717..35ad375f 100644
--- a/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h
+++ b/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h
@@ -13,7 +13,10 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/values.h"
+#include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h"
 #include "components/keyed_service/core/keyed_service.h"
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
+#include "components/policy/core/common/cloud/cloud_policy_core.h"
 
 namespace content {
 class BrowserContext;
@@ -30,7 +33,6 @@
 class GURL;
 
 namespace policy {
-class CloudPolicyClient;
 class DeviceManagementService;
 }
 
@@ -53,7 +55,9 @@
 // An event router that observes Safe Browsing events and notifies listeners.
 // The router also uploads events to the chrome reporting server side API if
 // the kRealtimeReportingFeature feature is enabled.
-class SafeBrowsingPrivateEventRouter : public KeyedService {
+class SafeBrowsingPrivateEventRouter
+    : public KeyedService,
+      public policy::CloudPolicyClient::Observer {
  public:
   // Feature that controls whether real-time reports are sent.
   static const base::Feature kRealtimeReportingFeature;
@@ -77,6 +81,7 @@
   static const char kKeyContentType[];
   static const char kKeyContentSize[];
   static const char kKeyTrigger[];
+  static const char kKeyEventResult[];
 
   static const char kKeyPasswordReuseEvent[];
   static const char kKeyPasswordChangedEvent[];
@@ -152,7 +157,8 @@
                             const std::string& trigger,
                             safe_browsing::DeepScanAccessPoint access_point,
                             const std::string& reason,
-                            const int64_t content_size);
+                            const int64_t content_size,
+                            safe_browsing::EventResult event_result);
 
   // Notifies listeners that the user saw a download warning.
   // - |url| is the download URL
@@ -191,6 +197,11 @@
 
   void SetIdentityManagerForTesting(signin::IdentityManager* identity_manager);
 
+  // policy::CloudPolicyClient::Observer:
+  void OnClientError(policy::CloudPolicyClient* client) override;
+  void OnPolicyFetched(policy::CloudPolicyClient* client) override {}
+  void OnRegistrationStateChanged(policy::CloudPolicyClient* client) override {}
+
  protected:
   // Callback to report safe browsing event through real-time reporting channel,
   // if the browser is authorized to do so. Declared as protected to be called
diff --git a/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_unittest.cc b/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_unittest.cc
index 851ea4f..99aa17e2 100644
--- a/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_unittest.cc
+++ b/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_unittest.cc
@@ -208,14 +208,14 @@
             safe_browsing::DeepScanAccessPoint::UPLOAD, result, 12345);
   }
 
-  void TriggerOnUnscannedFileEvent() {
+  void TriggerOnUnscannedFileEvent(safe_browsing::EventResult result) {
     SafeBrowsingPrivateEventRouterFactory::GetForProfile(profile_)
         ->OnUnscannedFileEvent(
             GURL("https://evil.com/sensitive_data.txt"), "sensitive_data.txt",
             "sha256_of_data", "text/plain",
             SafeBrowsingPrivateEventRouter::kTriggerFileDownload,
             safe_browsing::DeepScanAccessPoint::DOWNLOAD,
-            "filePasswordProtected", 12345);
+            "filePasswordProtected", 12345, result);
   }
 
   void SetReportingPolicy(bool enabled) {
@@ -741,14 +741,14 @@
                 SafeBrowsingPrivateEventRouter::kKeyTriggeredRuleName));
 }
 
-TEST_F(SafeBrowsingPrivateEventRouterTest, TestOnUnscannedFileEvent) {
+TEST_F(SafeBrowsingPrivateEventRouterTest, TestOnUnscannedFileEvent_Allowed) {
   SetUpRouters(/*realtime_reporting_enable=*/true, /*authorized=*/true);
 
   base::Value report;
   EXPECT_CALL(*client_, UploadRealtimeReport_(_, _))
       .WillOnce(CaptureArg(&report));
 
-  TriggerOnUnscannedFileEvent();
+  TriggerOnUnscannedFileEvent(safe_browsing::EventResult::ALLOWED);
   base::RunLoop().RunUntilIdle();
 
   Mock::VerifyAndClearExpectations(client_.get());
@@ -780,6 +780,53 @@
   EXPECT_EQ("filePasswordProtected",
             *event->FindStringKey(
                 SafeBrowsingPrivateEventRouter::kKeyUnscannedReason));
+  EXPECT_EQ(
+      EventResultToString(safe_browsing::EventResult::ALLOWED),
+      *event->FindStringKey(SafeBrowsingPrivateEventRouter::kKeyEventResult));
+}
+
+TEST_F(SafeBrowsingPrivateEventRouterTest, TestOnUnscannedFileEvent_Blocked) {
+  SetUpRouters(/*realtime_reporting_enable=*/true, /*authorized=*/true);
+
+  base::Value report;
+  EXPECT_CALL(*client_, UploadRealtimeReport_(_, _))
+      .WillOnce(CaptureArg(&report));
+
+  TriggerOnUnscannedFileEvent(safe_browsing::EventResult::BLOCKED);
+  base::RunLoop().RunUntilIdle();
+
+  Mock::VerifyAndClearExpectations(client_.get());
+  EXPECT_EQ(base::Value::Type::DICTIONARY, report.type());
+  base::Value* event_list =
+      report.FindKey(policy::RealtimeReportingJobConfiguration::kEventListKey);
+  ASSERT_NE(nullptr, event_list);
+  EXPECT_EQ(base::Value::Type::LIST, event_list->type());
+  base::Value::ListView mutable_list = event_list->GetList();
+  ASSERT_EQ(1, (int)mutable_list.size());
+  base::Value wrapper = std::move(mutable_list[0]);
+  EXPECT_EQ(base::Value::Type::DICTIONARY, wrapper.type());
+  base::Value* event =
+      wrapper.FindKey(SafeBrowsingPrivateEventRouter::kKeyUnscannedFileEvent);
+  ASSERT_NE(nullptr, event);
+
+  EXPECT_EQ(12345, *event->FindIntKey(
+                       SafeBrowsingPrivateEventRouter::kKeyContentSize));
+  EXPECT_EQ("text/plain", *event->FindStringKey(
+                              SafeBrowsingPrivateEventRouter::kKeyContentType));
+  EXPECT_EQ("sha256_of_data",
+            *event->FindStringKey(
+                SafeBrowsingPrivateEventRouter::kKeyDownloadDigestSha256));
+  EXPECT_EQ(
+      "sensitive_data.txt",
+      *event->FindStringKey(SafeBrowsingPrivateEventRouter::kKeyFileName));
+  EXPECT_EQ(SafeBrowsingPrivateEventRouter::kTriggerFileDownload,
+            *event->FindStringKey(SafeBrowsingPrivateEventRouter::kKeyTrigger));
+  EXPECT_EQ("filePasswordProtected",
+            *event->FindStringKey(
+                SafeBrowsingPrivateEventRouter::kKeyUnscannedReason));
+  EXPECT_EQ(
+      EventResultToString(safe_browsing::EventResult::BLOCKED),
+      *event->FindStringKey(SafeBrowsingPrivateEventRouter::kKeyEventResult));
 }
 
 TEST_F(SafeBrowsingPrivateEventRouterTest, TestProfileUsername) {
diff --git a/chrome/browser/extensions/api/terminal/terminal_private_api.cc b/chrome/browser/extensions/api/terminal/terminal_private_api.cc
index e2d7220..544a7ad 100644
--- a/chrome/browser/extensions/api/terminal/terminal_private_api.cc
+++ b/chrome/browser/extensions/api/terminal/terminal_private_api.cc
@@ -72,6 +72,7 @@
 const char kSwitchVmName[] = "vm_name";
 const char kSwitchTargetContainer[] = "target_container";
 const char kSwitchStartupId[] = "startup_id";
+const char kSwitchCurrentWorkingDir[] = "cwd";
 
 // Copies the value of |switch_name| if present from |src| to |dst|.  If not
 // present, uses |default_value|.  Returns the value set into |dst|.
@@ -233,8 +234,8 @@
     if (!crostini::CrostiniFeatures::Get()->IsAllowed(profile))
       return RespondNow(Error("vmshell not allowed"));
 
-    // command=vmshell: ensure --owner_id, --vm_name, and --target_container are
-    // set and the specified vm/container is running.
+    // command=vmshell: ensure --owner_id, --vm_name, --target_container, --cwd
+    // are set, and the specified vm/container is running.
     base::CommandLine vmshell_cmd({kVmShellCommand});
     if (!args)
       args = std::make_unique<std::vector<std::string>>();
@@ -247,6 +248,7 @@
     std::string container_name =
         GetSwitch(&params_args, &vmshell_cmd, kSwitchTargetContainer,
                   crostini::kCrostiniDefaultContainerName);
+    GetSwitch(&params_args, &vmshell_cmd, kSwitchCurrentWorkingDir, "");
     std::string startup_id = params_args.GetSwitchValueASCII(kSwitchStartupId);
     crostini::ContainerId container_id(vm_name, container_name);
 
diff --git a/chrome/browser/extensions/chrome_content_browser_client_extensions_part.cc b/chrome/browser/extensions/chrome_content_browser_client_extensions_part.cc
index 6823d87e4..1499dbb 100644
--- a/chrome/browser/extensions/chrome_content_browser_client_extensions_part.cc
+++ b/chrome/browser/extensions/chrome_content_browser_client_extensions_part.cc
@@ -312,7 +312,7 @@
 }
 
 // static
-bool ChromeContentBrowserClientExtensionsPart::ShouldLockToOrigin(
+bool ChromeContentBrowserClientExtensionsPart::ShouldLockProcess(
     content::BrowserContext* browser_context,
     const GURL& effective_site_url) {
   if (!effective_site_url.SchemeIs(kExtensionScheme))
diff --git a/chrome/browser/extensions/chrome_content_browser_client_extensions_part.h b/chrome/browser/extensions/chrome_content_browser_client_extensions_part.h
index b45cd87..c1be3e0 100644
--- a/chrome/browser/extensions/chrome_content_browser_client_extensions_part.h
+++ b/chrome/browser/extensions/chrome_content_browser_client_extensions_part.h
@@ -51,8 +51,8 @@
   static bool DoesSiteRequireDedicatedProcess(
       content::BrowserContext* browser_context,
       const GURL& effective_site_url);
-  static bool ShouldLockToOrigin(content::BrowserContext* browser_context,
-                                 const GURL& effective_site_url);
+  static bool ShouldLockProcess(content::BrowserContext* browser_context,
+                                const GURL& effective_site_url);
   static bool CanCommitURL(content::RenderProcessHost* process_host,
                            const GURL& url);
   static bool IsSuitableHost(Profile* profile,
diff --git a/chrome/browser/extensions/extension_context_menu_model.cc b/chrome/browser/extensions/extension_context_menu_model.cc
index b7a7a3e..81c069ec 100644
--- a/chrome/browser/extensions/extension_context_menu_model.cc
+++ b/chrome/browser/extensions/extension_context_menu_model.cc
@@ -555,9 +555,8 @@
       PAGE_ACCESS_RUN_ON_SITE,
       l10n_util::GetStringFUTF16(
           IDS_EXTENSIONS_CONTEXT_MENU_PAGE_ACCESS_RUN_ON_SITE,
-          url_formatter::StripWWW(base::UTF8ToUTF16(
-              url::Origin::Create(web_contents->GetLastCommittedURL())
-                  .host()))),
+          url_formatter::IDNToUnicode(url_formatter::StripWWW(
+              web_contents->GetLastCommittedURL().host()))),
       kRadioGroup);
   page_access_submenu_->AddRadioItemWithStringId(
       PAGE_ACCESS_RUN_ON_ALL_SITES,
diff --git a/chrome/browser/extensions/extension_tabs_apitest.cc b/chrome/browser/extensions/extension_tabs_apitest.cc
index 67aabc90..282dfa3 100644
--- a/chrome/browser/extensions/extension_tabs_apitest.cc
+++ b/chrome/browser/extensions/extension_tabs_apitest.cc
@@ -127,8 +127,7 @@
   ASSERT_TRUE(RunExtensionTest("tabs/connect")) << message_;
 }
 
-// Possible race in ChromeURLDataManager. http://crbug.com/59198
-IN_PROC_BROWSER_TEST_F(ExtensionApiTabTest, DISABLED_TabOnRemoved) {
+IN_PROC_BROWSER_TEST_F(ExtensionApiTabTest, TabOnRemoved) {
   ASSERT_TRUE(RunExtensionTest("tabs/on_removed")) << message_;
 }
 
diff --git a/chrome/browser/extensions/policy_handlers_unittest.cc b/chrome/browser/extensions/policy_handlers_unittest.cc
index bc3a0dd3..7c426b8c 100644
--- a/chrome/browser/extensions/policy_handlers_unittest.cc
+++ b/chrome/browser/extensions/policy_handlers_unittest.cc
@@ -63,10 +63,10 @@
   base::ListValue list;
   policy::PolicyMap policy_map;
   policy::PolicyErrorMap errors;
-  ExtensionListPolicyHandler handler(
-      policy::key::kExtensionInstallBlacklist, kTestPref, true);
+  ExtensionListPolicyHandler handler(policy::key::kExtensionInstallBlocklist,
+                                     kTestPref, true);
 
-  policy_map.Set(policy::key::kExtensionInstallBlacklist,
+  policy_map.Set(policy::key::kExtensionInstallBlocklist,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
                  policy::POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
   errors.Clear();
@@ -74,7 +74,7 @@
   EXPECT_TRUE(errors.empty());
 
   list.AppendString("abcdefghijklmnopabcdefghijklmnop");
-  policy_map.Set(policy::key::kExtensionInstallBlacklist,
+  policy_map.Set(policy::key::kExtensionInstallBlocklist,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
                  policy::POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
   errors.Clear();
@@ -82,7 +82,7 @@
   EXPECT_TRUE(errors.empty());
 
   list.AppendString("*");
-  policy_map.Set(policy::key::kExtensionInstallBlacklist,
+  policy_map.Set(policy::key::kExtensionInstallBlocklist,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
                  policy::POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
   errors.Clear();
@@ -90,14 +90,14 @@
   EXPECT_TRUE(errors.empty());
 
   list.AppendString("invalid");
-  policy_map.Set(policy::key::kExtensionInstallBlacklist,
+  policy_map.Set(policy::key::kExtensionInstallBlocklist,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
                  policy::POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
   errors.Clear();
   EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
   EXPECT_FALSE(errors.empty());
   EXPECT_FALSE(
-      errors.GetErrors(policy::key::kExtensionInstallBlacklist).empty());
+      errors.GetErrors(policy::key::kExtensionInstallBlocklist).empty());
 }
 
 TEST(ExtensionSettingsPolicyHandlerTest, CheckPolicySettingsURL) {
@@ -149,14 +149,14 @@
   base::ListValue expected;
   policy::PolicyMap policy_map;
   PrefValueMap prefs;
-  base::Value* value = NULL;
-  ExtensionListPolicyHandler handler(
-      policy::key::kExtensionInstallBlacklist, kTestPref, false);
+  base::Value* value = nullptr;
+  ExtensionListPolicyHandler handler(policy::key::kExtensionInstallBlocklist,
+                                     kTestPref, false);
 
   policy.AppendString("abcdefghijklmnopabcdefghijklmnop");
   expected.AppendString("abcdefghijklmnopabcdefghijklmnop");
 
-  policy_map.Set(policy::key::kExtensionInstallBlacklist,
+  policy_map.Set(policy::key::kExtensionInstallBlocklist,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
                  policy::POLICY_SOURCE_CLOUD, policy.Clone(), nullptr);
   handler.ApplyPolicySettings(policy_map, &prefs);
@@ -164,7 +164,7 @@
   EXPECT_EQ(expected, *value);
 
   policy.AppendString("invalid");
-  policy_map.Set(policy::key::kExtensionInstallBlacklist,
+  policy_map.Set(policy::key::kExtensionInstallBlocklist,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
                  policy::POLICY_SOURCE_CLOUD, policy.Clone(), nullptr);
   handler.ApplyPolicySettings(policy_map, &prefs);
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 936773ef..322dcff 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -144,11 +144,6 @@
     "expiry_milestone": 90
   },
   {
-    "name": "app-service-instance-registry",
-    "owners": [ "//components/services/app_service/OWNERS" ],
-    "expiry_milestone": 85
-  },
-  {
     "name": "app-service-intent-handling",
     "owners": [ "chromeos-apps-foundation-team@google.com" ],
     "expiry_milestone": 85
@@ -1036,7 +1031,7 @@
   {
     "name": "display-identification",
     "owners": [ "baileyberro", "cros-peripherals@google.com" ],
-    "expiry_milestone": 85
+    "expiry_milestone": 89
   },
   {
     "name": "dlc-settings-ui",
@@ -1074,6 +1069,11 @@
     "expiry_milestone": 88
   },
   {
+    "name": "download-later-debug-on-wifi",
+    "owners": [ "xingliu", "dtrainor" ],
+    "expiry_milestone": 88
+  },
+  {
     "name": "drag_and_drop",
     "owners": [ "//ios/chrome/browser/drag_and_drop/OWNERS" ],
     "expiry_milestone": 86
@@ -2677,6 +2677,11 @@
     "expiry_milestone": 89
   },
   {
+    "name": "force-preferred-interval-for-video",
+    "owners": [ "khushalsagar" ],
+    "expiry_milestone": 89
+  },
+  {
     "name": "force-show-update-menu-badge",
     "owners": [ "//chrome/android/java/src/org/chromium/chrome/browser/omaha/OWNERS" ],
     // This is required by test teams to verify functionality on devices which
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index d14cfca..8ff7912 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -333,9 +333,9 @@
 
 const char kBackForwardCacheName[] = "Back-forward cache";
 const char kBackForwardCacheDescription[] =
-    "Enables back-forward cache. NOTE: this feature is highly experimental and "
-    "will lead to various breakages, up to and including user data loss. "
-    "Do not enable unless you work on this feature";
+    "If enabled, caches eligible pages after cross-site navigations."
+    "To enable caching pages on same-site navigations too, choose 'enabled "
+    "same-site support'.";
 
 const char kBypassAppBannerEngagementChecksName[] =
     "Bypass user engagement checks";
@@ -428,6 +428,12 @@
     "Forces Chrome to use a specific color profile instead of the color "
     "of the window's current monitor, as specified by the operating system.";
 
+const char kForcePreferredIntervalForVideoName[] =
+    "Force preferred interval for video";
+const char kForcePreferredIntervalForVideoDescription[] =
+    "When enabled, the composition rate is reduced to match the video playback "
+    "rate irrespective of the update frequency of other page content";
+
 const char kDynamicColorGamutName[] = "Dynamic color gamut";
 const char kDynamicColorGamutDescription[] =
     "Displays in wide color when the content is wide. When the content is "
@@ -765,6 +771,11 @@
 const char kDownloadLaterName[] = "Enable download later";
 const char kDownloadLaterDescription[] = "Enables download later feature.";
 
+const char kDownloadLaterDebugOnWifiName[] =
+    "Show download later dialog on WIFI.";
+const char kDownloadLaterDebugOnWifiNameDescription[] =
+    "Show download later dialog on WIFI.";
+
 const char kEnableLayoutNGName[] = "Enable LayoutNG";
 const char kEnableLayoutNGDescription[] =
     "Enable Blink's next generation layout engine.";
@@ -3303,11 +3314,6 @@
     "Shows settings to enable/disable scroll acceleration and to adjust the "
     "sensitivity for scrolling.";
 
-const char kAppServiceInstanceRegistryName[] = "App Service Instance Registry";
-const char kAppServiceInstanceRegistryDescription[] =
-    "Use the App Service to provide app instance information, such as the "
-    "app window running status.";
-
 const char kAppServiceIntentHandlingName[] = "App Service Intent Handling";
 const char kAppServiceIntentHandlingDescription[] =
     "Use the App Service to provide data for intent handling.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 2f482668..4009af84 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -271,6 +271,9 @@
 extern const char kForceColorProfileName[];
 extern const char kForceColorProfileDescription[];
 
+extern const char kForcePreferredIntervalForVideoName[];
+extern const char kForcePreferredIntervalForVideoDescription[];
+
 extern const char kDynamicColorGamutName[];
 extern const char kDynamicColorGamutDescription[];
 
@@ -509,6 +512,9 @@
 extern const char kDownloadLaterName[];
 extern const char kDownloadLaterDescription[];
 
+extern const char kDownloadLaterDebugOnWifiName[];
+extern const char kDownloadLaterDebugOnWifiNameDescription[];
+
 extern const char kDuetTabStripIntegrationAndroidName[];
 extern const char kDuetTabStripIntegrationAndroidDescription[];
 
@@ -1909,9 +1915,6 @@
 extern const char kAppServiceAdaptiveIconName[];
 extern const char kAppServiceAdaptiveIconDescription[];
 
-extern const char kAppServiceInstanceRegistryName[];
-extern const char kAppServiceInstanceRegistryDescription[];
-
 extern const char kAppServiceIntentHandlingName[];
 extern const char kAppServiceIntentHandlingDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index a68111e..d2ba908 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -103,7 +103,6 @@
     &kAdjustWebApkInstallationSpace,
     &kAllowNewIncognitoTabIntents,
     &kAllowRemoteContextForNotifications,
-    &kAndroidBlockIntentNonSafelistedHeaders,
     &kAndroidDefaultBrowserPromo,
     &kAndroidMultipleDisplay,
     &kAndroidNightModeTabReparenting,
@@ -274,9 +273,6 @@
 const base::Feature kAdjustWebApkInstallationSpace = {
     "AdjustWebApkInstallationSpace", base::FEATURE_DISABLED_BY_DEFAULT};
 
-const base::Feature kAndroidBlockIntentNonSafelistedHeaders{
-    "AndroidBlockIntentNonSafelistedHeaders", base::FEATURE_ENABLED_BY_DEFAULT};
-
 const base::Feature kAndroidDefaultBrowserPromo{
     "AndroidDefaultBrowserPromo", base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index 787d2eb5..ddc4820 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -15,7 +15,6 @@
 extern const base::Feature kAdjustWebApkInstallationSpace;
 extern const base::Feature kAllowNewIncognitoTabIntents;
 extern const base::Feature kAllowRemoteContextForNotifications;
-extern const base::Feature kAndroidBlockIntentNonSafelistedHeaders;
 extern const base::Feature kAndroidDefaultBrowserPromo;
 extern const base::Feature kAndroidMultipleDisplay;
 extern const base::Feature kAndroidNightModeTabReparenting;
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index c2f002a..90e3768 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -203,8 +203,6 @@
     public static final String AUTOFILL_ENABLE_SURFACING_SERVER_CARD_NICKNAME =
             "AutofillEnableSurfacingServerCardNickname";
     public static final String ADJUST_WEBAPK_INSTALLATION_SPACE = "AdjustWebApkInstallationSpace";
-    public static final String ANDROID_BLOCK_INTENT_NON_SAFELISTED_HEADERS =
-            "AndroidBlockIntentNonSafelistedHeaders";
     public static final String ANDROID_DEFAULT_BROWSER_PROMO = "AndroidDefaultBrowserPromo";
     public static final String ANDROID_MULTIPLE_DISPLAY = "AndroidMultipleDisplay";
     public static final String ANDROID_NIGHT_MODE_TAB_REPARENTING =
diff --git a/chrome/browser/metrics/desktop_session_duration/touch_mode_stats_tracker.cc b/chrome/browser/metrics/desktop_session_duration/touch_mode_stats_tracker.cc
new file mode 100644
index 0000000..c076ab0
--- /dev/null
+++ b/chrome/browser/metrics/desktop_session_duration/touch_mode_stats_tracker.cc
@@ -0,0 +1,82 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/metrics/desktop_session_duration/touch_mode_stats_tracker.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/no_destructor.h"
+#include "chrome/browser/metrics/desktop_session_duration/desktop_session_duration_tracker.h"
+#include "ui/base/pointer/touch_ui_controller.h"
+
+// static
+void TouchModeStatsTracker::Initialize(
+    metrics::DesktopSessionDurationTracker* session_duration_tracker,
+    ui::TouchUiController* touch_ui_controller) {
+  static base::NoDestructor<TouchModeStatsTracker> stats_tracker(
+      session_duration_tracker, touch_ui_controller);
+}
+
+TouchModeStatsTracker::TouchModeStatsTracker(
+    metrics::DesktopSessionDurationTracker* session_duration_tracker,
+    ui::TouchUiController* touch_ui_controller)
+    : touch_ui_controller_(touch_ui_controller) {
+  session_duration_tracker->AddObserver(this);
+
+  // If this instance is destroyed, |mode_change_subscription_|'s
+  // destructor will unregister the callback. Hence Unretained is safe.
+  mode_change_subscription_ =
+      touch_ui_controller->RegisterCallback(base::BindRepeating(
+          &TouchModeStatsTracker::TouchModeChanged, base::Unretained(this)));
+}
+
+TouchModeStatsTracker::~TouchModeStatsTracker() = default;
+
+// static
+const char TouchModeStatsTracker::kSessionTouchDurationHistogramName[] =
+    "Session.TotalDuration.TouchMode";
+
+void TouchModeStatsTracker::TouchModeChanged() {
+  if (session_start_time_.is_null())
+    return;
+
+  auto switch_time = base::TimeTicks::Now();
+  DCHECK_GE(switch_time, last_touch_mode_switch_in_session_);
+
+  // If we changed to non-touch mode, we were in touch mode in the span
+  // of time from last_touch_mode_switch_in_session_ to switch_time.
+  if (!touch_ui_controller_->touch_ui()) {
+    touch_mode_duration_in_session_ +=
+        switch_time - last_touch_mode_switch_in_session_;
+  }
+
+  last_touch_mode_switch_in_session_ = switch_time;
+}
+
+void TouchModeStatsTracker::OnSessionStarted(base::TimeTicks session_start) {
+  session_start_time_ = session_start;
+  last_touch_mode_switch_in_session_ = session_start_time_;
+  touch_mode_duration_in_session_ = base::TimeDelta();
+}
+
+void TouchModeStatsTracker::OnSessionEnded(base::TimeDelta session_length,
+                                           base::TimeTicks session_end) {
+  // If we end in touch mode, we must count the time from
+  // last_touch_mode_switch_in_session_ to session_end.
+  if (touch_ui_controller_->touch_ui()) {
+    DCHECK_GE(session_end, last_touch_mode_switch_in_session_);
+    touch_mode_duration_in_session_ +=
+        session_end - last_touch_mode_switch_in_session_;
+  }
+
+  // The samples here correspond 1:1 with Session.TotalDuration, so the
+  // bucketing matches too.
+  base::UmaHistogramLongTimes(kSessionTouchDurationHistogramName,
+                              touch_mode_duration_in_session_);
+
+  session_start_time_ = base::TimeTicks();
+  last_touch_mode_switch_in_session_ = base::TimeTicks();
+  touch_mode_duration_in_session_ = base::TimeDelta();
+}
diff --git a/chrome/browser/metrics/desktop_session_duration/touch_mode_stats_tracker.h b/chrome/browser/metrics/desktop_session_duration/touch_mode_stats_tracker.h
new file mode 100644
index 0000000..0c4e122
--- /dev/null
+++ b/chrome/browser/metrics/desktop_session_duration/touch_mode_stats_tracker.h
@@ -0,0 +1,62 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_METRICS_DESKTOP_SESSION_DURATION_TOUCH_MODE_STATS_TRACKER_H_
+#define CHROME_BROWSER_METRICS_DESKTOP_SESSION_DURATION_TOUCH_MODE_STATS_TRACKER_H_
+
+#include <memory>
+
+#include "base/time/time.h"
+#include "chrome/browser/metrics/desktop_session_duration/desktop_session_duration_tracker.h"
+#include "ui/base/pointer/touch_ui_controller.h"
+
+// Records active time spent in touch mode. Emits one sample to
+// |kSessionTouchDurationHistogram| for every sample in
+// "Session.TotalDuration", which is managed by
+// |metrics::DesktopSessionDurationTracker|. Each sample is the time
+// spent in touch mode within the corresponding session.
+class TouchModeStatsTracker
+    : public metrics::DesktopSessionDurationTracker::Observer {
+ public:
+  TouchModeStatsTracker(
+      metrics::DesktopSessionDurationTracker* session_duration_tracker,
+      ui::TouchUiController* touch_ui_controller);
+
+  ~TouchModeStatsTracker() override;
+
+  // Creates the global instance. Any call after the first is a no-op.
+  static void Initialize(
+      metrics::DesktopSessionDurationTracker* session_duration_tracker,
+      ui::TouchUiController* touch_ui_controller);
+
+  static const char kSessionTouchDurationHistogramName[];
+
+ private:
+  void TouchModeChanged();
+
+  // metrics::DesktopSessionDurationTracker::Observer:
+  void OnSessionStarted(base::TimeTicks session_start) override;
+  void OnSessionEnded(base::TimeDelta session_length,
+                      base::TimeTicks session_end) override;
+
+  ui::TouchUiController* const touch_ui_controller_;
+
+  std::unique_ptr<ui::TouchUiController::Subscription>
+      mode_change_subscription_;
+
+  // The time passed by OnSessionStarted() if there is an ongoing
+  // session, or 0 otherwise.
+  base::TimeTicks session_start_time_;
+
+  // The time of the last TouchModeChanged() call, or
+  // session_start_time_ if there's been no switch.
+  base::TimeTicks last_touch_mode_switch_in_session_;
+
+  // The total time spent in touch mode since the last OnSessionStarted() call,
+  // and before the corresponding OnSessionEnded() call. This value is logged
+  // and discarded upon OnSessionEnded().
+  base::TimeDelta touch_mode_duration_in_session_;
+};
+
+#endif  // CHROME_BROWSER_METRICS_DESKTOP_SESSION_DURATION_TOUCH_MODE_STATS_TRACKER_H_
diff --git a/chrome/browser/metrics/desktop_session_duration/touch_mode_stats_tracker_unittest.cc b/chrome/browser/metrics/desktop_session_duration/touch_mode_stats_tracker_unittest.cc
new file mode 100644
index 0000000..f0f5391
--- /dev/null
+++ b/chrome/browser/metrics/desktop_session_duration/touch_mode_stats_tracker_unittest.cc
@@ -0,0 +1,187 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/metrics/desktop_session_duration/touch_mode_stats_tracker.h"
+
+#include <memory>
+
+#include "base/run_loop.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/time/time.h"
+#include "chrome/browser/metrics/desktop_session_duration/desktop_session_duration_tracker.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/test/base/test_browser_window.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile.h"
+#include "chrome/test/base/testing_profile_manager.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/pointer/touch_ui_controller.h"
+
+namespace {
+
+constexpr base::TimeDelta kInactivityTimeout = base::TimeDelta::FromMinutes(5);
+
+class SessionEndWaiter
+    : public metrics::DesktopSessionDurationTracker::Observer {
+ public:
+  explicit SessionEndWaiter(metrics::DesktopSessionDurationTracker* tracker)
+      : tracker_(tracker) {
+    tracker_->AddObserver(this);
+  }
+
+  ~SessionEndWaiter() override { tracker_->RemoveObserver(this); }
+
+  void Wait() {
+    ASSERT_FALSE(waiting_);
+    if (!tracker_->in_session())
+      return;
+
+    waiting_ = true;
+    base::RunLoop run_loop;
+    end_closure_ = run_loop.QuitClosure();
+    run_loop.Run();
+  }
+
+  // metrics::DesktopSessionDurationTracker::Observer:
+  void OnSessionEnded(base::TimeDelta session_length,
+                      base::TimeTicks session_end) override {
+    if (!waiting_)
+      return;
+    end_closure_.Run();
+  }
+
+ private:
+  metrics::DesktopSessionDurationTracker* tracker_;
+  base::RepeatingClosure end_closure_;
+  bool waiting_ = false;
+};
+
+}  // namespace
+
+class TouchModeStatsTrackerTest : public ::testing::Test {
+ public:
+  TouchModeStatsTrackerTest()
+      : profile_manager_(TestingBrowserProcess::GetGlobal()) {}
+  ~TouchModeStatsTrackerTest() override = default;
+
+  void SetUp() override {
+    ASSERT_TRUE(profile_manager_.SetUp());
+    profile_ = profile_manager_.CreateTestingProfile("p1");
+
+    metrics::DesktopSessionDurationTracker::Initialize();
+    metrics::DesktopSessionDurationTracker::Get()
+        ->SetInactivityTimeoutForTesting(kInactivityTimeout);
+    touch_mode_stats_tracker_ = std::make_unique<TouchModeStatsTracker>(
+        metrics::DesktopSessionDurationTracker::Get(),
+        ui::TouchUiController::Get());
+
+    Browser::CreateParams params(profile_, false);
+    browser_ = CreateBrowserWithTestWindowForParams(&params);
+  }
+
+  void TearDown() override {
+    browser_.reset();
+    touch_mode_stats_tracker_.reset();
+    metrics::DesktopSessionDurationTracker::CleanupForTesting();
+  }
+
+  void StartSession() {
+    BrowserList::SetLastActive(browser_.get());
+    task_environment_.RunUntilIdle();
+    metrics::DesktopSessionDurationTracker::Get()->OnUserEvent();
+  }
+
+  void EndSession() {
+    BrowserList::NotifyBrowserNoLongerActive(browser_.get());
+    SessionEndWaiter waiter(metrics::DesktopSessionDurationTracker::Get());
+    waiter.Wait();
+  }
+
+  content::BrowserTaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  Profile* profile_;
+  std::unique_ptr<TouchModeStatsTracker> touch_mode_stats_tracker_;
+
+ private:
+  TestingProfileManager profile_manager_;
+  std::unique_ptr<Browser> browser_;
+};
+
+// An entire session spent in touch mode should be logged accordingly.
+TEST_F(TouchModeStatsTrackerTest, TouchSession) {
+  ui::TouchUiController::TouchUiScoperForTesting enable_touch_mode(true);
+  base::HistogramTester histograms;
+
+  StartSession();
+  ASSERT_TRUE(metrics::DesktopSessionDurationTracker::Get()->in_session());
+  task_environment_.FastForwardBy(base::TimeDelta::FromMinutes(1));
+  EndSession();
+
+  histograms.ExpectUniqueTimeSample(
+      TouchModeStatsTracker::kSessionTouchDurationHistogramName,
+      base::TimeDelta::FromMinutes(1), 1);
+}
+
+// The touch duration logged should be 0 for a non-touch session.
+TEST_F(TouchModeStatsTrackerTest, NonTouchSession) {
+  ui::TouchUiController::TouchUiScoperForTesting disable_touch_mode(false);
+  base::HistogramTester histograms;
+
+  StartSession();
+  task_environment_.FastForwardBy(base::TimeDelta::FromMinutes(1));
+  EndSession();
+
+  histograms.ExpectUniqueTimeSample(
+      TouchModeStatsTracker::kSessionTouchDurationHistogramName,
+      base::TimeDelta(), 1);
+}
+
+// If the touch mode changes during a session, the logged duration
+// should comprise the session time spent in touch mode.
+TEST_F(TouchModeStatsTrackerTest, TouchChangesDuringSession) {
+  ui::TouchUiController::TouchUiScoperForTesting touch_mode_override(false);
+
+  // Check starting in touch mode.
+  {
+    base::HistogramTester histograms;
+    StartSession();
+
+    task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(15));
+    touch_mode_override.UpdateState(true);
+    task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(15));
+    touch_mode_override.UpdateState(false);
+    task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(15));
+    touch_mode_override.UpdateState(true);
+    task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(15));
+    touch_mode_override.UpdateState(false);
+    task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(15));
+
+    EndSession();
+    histograms.ExpectUniqueTimeSample(
+        TouchModeStatsTracker::kSessionTouchDurationHistogramName,
+        base::TimeDelta::FromSeconds(30), 1);
+  }
+
+  touch_mode_override.UpdateState(true);
+  task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(15));
+
+  // Check starting in non-touch mode.
+  {
+    base::HistogramTester histograms;
+    StartSession();
+
+    task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(15));
+    touch_mode_override.UpdateState(false);
+    task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(15));
+    touch_mode_override.UpdateState(true);
+    task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(15));
+
+    EndSession();
+    histograms.ExpectUniqueTimeSample(
+        TouchModeStatsTracker::kSessionTouchDurationHistogramName,
+        base::TimeDelta::FromSeconds(30), 1);
+  }
+}
diff --git a/chrome/browser/metrics/oom/out_of_memory_reporter.cc b/chrome/browser/metrics/oom/out_of_memory_reporter.cc
index cd3537f..d5c497c 100644
--- a/chrome/browser/metrics/oom/out_of_memory_reporter.cc
+++ b/chrome/browser/metrics/oom/out_of_memory_reporter.cc
@@ -78,6 +78,10 @@
 }
 
 void OutOfMemoryReporter::RenderProcessGone(base::TerminationStatus status) {
+  // Don't record OOM metrics (especially not UKM) for unactivated portals
+  // since the user didn't explicitly navigate to it.
+  if (web_contents()->IsPortal())
+    return;
   if (!last_committed_source_id_.has_value())
     return;
   if (web_contents()->GetVisibility() != content::Visibility::VISIBLE)
diff --git a/chrome/browser/metrics/oom/out_of_memory_reporter_browsertest.cc b/chrome/browser/metrics/oom/out_of_memory_reporter_browsertest.cc
index ba1b3066..4b2b0fe 100644
--- a/chrome/browser/metrics/oom/out_of_memory_reporter_browsertest.cc
+++ b/chrome/browser/metrics/oom/out_of_memory_reporter_browsertest.cc
@@ -9,24 +9,44 @@
 
 #include "base/macros.h"
 #include "base/optional.h"
+#include "base/test/test_timeouts.h"
 #include "build/build_config.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/navigation_controller.h"
 #include "content/public/common/url_constants.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/no_renderer_crashes_assertion.h"
+#include "content/public/test/test_navigation_observer.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "services/service_manager/embedder/switches.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/features.h"
 #include "url/gurl.h"
 
-class OutOfMemoryReporterBrowserTest : public InProcessBrowserTest,
-                                       public OutOfMemoryReporter::Observer {
+using content::JsReplace;
+using content::NavigationController;
+using content::ScopedAllowRendererCrashes;
+using content::TestNavigationObserver;
+using content::WebContents;
+using ui_test_utils::NavigateToURL;
+
+// No current reliable way to determine OOM on Linux/Mac. Sanitizers also
+// interfere with the exit code on OOM, making this detection unreliable.
+#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(ADDRESS_SANITIZER)
+#define MAYBE_OutOfMemoryReporterBrowserTest \
+  DISABLED_OutOfMemoryReporterBrowserTest
+#else
+#define MAYBE_OutOfMemoryReporterBrowserTest OutOfMemoryReporterBrowserTest
+#endif
+class MAYBE_OutOfMemoryReporterBrowserTest
+    : public InProcessBrowserTest,
+      public OutOfMemoryReporter::Observer {
  public:
-  OutOfMemoryReporterBrowserTest() {}
-  ~OutOfMemoryReporterBrowserTest() override {}
+  MAYBE_OutOfMemoryReporterBrowserTest() = default;
+  ~MAYBE_OutOfMemoryReporterBrowserTest() override = default;
 
   // InProcessBrowserTest:
   void SetUp() override {
@@ -50,30 +70,134 @@
   base::Optional<GURL> last_oom_url_;
 
  private:
-  DISALLOW_COPY_AND_ASSIGN(OutOfMemoryReporterBrowserTest);
+  DISALLOW_COPY_AND_ASSIGN(MAYBE_OutOfMemoryReporterBrowserTest);
 };
 
-// No current reliable way to determine OOM on Linux/Mac. Sanitizers also
-// interfere with the exit code on OOM, making this detection unreliable.
-#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(ADDRESS_SANITIZER)
-#define MAYBE_MemoryExhaust DISABLED_MemoryExhaust
-#else
-#define MAYBE_MemoryExhaust MemoryExhaust
-#endif
-IN_PROC_BROWSER_TEST_F(OutOfMemoryReporterBrowserTest, MAYBE_MemoryExhaust) {
-
-  content::WebContents* web_contents =
+IN_PROC_BROWSER_TEST_F(MAYBE_OutOfMemoryReporterBrowserTest, MemoryExhaust) {
+  WebContents* web_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
   OutOfMemoryReporter::FromWebContents(web_contents)->AddObserver(this);
 
   const GURL crash_url = embedded_test_server()->GetURL("/title1.html");
-  ui_test_utils::NavigateToURL(browser(), crash_url);
+  NavigateToURL(browser(), crash_url);
 
   // Careful, this doesn't actually commit the navigation. So, navigating to
   // this URL will cause an OOM associated with the previous committed URL.
-  content::ScopedAllowRendererCrashes allow_renderer_crashes(
+  ScopedAllowRendererCrashes allow_renderer_crashes(
       browser()->tab_strip_model()->GetActiveWebContents());
-  ui_test_utils::NavigateToURL(browser(),
-                               GURL(content::kChromeUIMemoryExhaustURL));
+  NavigateToURL(browser(), GURL(content::kChromeUIMemoryExhaustURL));
+  EXPECT_EQ(crash_url, last_oom_url_.value());
+}
+
+// No current reliable way to determine OOM on Linux/Mac. Sanitizers also
+// interfere with the exit code on OOM, making this detection unreliable.
+#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(ADDRESS_SANITIZER)
+#define MAYBE_PortalOutOfMemoryReporterBrowserTest \
+  DISABLED_PortalOutOfMemoryReporterBrowserTest
+#else
+#define MAYBE_PortalOutOfMemoryReporterBrowserTest \
+  PortalOutOfMemoryReporterBrowserTest
+#endif
+class MAYBE_PortalOutOfMemoryReporterBrowserTest
+    : public MAYBE_OutOfMemoryReporterBrowserTest {
+ protected:
+  void SetUp() override {
+    scoped_feature_list_.InitWithFeatures(
+        /*enabled_features=*/{blink::features::kPortals,
+                              blink::features::kPortalsCrossOrigin},
+        /*disabled_features=*/{});
+    MAYBE_OutOfMemoryReporterBrowserTest::SetUp();
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// Since a portal element is navigated to without a gesture from the user, an
+// OOM inside an un-activated portal should not cause an OOM report.
+IN_PROC_BROWSER_TEST_F(MAYBE_PortalOutOfMemoryReporterBrowserTest,
+                       NotReportedForPortal) {
+  WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  OutOfMemoryReporter::FromWebContents(web_contents)->AddObserver(this);
+
+  const GURL url(embedded_test_server()->GetURL("/portal/activate.html"));
+  const GURL memory_exhaust_url(content::kChromeUIMemoryExhaustURL);
+
+  // Navigate the main web contents to a page with a <portal> element.
+  ui_test_utils::NavigateToURL(browser(), url);
+  std::vector<WebContents*> inner_web_contents =
+      web_contents->GetInnerWebContents();
+  ASSERT_EQ(1u, inner_web_contents.size());
+
+  // Both the portal and the main frame will add this class as an observer.
+  WebContents* portal_contents = inner_web_contents[0];
+  OutOfMemoryReporter::FromWebContents(portal_contents)->AddObserver(this);
+
+  // Navigate the portal to the internal OOM crash page. Normally a portal can
+  // only be navigated via script but we have to cheat here a bit since script
+  // navigations can't access the crash page.
+  ScopedAllowRendererCrashes allow_renderer_crashes(portal_contents);
+  TestNavigationObserver nav_observer(portal_contents);
+  NavigationController::LoadURLParams params(memory_exhaust_url);
+  params.transition_type = ui::PageTransitionFromInt(
+      ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
+  portal_contents->GetController().LoadURLWithParams(params);
+  nav_observer.Wait();
+
+  // Wait a short amount of time to ensure the OOM report isn't delayed.
+  base::RunLoop run_loop;
+  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
+  run_loop.Run();
+
+  // Ensure we didn't get an OOM report from either web contents.
+  EXPECT_FALSE(last_oom_url_.has_value());
+}
+
+// Now try a crash that occurs in a portal after activation. Since this should
+// behave the same as any other top-level browsing context, we now expect to
+// record the OOM crash.
+IN_PROC_BROWSER_TEST_F(MAYBE_PortalOutOfMemoryReporterBrowserTest,
+                       ReportForActivatedPortal) {
+  WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  OutOfMemoryReporter::FromWebContents(web_contents)->AddObserver(this);
+
+  const GURL main_url(embedded_test_server()->GetURL("/portal/activate.html"));
+  const GURL crash_url(
+      embedded_test_server()->GetURL("/portal/activate-portal.html"));
+  const GURL memory_exhaust_url(content::kChromeUIMemoryExhaustURL);
+
+  // Navigate the main web contents to a page with a <portal> element.
+  ui_test_utils::NavigateToURL(browser(), main_url);
+  std::vector<WebContents*> inner_web_contents =
+      web_contents->GetInnerWebContents();
+  ASSERT_EQ(1u, inner_web_contents.size());
+
+  // Both the portal and the main frame will add this class as an observer.
+  WebContents* portal_contents = inner_web_contents[0];
+  OutOfMemoryReporter::FromWebContents(portal_contents)->AddObserver(this);
+
+  // Activate the portal - this is a user-gesture gated signal and means the
+  // user intended to visit the portaled content so we should report an OOM
+  // now.
+  ASSERT_TRUE(ExecJs(web_contents, "activate();"));
+
+  // Navigate the now-activated portal to the internal OOM crash page.
+  ScopedAllowRendererCrashes allow_renderer_crashes(portal_contents);
+  TestNavigationObserver nav_observer(portal_contents);
+  NavigationController::LoadURLParams params(memory_exhaust_url);
+  params.transition_type = ui::PageTransitionFromInt(
+      ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
+  portal_contents->GetController().LoadURLWithParams(params);
+  nav_observer.Wait();
+
+  // Wait a short amount of time to ensure the OOM report isn't delayed.
+  base::RunLoop run_loop;
+  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
+  run_loop.Run();
+
   EXPECT_EQ(crash_url, last_oom_url_.value());
 }
diff --git a/chrome/browser/nearby_sharing/logging/BUILD.gn b/chrome/browser/nearby_sharing/logging/BUILD.gn
index f831277..997e5df3 100644
--- a/chrome/browser/nearby_sharing/logging/BUILD.gn
+++ b/chrome/browser/nearby_sharing/logging/BUILD.gn
@@ -22,3 +22,15 @@
     "//testing/gtest",
   ]
 }
+
+source_set("util") {
+  sources = [
+    "proto_to_dictionary_conversion.cc",
+    "proto_to_dictionary_conversion.h",
+  ]
+
+  deps = [
+    "//base",
+    "//chrome/browser/nearby_sharing/proto",
+  ]
+}
diff --git a/chrome/browser/nearby_sharing/logging/proto_to_dictionary_conversion.cc b/chrome/browser/nearby_sharing/logging/proto_to_dictionary_conversion.cc
new file mode 100644
index 0000000..38a578f
--- /dev/null
+++ b/chrome/browser/nearby_sharing/logging/proto_to_dictionary_conversion.cc
@@ -0,0 +1,257 @@
+// Copyright 2020 The Chromium Authors.All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/nearby_sharing/logging/proto_to_dictionary_conversion.h"
+
+#include <string>
+#include <utility>
+
+#include "base/base64url.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/time/time.h"
+
+namespace {
+std::string Encode(const std::string& str) {
+  std::string encoded_string;
+  base::Base64UrlEncode(str, base::Base64UrlEncodePolicy::INCLUDE_PADDING,
+                        &encoded_string);
+  return encoded_string;
+}
+
+std::string TruncateString(const std::string& str) {
+  if (str.length() <= 10)
+    return str;
+  return str.substr(0, 5) + "..." + str.substr(str.length() - 5, str.length());
+}
+}  // namespace
+
+base::Value ListPublicCertificatesRequestToReadableDictionary(
+    const nearbyshare::proto::ListPublicCertificatesRequest& request) {
+  base::Value dict(base::Value::Type::DICTIONARY);
+  dict.SetStringKey("parent", request.parent());
+  dict.SetIntKey("page_size", request.page_size());
+  dict.SetStringKey("page_token", request.page_token());
+
+  base::Value secret_ids_list(base::Value::Type::LIST);
+  for (const auto& secret_id : request.secret_ids()) {
+    secret_ids_list.Append(TruncateString(Encode(secret_id)));
+  }
+  dict.SetKey("secret_ids", std::move(secret_ids_list));
+  return dict;
+}
+
+base::Value ListPublicCertificatesResponseToReadableDictionary(
+    const nearbyshare::proto::ListPublicCertificatesResponse& response) {
+  base::Value dict(base::Value::Type::DICTIONARY);
+  dict.SetStringKey("next_page_token", response.next_page_token());
+
+  base::Value public_certificates_list(base::Value::Type::LIST);
+  for (const auto& public_certificate : response.public_certificates()) {
+    public_certificates_list.Append(
+        PublicCertificateToReadableDictionary(public_certificate));
+  }
+  dict.SetKey("public_certificates", std::move(public_certificates_list));
+  return dict;
+}
+
+base::Value PublicCertificateToReadableDictionary(
+    const nearbyshare::proto::PublicCertificate& certificate) {
+  base::Value dict(base::Value::Type::DICTIONARY);
+  dict.SetStringKey("secret_id",
+                    TruncateString(Encode(certificate.secret_id())));
+  dict.SetStringKey("secret_key",
+                    TruncateString(Encode(certificate.secret_key())));
+  dict.SetStringKey("public_key",
+                    TruncateString(Encode(certificate.public_key())));
+  dict.SetKey("start_time",
+              TimestampToReadableDictionary(certificate.start_time()));
+  dict.SetKey("end_time",
+              TimestampToReadableDictionary(certificate.end_time()));
+  dict.SetBoolKey("for_selected_contacts", certificate.for_selected_contacts());
+  dict.SetStringKey(
+      "metadata_encryption_key",
+      TruncateString(Encode(certificate.metadata_encryption_key())));
+  dict.SetStringKey(
+      "encrypted_metadata_bytes",
+      TruncateString(Encode(certificate.encrypted_metadata_bytes())));
+  dict.SetStringKey(
+      "metadata_encryption_key_tag",
+      TruncateString(Encode(certificate.metadata_encryption_key_tag())));
+  return dict;
+}
+
+base::Value TimestampToReadableDictionary(
+    const nearbyshare::proto::Timestamp& timestamp) {
+  base::Value dict(base::Value::Type::DICTIONARY);
+  dict.SetStringKey("seconds", base::NumberToString(timestamp.seconds()));
+  dict.SetStringKey("nanos", base::NumberToString(timestamp.nanos()));
+  return dict;
+}
+
+base::Value CheckContactReachabilityRequestToReadableDictionary(
+    const nearbyshare::proto::CheckContactsReachabilityRequest& request) {
+  base::Value dict(base::Value::Type::DICTIONARY);
+  base::Value contacts_list(base::Value::Type::LIST);
+  for (const auto& contact : request.contacts()) {
+    contacts_list.Append(ReachableContactToReadableDictionary(contact));
+  }
+  dict.SetKey("contacts", std::move(contacts_list));
+  return dict;
+}
+
+base::Value ReachableContactToReadableDictionary(
+    const nearbyshare::proto::CheckContactsReachabilityRequest::
+        ReachableContact& contact) {
+  base::Value dict(base::Value::Type::DICTIONARY);
+  dict.SetStringKey("contact_id", contact.contact_id());
+  base::Value phone_numbers_list(base::Value::Type::LIST);
+  for (const auto& phone_number : contact.phone_numbers()) {
+    phone_numbers_list.Append(phone_number);
+  }
+  dict.SetKey("phone_numbers", std::move(phone_numbers_list));
+  base::Value emails_list(base::Value::Type::LIST);
+  for (const auto& email : contact.emails()) {
+    emails_list.Append(email);
+  }
+  dict.SetKey("emails", std::move(emails_list));
+  return dict;
+}
+
+base::Value CheckContactReachabilityResponseToReadableDictionary(
+    const nearbyshare::proto::CheckContactsReachabilityResponse& response) {
+  base::Value dict(base::Value::Type::DICTIONARY);
+  base::Value results_list(base::Value::Type::LIST);
+  for (const auto& result : response.results()) {
+    results_list.Append(ResultToReadableDictionary(result));
+  }
+  dict.SetKey("results", std::move(results_list));
+  return dict;
+}
+
+base::Value ResultToReadableDictionary(
+    const nearbyshare::proto::CheckContactsReachabilityResponse::Result&
+        result) {
+  base::Value dict(base::Value::Type::DICTIONARY);
+  dict.SetStringKey("contact_id", result.contact_id());
+  dict.SetBoolKey("is_reachable", result.is_reachable());
+  dict.SetBoolKey("is_recommended", result.is_recommended());
+  return dict;
+}
+
+base::Value ListContactPeopleRequestToReadableDictionary(
+    const nearbyshare::proto::ListContactPeopleRequest& request) {
+  base::Value dict(base::Value::Type::DICTIONARY);
+  dict.SetStringKey("parent", request.parent());
+  dict.SetIntKey("page_size", request.page_size());
+  dict.SetStringKey("page_token", request.page_token());
+  return dict;
+}
+
+base::Value ListContactPeopleResponseToReadableDictionary(
+    const nearbyshare::proto::ListContactPeopleResponse& response) {
+  base::Value dict(base::Value::Type::DICTIONARY);
+  base::Value contact_records_list(base::Value::Type::LIST);
+  for (const auto& contact_record : response.contact_records()) {
+    contact_records_list.Append(
+        ContactRecordToReadableDictionary(contact_record));
+  }
+  dict.SetKey("contact_records", std::move(contact_records_list));
+  dict.SetStringKey("next_page_token", response.next_page_token());
+  return dict;
+}
+
+base::Value ContactRecordToReadableDictionary(
+    const nearbyshare::proto::ContactRecord& contact_record) {
+  base::Value dict(base::Value::Type::DICTIONARY);
+  dict.SetStringKey("id", contact_record.id());
+  dict.SetStringKey("person_name", contact_record.person_name());
+  dict.SetStringKey("image_url", contact_record.image_url());
+  base::Value identifiers_list(base::Value::Type::LIST);
+  for (const auto& identifier : contact_record.identifiers()) {
+    identifiers_list.Append(IdentifierToReadableDictionary(identifier));
+  }
+  dict.SetKey("identifiers", std::move(identifiers_list));
+  return dict;
+}
+
+base::Value IdentifierToReadableDictionary(
+    const nearbyshare::proto::Contact::Identifier& identifier) {
+  base::Value dict(base::Value::Type::DICTIONARY);
+  if (!identifier.obfuscated_gaia().empty()) {
+    dict.SetStringKey("identifier", identifier.obfuscated_gaia());
+  } else if (!identifier.phone_number().empty()) {
+    dict.SetStringKey("identifier", identifier.phone_number());
+  } else if (!identifier.account_name().empty()) {
+    dict.SetStringKey("identifier", identifier.account_name());
+  }
+  return dict;
+}
+
+base::Value UpdateDeviceRequestToReadableDictionary(
+    const nearbyshare::proto::UpdateDeviceRequest& request) {
+  base::Value dict(base::Value::Type::DICTIONARY);
+  dict.SetKey("device", DeviceToReadableDictionary(request.device()));
+  dict.SetKey("update_mask",
+              FieldMaskToReadableDictionary(request.update_mask()));
+  return dict;
+}
+
+base::Value DeviceToReadableDictionary(
+    const nearbyshare::proto::Device& device) {
+  base::Value dict(base::Value::Type::DICTIONARY);
+  dict.SetStringKey("name", device.name());
+  dict.SetStringKey("display_name", device.display_name());
+  base::Value contacts_list(base::Value::Type::LIST);
+  for (const auto& contact : device.contacts()) {
+    contacts_list.Append(ContactToReadableDictionary(contact));
+  }
+  dict.SetKey("contacts", std::move(contacts_list));
+  base::Value public_certificates_list(base::Value::Type::LIST);
+  for (const auto& certificate : device.public_certificates()) {
+    contacts_list.Append(PublicCertificateToReadableDictionary(certificate));
+  }
+  dict.SetKey("public_certificates", std::move(public_certificates_list));
+  return dict;
+}
+
+base::Value ContactToReadableDictionary(
+    const nearbyshare::proto::Contact& contact) {
+  base::Value dict(base::Value::Type::DICTIONARY);
+  dict.SetKey("identifier",
+              IdentifierToReadableDictionary(contact.identifier()));
+  dict.SetBoolKey("is_selected", contact.is_selected());
+  return dict;
+}
+
+base::Value FieldMaskToReadableDictionary(
+    const nearbyshare::proto::FieldMask& mask) {
+  base::Value dict(base::Value::Type::DICTIONARY);
+  base::Value paths_list(base::Value::Type::LIST);
+  for (const auto& path : mask.paths()) {
+    paths_list.Append(path);
+  }
+  dict.SetKey("paths", std::move(paths_list));
+  return dict;
+}
+
+base::Value UpdateDeviceResponseToReadableDictionary(
+    const nearbyshare::proto::UpdateDeviceResponse& response) {
+  base::Value dict(base::Value::Type::DICTIONARY);
+  dict.SetKey("device", DeviceToReadableDictionary(response.device()));
+  dict.SetStringKey("person_name", response.person_name());
+  dict.SetStringKey("image_url", response.image_url());
+  return dict;
+}
+
+base::Value EncryptedMetadataToReadableDictionary(
+    const nearbyshare::proto::EncryptedMetadata& data) {
+  base::Value dict(base::Value::Type::DICTIONARY);
+  dict.SetStringKey("device_name", data.device_name());
+  dict.SetStringKey("full_name", data.full_name());
+  dict.SetStringKey("icon_url", data.icon_url());
+  dict.SetStringKey("bluetooth_mac_address",
+                    TruncateString(Encode(data.bluetooth_mac_address())));
+  dict.SetStringKey("obfuscated_gaia_id", data.obfuscated_gaia_id());
+  return dict;
+}
diff --git a/chrome/browser/nearby_sharing/logging/proto_to_dictionary_conversion.h b/chrome/browser/nearby_sharing/logging/proto_to_dictionary_conversion.h
new file mode 100644
index 0000000..2addebe
--- /dev/null
+++ b/chrome/browser/nearby_sharing/logging/proto_to_dictionary_conversion.h
@@ -0,0 +1,57 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NEARBY_SHARING_LOGGING_PROTO_TO_DICTIONARY_CONVERSION_H_
+#define CHROME_BROWSER_NEARBY_SHARING_LOGGING_PROTO_TO_DICTIONARY_CONVERSION_H_
+
+#include "base/values.h"
+#include "chrome/browser/nearby_sharing/proto/certificate_rpc.pb.h"
+#include "chrome/browser/nearby_sharing/proto/contact_rpc.pb.h"
+#include "chrome/browser/nearby_sharing/proto/device_rpc.pb.h"
+#include "chrome/browser/nearby_sharing/proto/encrypted_metadata.pb.h"
+#include "chrome/browser/nearby_sharing/proto/field_mask.pb.h"
+#include "chrome/browser/nearby_sharing/proto/rpc_resources.pb.h"
+#include "chrome/browser/nearby_sharing/proto/timestamp.pb.h"
+
+// Converts Nearby Share protos to readable, JSON-style dictionaries.
+base::Value ListPublicCertificatesRequestToReadableDictionary(
+    const nearbyshare::proto::ListPublicCertificatesRequest& request);
+base::Value ListPublicCertificatesResponseToReadableDictionary(
+    const nearbyshare::proto::ListPublicCertificatesResponse& response);
+base::Value PublicCertificateToReadableDictionary(
+    const nearbyshare::proto::PublicCertificate& certificate);
+base::Value TimestampToReadableDictionary(
+    const nearbyshare::proto::Timestamp& timestamp);
+base::Value CheckContactReachabilityRequestToReadableDictionary(
+    const nearbyshare::proto::CheckContactsReachabilityRequest& request);
+base::Value ReachableContactToReadableDictionary(
+    const nearbyshare::proto::CheckContactsReachabilityRequest::
+        ReachableContact& contact);
+base::Value CheckContactReachabilityResponseToReadableDictionary(
+    const nearbyshare::proto::CheckContactsReachabilityResponse& response);
+base::Value ResultToReadableDictionary(
+    const nearbyshare::proto::CheckContactsReachabilityResponse::Result&
+        result);
+base::Value ListContactPeopleRequestToReadableDictionary(
+    const nearbyshare::proto::ListContactPeopleRequest& request);
+base::Value ListContactPeopleResponseToReadableDictionary(
+    const nearbyshare::proto::ListContactPeopleResponse& response);
+base::Value ContactRecordToReadableDictionary(
+    const nearbyshare::proto::ContactRecord& contact_record);
+base::Value IdentifierToReadableDictionary(
+    const nearbyshare::proto::Contact::Identifier& identifier);
+base::Value UpdateDeviceRequestToReadableDictionary(
+    const nearbyshare::proto::UpdateDeviceRequest& request);
+base::Value DeviceToReadableDictionary(
+    const nearbyshare::proto::Device& device);
+base::Value ContactToReadableDictionary(
+    const nearbyshare::proto::Contact& contact);
+base::Value FieldMaskToReadableDictionary(
+    const nearbyshare::proto::FieldMask& mask);
+base::Value UpdateDeviceResponseToReadableDictionary(
+    const nearbyshare::proto::UpdateDeviceResponse& response);
+base::Value EncryptedMetadataToReadableDictionary(
+    const nearbyshare::proto::EncryptedMetadata& data);
+
+#endif  // CHROME_BROWSER_NEARBY_SHARING_LOGGING_PROTO_TO_DICTIONARY_CONVERSION_H_
diff --git a/chrome/browser/nearby_sharing/nearby_notification_manager.cc b/chrome/browser/nearby_sharing/nearby_notification_manager.cc
index d417161f5..50166490 100644
--- a/chrome/browser/nearby_sharing/nearby_notification_manager.cc
+++ b/chrome/browser/nearby_sharing/nearby_notification_manager.cc
@@ -18,8 +18,26 @@
 namespace {
 
 constexpr char kNearbyNotificationId[] = "chrome://nearby";
+constexpr char kNearbyOnboardingNotificationId[] = "chrome://nearby/onboarding";
 constexpr char kNearbyNotifier[] = "nearby";
 
+// Creates a default Nearby Share notification with empty content.
+message_center::Notification CreateNearbyNotification(const std::string& id) {
+  message_center::Notification notification(
+      message_center::NOTIFICATION_TYPE_SIMPLE, id,
+      /*title=*/base::string16(),
+      /*message=*/base::string16(),
+      /*icon=*/gfx::Image(),
+      l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_SOURCE),
+      /*origin_url=*/GURL(),
+      message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
+                                 kNearbyNotifier),
+      /*optional_fields=*/{},
+      /*delegate=*/nullptr);
+  notification.set_vector_small_image(kNearbyShareIcon);
+  return notification;
+}
+
 FileAttachment::Type GetCommonFileAttachmentType(
     const std::vector<FileAttachment>& files) {
   if (files.empty())
@@ -90,7 +108,7 @@
   return l10n_util::GetPluralStringFUTF16(resource_id, text_count + file_count);
 }
 
-base::string16 GetNotificationTitle(const ShareTarget& share_target) {
+base::string16 GetProgressNotificationTitle(const ShareTarget& share_target) {
   int resource_id = share_target.is_incoming()
                         ? IDS_NEARBY_NOTIFICATION_RECEIVE_PROGRESS_TITLE
                         : IDS_NEARBY_NOTIFICATION_SEND_PROGRESS_TITLE;
@@ -100,6 +118,21 @@
   return l10n_util::GetStringFUTF16(resource_id, attachments, device_name);
 }
 
+base::string16 GetConnectionRequestNotificationMessage(
+    const ShareTarget& share_target) {
+  base::string16 attachments = GetAttachmentsString(share_target);
+  base::string16 device_name = base::ASCIIToUTF16(share_target.device_name());
+
+  return l10n_util::GetStringFUTF16(
+      IDS_NEARBY_NOTIFICATION_CONNECTION_REQUEST_MESSAGE, device_name,
+      attachments);
+}
+
+gfx::Image GetImageFromShareTarget(const ShareTarget& share_target) {
+  // TODO(crbug.com/1102348): Create or get profile picture of |share_target|.
+  return gfx::Image();
+}
+
 }  // namespace
 
 NearbyNotificationManager::NearbyNotificationManager(Profile* profile)
@@ -112,22 +145,11 @@
     const TransferMetadata& transfer_metadata) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  message_center::RichNotificationData rich_notification_data;
-  rich_notification_data.never_timeout = true;
-
-  message_center::Notification notification(
-      message_center::NOTIFICATION_TYPE_PROGRESS, kNearbyNotificationId,
-      GetNotificationTitle(share_target),
-      /*message=*/base::string16(),
-      /*icon=*/gfx::Image(),
-      l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_SOURCE),
-      /*origin_url=*/GURL(),
-      message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
-                                 kNearbyNotifier),
-      rich_notification_data,
-      /*delegate=*/nullptr);
-
-  notification.set_vector_small_image(kNearbyShareIcon);
+  message_center::Notification notification =
+      CreateNearbyNotification(kNearbyNotificationId);
+  notification.set_type(message_center::NOTIFICATION_TYPE_PROGRESS);
+  notification.set_title(GetProgressNotificationTitle(share_target));
+  notification.set_never_timeout(true);
   notification.set_progress(100.0 * transfer_metadata.progress());
 
   std::vector<message_center::ButtonInfo> notification_actions;
@@ -138,3 +160,43 @@
       NotificationHandler::Type::NEARBY_SHARE, notification,
       /*metadata=*/nullptr);
 }
+
+void NearbyNotificationManager::ShowConnectionRequest(
+    const ShareTarget& share_target) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  message_center::Notification notification =
+      CreateNearbyNotification(kNearbyNotificationId);
+  notification.set_title(l10n_util::GetStringUTF16(
+      IDS_NEARBY_NOTIFICATION_CONNECTION_REQUEST_TITLE));
+  notification.set_message(
+      GetConnectionRequestNotificationMessage(share_target));
+  notification.set_icon(GetImageFromShareTarget(share_target));
+  notification.set_never_timeout(true);
+
+  std::vector<message_center::ButtonInfo> notification_actions;
+  notification_actions.emplace_back(
+      l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_RECEIVE_ACTION));
+  notification_actions.emplace_back(
+      l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_DECLINE_ACTION));
+  notification.set_buttons(notification_actions);
+
+  NotificationDisplayServiceFactory::GetForProfile(profile_)->Display(
+      NotificationHandler::Type::NEARBY_SHARE, notification,
+      /*metadata=*/nullptr);
+}
+
+void NearbyNotificationManager::ShowOnboarding() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  message_center::Notification notification =
+      CreateNearbyNotification(kNearbyOnboardingNotificationId);
+  notification.set_title(
+      l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_ONBOARDING_TITLE));
+  notification.set_message(
+      l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_ONBOARDING_MESSAGE));
+
+  NotificationDisplayServiceFactory::GetForProfile(profile_)->Display(
+      NotificationHandler::Type::NEARBY_SHARE, notification,
+      /*metadata=*/nullptr);
+}
diff --git a/chrome/browser/nearby_sharing/nearby_notification_manager.h b/chrome/browser/nearby_sharing/nearby_notification_manager.h
index c2827f6..1208a76a 100644
--- a/chrome/browser/nearby_sharing/nearby_notification_manager.h
+++ b/chrome/browser/nearby_sharing/nearby_notification_manager.h
@@ -21,9 +21,20 @@
   explicit NearbyNotificationManager(Profile* profile);
   ~NearbyNotificationManager();
 
+  // Shows a progress notification of the data being transferred to or from
+  // |share_target|. Has a cancel action to cancel the transfer.
   void ShowProgress(const ShareTarget& share_target,
                     const TransferMetadata& transfer_metadata);
 
+  // Shows an incoming connection request notification from |share_target|
+  // wanting to send data to this device. Has accept & decline actions.
+  void ShowConnectionRequest(const ShareTarget& share_target);
+
+  // Shows an onboarding notification when a nearby device is attempting to
+  // share. Clicking it will make the local device visible to all nearby
+  // devices.
+  void ShowOnboarding();
+
  private:
   Profile* profile_;
 };
diff --git a/chrome/browser/nearby_sharing/nearby_notification_manager_unittest.cc b/chrome/browser/nearby_sharing/nearby_notification_manager_unittest.cc
index db9d9b9..3d977f10 100644
--- a/chrome/browser/nearby_sharing/nearby_notification_manager_unittest.cc
+++ b/chrome/browser/nearby_sharing/nearby_notification_manager_unittest.cc
@@ -278,3 +278,74 @@
     NearbyNotificationManagerProgressNotificationTest,
     testing::Combine(testing::ValuesIn(kProgressNotificationTestParams),
                      testing::Bool()));
+
+TEST_F(NearbyNotificationManagerTest, ShowConnectionRequest_ShowsNotification) {
+  std::string device_name = "device";
+  ShareTargetBuilder share_target_builder;
+  share_target_builder.set_device_name(device_name);
+  share_target_builder.add_attachment(
+      CreateFileAttachment(FileAttachment::Type::kImage));
+  ShareTarget share_target = share_target_builder.build();
+
+  manager()->ShowConnectionRequest(share_target);
+
+  std::vector<message_center::Notification> notifications =
+      GetDisplayedNotifications();
+  ASSERT_EQ(1u, notifications.size());
+
+  const message_center::Notification& notification = notifications[0];
+
+  base::string16 expected_title = l10n_util::GetStringUTF16(
+      IDS_NEARBY_NOTIFICATION_CONNECTION_REQUEST_TITLE);
+
+  base::string16 expected_message = l10n_util::GetStringFUTF16(
+      IDS_NEARBY_NOTIFICATION_CONNECTION_REQUEST_MESSAGE,
+      base::ASCIIToUTF16(device_name),
+      l10n_util::GetPluralStringFUTF16(IDS_NEARBY_FILE_ATTACHMENTS_IMAGES, 1));
+
+  EXPECT_EQ(message_center::NOTIFICATION_TYPE_SIMPLE, notification.type());
+  EXPECT_EQ(expected_title, notification.title());
+  EXPECT_EQ(expected_message, notification.message());
+  // TODO(crbug.com/1102348): verify notification.icon()
+  EXPECT_EQ(GURL(), notification.origin_url());
+  EXPECT_TRUE(notification.never_timeout());
+  EXPECT_FALSE(notification.renotify());
+  EXPECT_EQ(&kNearbyShareIcon, &notification.vector_small_image());
+  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_SOURCE),
+            notification.display_source());
+
+  const std::vector<message_center::ButtonInfo>& buttons =
+      notification.buttons();
+  ASSERT_EQ(2u, buttons.size());
+
+  const message_center::ButtonInfo& receive_button = buttons[0];
+  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_RECEIVE_ACTION),
+            receive_button.title);
+  const message_center::ButtonInfo& decline_button = buttons[1];
+  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_DECLINE_ACTION),
+            decline_button.title);
+}
+
+TEST_F(NearbyNotificationManagerTest, ShowOnboarding_ShowsNotification) {
+  manager()->ShowOnboarding();
+
+  std::vector<message_center::Notification> notifications =
+      GetDisplayedNotifications();
+  ASSERT_EQ(1u, notifications.size());
+
+  const message_center::Notification& notification = notifications[0];
+  EXPECT_EQ(message_center::NOTIFICATION_TYPE_SIMPLE, notification.type());
+  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_ONBOARDING_TITLE),
+            notification.title());
+  EXPECT_EQ(
+      l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_ONBOARDING_MESSAGE),
+      notification.message());
+  EXPECT_TRUE(notification.icon().IsEmpty());
+  EXPECT_EQ(GURL(), notification.origin_url());
+  EXPECT_FALSE(notification.never_timeout());
+  EXPECT_FALSE(notification.renotify());
+  EXPECT_EQ(&kNearbyShareIcon, &notification.vector_small_image());
+  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_SOURCE),
+            notification.display_source());
+  EXPECT_EQ(0u, notification.buttons().size());
+}
diff --git a/chrome/browser/nearby_sharing/nearby_process_manager.cc b/chrome/browser/nearby_sharing/nearby_process_manager.cc
index 2a63288..037dae5 100644
--- a/chrome/browser/nearby_sharing/nearby_process_manager.cc
+++ b/chrome/browser/nearby_sharing/nearby_process_manager.cc
@@ -7,7 +7,10 @@
 #include <memory>
 #include <utility>
 
+#include "base/barrier_closure.h"
 #include "base/bind.h"
+#include "base/callback.h"
+#include "base/callback_helpers.h"
 #include "base/files/file_path.h"
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
@@ -20,6 +23,7 @@
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/sharing/webrtc/sharing_mojo_service.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
+#include "chrome/services/sharing/public/mojom/nearby_connections.mojom.h"
 #include "components/prefs/pref_service.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "content/public/browser/service_process_host.h"
@@ -130,6 +134,7 @@
   if (!IsActiveProfile(profile))
     return nullptr;
 
+  active_profile_ = profile;
   // Launch a new Nearby Connections interface if required.
   if (!connections_.is_bound())
     BindNearbyConnections();
@@ -142,6 +147,7 @@
   if (!IsActiveProfile(profile))
     return nullptr;
 
+  active_profile_ = profile;
   // Launch a new Nearby Sharing Decoder interface if required.
   if (!decoder_.is_bound())
     BindNearbySharingDecoder();
@@ -155,7 +161,6 @@
 
   bool was_running = sharing_process_.is_bound();
 
-  connections_host_.reset();
   connections_.reset();
   decoder_.reset();
   sharing_process_.reset();
@@ -186,46 +191,6 @@
       &NearbyProcessManager::OnNearbyProcessStopped, base::Unretained(this)));
 }
 
-void NearbyProcessManager::GetBluetoothAdapter(
-    location::nearby::connections::mojom::NearbyConnectionsHost::
-        GetBluetoothAdapterCallback callback) {
-  NS_LOG(VERBOSE) << __func__
-                  << " Request for Bluetooth "
-                     "adapter received on the browser process.";
-  if (device::BluetoothAdapterFactory::IsBluetoothSupported()) {
-    device::BluetoothAdapterFactory::Get()->GetAdapter(
-        base::BindOnce(&NearbyProcessManager::OnGetBluetoothAdapter,
-                       base::Unretained(this), std::move(callback)));
-  } else {
-    NS_LOG(VERBOSE)
-        << __func__
-        << " Bluetooth is not supported on this device, returning NullRemote";
-    std::move(callback).Run(/*adapter=*/mojo::NullRemote());
-  }
-}
-
-void NearbyProcessManager::GetWebRtcSignalingMessenger(
-    location::nearby::connections::mojom::NearbyConnectionsHost::
-        GetWebRtcSignalingMessengerCallback callback) {
-  if (!IsAnyProfileActive()) {
-    std::move(callback).Run(/*messenger=*/mojo::NullRemote());
-    return;
-  }
-
-  auto url_loader_factory =
-      content::BrowserContext::GetDefaultStoragePartition(active_profile_)
-          ->GetURLLoaderFactoryForBrowserProcess();
-  signin::IdentityManager* identity_manager =
-      IdentityManagerFactory::GetForProfile(active_profile_);
-
-  mojo::PendingRemote<sharing::mojom::WebRtcSignalingMessenger> messenger;
-  mojo::MakeSelfOwnedReceiver(
-      std::make_unique<WebRtcSignalingMessenger>(identity_manager,
-                                                 std::move(url_loader_factory)),
-      messenger.InitWithNewPipeAndPassReceiver());
-  std::move(callback).Run(std::move(messenger));
-}
-
 NearbyProcessManager::NearbyProcessManager() {
   // profile_manager() might be null in tests or during shutdown.
   if (auto* manager = g_browser_process->profile_manager())
@@ -251,22 +216,106 @@
   if (!sharing_process_.is_bound())
     LaunchNewProcess();
 
-  // Create the Nearby Connections stack in the sandboxed process.
-  // base::Unretained() calls below are safe as |this| is a singleton.
-  sharing_process_->CreateNearbyConnections(
-      connections_host_.BindNewPipeAndPassRemote(),
-      base::BindOnce(&NearbyProcessManager::OnNearbyConnections,
-                     base::Unretained(this),
-                     connections_.BindNewPipeAndPassReceiver()));
+  mojo::PendingReceiver<NearbyConnectionsMojom> pending_receiver =
+      connections_.BindNewPipeAndPassReceiver();
+  auto dependencies = location::nearby::connections::mojom::
+      NearbyConnectionsDependencies::New();
+  location::nearby::connections::mojom::NearbyConnectionsDependencies*
+      dependencies_ptr = dependencies.get();
+
+  // base::Unretained() is safe as |this| is a singleton.
+  auto done_closure = base::BarrierClosure(
+      /*num_closures=*/2,
+      base::BindOnce(&NearbyProcessManager::OnDependenciesGathered,
+                     base::Unretained(this), std::move(pending_receiver),
+                     std::move(dependencies)));
+
+  GetBluetoothAdapter(dependencies_ptr,
+                      base::ScopedClosureRunner(done_closure));
+
+  GetWebRtcSignalingMessenger(dependencies_ptr,
+                              base::ScopedClosureRunner(done_closure));
 
   // Terminate the process if the Nearby Connections interface disconnects as
   // that indicated an incorrect state and we have to restart the process.
-  connections_host_.set_disconnect_handler(base::BindOnce(
-      &NearbyProcessManager::OnNearbyProcessStopped, base::Unretained(this)));
+  // base::Unretained() is safe as |this| is a singleton.
   connections_.set_disconnect_handler(base::BindOnce(
       &NearbyProcessManager::OnNearbyProcessStopped, base::Unretained(this)));
 }
 
+void NearbyProcessManager::GetBluetoothAdapter(
+    location::nearby::connections::mojom::NearbyConnectionsDependencies*
+        dependencies,
+    base::ScopedClosureRunner done_closure) {
+  NS_LOG(VERBOSE) << __func__
+                  << " Request for Bluetooth "
+                     "adapter received on the browser process.";
+  if (!device::BluetoothAdapterFactory::IsBluetoothSupported()) {
+    NS_LOG(VERBOSE) << __func__ << " Bluetooth is not supported on this device";
+    dependencies->bluetooth_adapter = mojo::NullRemote();
+    return;
+  }
+
+  // base::Unretained() is safe as |this| is a singleton.
+  device::BluetoothAdapterFactory::Get()->GetAdapter(base::BindOnce(
+      &NearbyProcessManager::OnGetBluetoothAdapter, base::Unretained(this),
+      dependencies, std::move(done_closure)));
+}
+
+void NearbyProcessManager::OnGetBluetoothAdapter(
+    location::nearby::connections::mojom::NearbyConnectionsDependencies*
+        dependencies,
+    base::ScopedClosureRunner done_closure,
+    scoped_refptr<device::BluetoothAdapter> adapter) {
+  if (!adapter->IsPresent()) {
+    NS_LOG(VERBOSE) << __func__ << " Bluetooth adapter is not present";
+    dependencies->bluetooth_adapter = mojo::NullRemote();
+    return;
+  }
+
+  mojo::PendingRemote<bluetooth::mojom::Adapter> pending_adapter;
+  mojo::MakeSelfOwnedReceiver(std::make_unique<bluetooth::Adapter>(adapter),
+                              pending_adapter.InitWithNewPipeAndPassReceiver());
+
+  NS_LOG(VERBOSE) << __func__ << " Got bluetooth adapter";
+  dependencies->bluetooth_adapter = std::move(pending_adapter);
+}
+
+void NearbyProcessManager::GetWebRtcSignalingMessenger(
+    location::nearby::connections::mojom::NearbyConnectionsDependencies*
+        dependencies,
+    base::ScopedClosureRunner done_closure) {
+  DCHECK(active_profile_);
+
+  auto url_loader_factory = active_profile_->GetURLLoaderFactory();
+  signin::IdentityManager* identity_manager =
+      IdentityManagerFactory::GetForProfile(active_profile_);
+
+  mojo::PendingRemote<sharing::mojom::WebRtcSignalingMessenger> messenger;
+  mojo::MakeSelfOwnedReceiver(
+      std::make_unique<WebRtcSignalingMessenger>(identity_manager,
+                                                 std::move(url_loader_factory)),
+      messenger.InitWithNewPipeAndPassReceiver());
+
+  NS_LOG(VERBOSE) << __func__ << " Got WebRTC signaling messenger";
+  dependencies->webrtc_signaling_messenger = std::move(messenger);
+}
+
+void NearbyProcessManager::OnDependenciesGathered(
+    mojo::PendingReceiver<NearbyConnectionsMojom> receiver,
+    location::nearby::connections::mojom::NearbyConnectionsDependenciesPtr
+        dependencies) {
+  if (!sharing_process_.is_bound())
+    return;
+
+  // Create the Nearby Connections stack in the sandboxed process.
+  // base::Unretained() calls below are safe as |this| is a singleton.
+  sharing_process_->CreateNearbyConnections(
+      std::move(dependencies),
+      base::BindOnce(&NearbyProcessManager::OnNearbyConnections,
+                     base::Unretained(this), std::move(receiver)));
+}
+
 void NearbyProcessManager::OnNearbyConnections(
     mojo::PendingReceiver<NearbyConnectionsMojom> receiver,
     mojo::PendingRemote<NearbyConnectionsMojom> remote) {
@@ -313,15 +362,3 @@
   for (auto& observer : observers_)
     observer.OnNearbyProcessStarted();
 }
-
-void NearbyProcessManager::OnGetBluetoothAdapter(
-    location::nearby::connections::mojom::NearbyConnectionsHost::
-        GetBluetoothAdapterCallback callback,
-    scoped_refptr<device::BluetoothAdapter> adapter) {
-  NS_LOG(VERBOSE) << __func__
-                  << " Got adapter instance, returning to utility process";
-  mojo::PendingRemote<bluetooth::mojom::Adapter> pending_adapter;
-  mojo::MakeSelfOwnedReceiver(std::make_unique<bluetooth::Adapter>(adapter),
-                              pending_adapter.InitWithNewPipeAndPassReceiver());
-  std::move(callback).Run(std::move(pending_adapter));
-}
diff --git a/chrome/browser/nearby_sharing/nearby_process_manager.h b/chrome/browser/nearby_sharing/nearby_process_manager.h
index a9b1603..385a35e3 100644
--- a/chrome/browser/nearby_sharing/nearby_process_manager.h
+++ b/chrome/browser/nearby_sharing/nearby_process_manager.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_NEARBY_SHARING_NEARBY_PROCESS_MANAGER_H_
 #define CHROME_BROWSER_NEARBY_SHARING_NEARBY_PROCESS_MANAGER_H_
 
+#include "base/callback_forward.h"
 #include "base/gtest_prod_util.h"
 #include "base/no_destructor.h"
 #include "base/observer_list.h"
@@ -26,14 +27,10 @@
 // Manages the lifetime of the Nearby process. It runs the Nearby Connections
 // library and Nearby Sharing data decoding. Only one instance of the process is
 // supported at a time.
-class NearbyProcessManager
-    : public ProfileManagerObserver,
-      public location::nearby::connections::mojom::NearbyConnectionsHost {
+class NearbyProcessManager : public ProfileManagerObserver {
  public:
   using NearbyConnectionsMojom =
       location::nearby::connections::mojom::NearbyConnections;
-  using NearbyConnectionsHostMojom =
-      location::nearby::connections::mojom::NearbyConnectionsHost;
   using NearbySharingDecoderMojom = sharing::mojom::NearbySharingDecoder;
 
   // Returns an instance to the singleton of this class. This is used from
@@ -113,11 +110,6 @@
   // process running in a sandbox.
   void BindSharingProcess(mojo::PendingRemote<sharing::mojom::Sharing> sharing);
 
-  // location::nearby::connections::mojom::NearbyConnectionsHost:
-  void GetBluetoothAdapter(GetBluetoothAdapterCallback callback) override;
-  void GetWebRtcSignalingMessenger(
-      GetWebRtcSignalingMessengerCallback callback) override;
-
  private:
   FRIEND_TEST_ALL_PREFIXES(NearbyProcessManagerTest, AddRemoveObserver);
   FRIEND_TEST_ALL_PREFIXES(NearbySharingServiceImplTest,
@@ -140,6 +132,28 @@
   // if there is none running yet.
   void BindNearbyConnections();
 
+  // Gather dependencies for NearbyConnections:
+  void GetBluetoothAdapter(
+      location::nearby::connections::mojom::NearbyConnectionsDependencies*
+          dependencies,
+      base::ScopedClosureRunner done_closure);
+  void OnGetBluetoothAdapter(
+      location::nearby::connections::mojom::NearbyConnectionsDependencies*
+          dependencies,
+      base::ScopedClosureRunner done_closure,
+      scoped_refptr<device::BluetoothAdapter> adapter);
+
+  void GetWebRtcSignalingMessenger(
+      location::nearby::connections::mojom::NearbyConnectionsDependencies*
+          dependencies,
+      base::ScopedClosureRunner done_closure);
+
+  // Called when all dependencies are gathered.
+  void OnDependenciesGathered(
+      mojo::PendingReceiver<NearbyConnectionsMojom> receiver,
+      location::nearby::connections::mojom::NearbyConnectionsDependenciesPtr
+          dependencies);
+
   // Called by the sandboxed process after initializing the Nearby Connections
   // library.
   void OnNearbyConnections(
@@ -161,11 +175,6 @@
       mojo::PendingReceiver<NearbySharingDecoderMojom> receiver,
       mojo::PendingRemote<NearbySharingDecoderMojom> remote);
 
-  // Called when a bluetooth adapter is acquired and we can finish
-  // the GetBluetoothAdapter mojo call
-  void OnGetBluetoothAdapter(GetBluetoothAdapterCallback callback,
-                             scoped_refptr<device::BluetoothAdapter> adapter);
-
   // The bound remote to a sandboxed process.
   mojo::Remote<sharing::mojom::Sharing> sharing_process_;
   // The bound remote to the Nearby Connections library inside the sandbox.
@@ -173,8 +182,6 @@
   // The bound remote to the Nearby Decoder interface inside the sandbox.
   mojo::Remote<NearbySharingDecoderMojom> decoder_;
 
-  // Host interface for the Nearby Connections interface.
-  mojo::Receiver<NearbyConnectionsHostMojom> connections_host_{this};
   // All registered observers, typically one per loaded profile.
   base::ObserverList<Observer> observers_;
   // Profile using the Nearby process. This might be nullptr if the active
diff --git a/chrome/browser/nearby_sharing/nearby_process_manager_unittest.cc b/chrome/browser/nearby_sharing/nearby_process_manager_unittest.cc
index acbedce..0d1d6c1 100644
--- a/chrome/browser/nearby_sharing/nearby_process_manager_unittest.cc
+++ b/chrome/browser/nearby_sharing/nearby_process_manager_unittest.cc
@@ -32,8 +32,10 @@
 
 using NearbyConnectionsMojom =
     location::nearby::connections::mojom::NearbyConnections;
-using NearbyConnectionsHostMojom =
-    location::nearby::connections::mojom::NearbyConnectionsHost;
+using NearbyConnectionsDependencies =
+    location::nearby::connections::mojom::NearbyConnectionsDependencies;
+using NearbyConnectionsDependenciesPtr =
+    location::nearby::connections::mojom::NearbyConnectionsDependenciesPtr;
 using NearbySharingDecoderMojom = sharing::mojom::NearbySharingDecoder;
 
 namespace {
@@ -80,10 +82,9 @@
     NOTIMPLEMENTED();
   }
   void CreateNearbyConnections(
-      mojo::PendingRemote<NearbyConnectionsHostMojom> host,
+      NearbyConnectionsDependenciesPtr dependencies,
       CreateNearbyConnectionsCallback callback) override {
-    connections_host.Bind(std::move(host));
-
+    dependencies_ = std::move(dependencies);
     mojo::PendingRemote<NearbyConnectionsMojom> remote;
     mojo::MakeSelfOwnedReceiver(std::make_unique<MockNearbyConnections>(),
                                 remote.InitWithNewPipeAndPassReceiver());
@@ -112,11 +113,13 @@
 
   void Reset() { receiver.reset(); }
 
+  NearbyConnectionsDependencies* dependencies() { return dependencies_.get(); }
+
  private:
   base::RunLoop run_loop_connections;
   base::RunLoop run_loop_decoder;
   mojo::Receiver<sharing::mojom::Sharing> receiver{this};
-  mojo::Remote<NearbyConnectionsHostMojom> connections_host;
+  NearbyConnectionsDependenciesPtr dependencies_;
 };
 
 class MockNearbyProcessManagerObserver : public NearbyProcessManager::Observer {
@@ -259,6 +262,10 @@
   FakeSharingMojoService fake_sharing_service;
   manager.BindSharingProcess(fake_sharing_service.BindSharingService());
 
+  auto adapter = base::MakeRefCounted<device::MockBluetoothAdapter>();
+  EXPECT_CALL(*adapter, IsPresent()).WillOnce(testing::Return(true));
+  device::BluetoothAdapterFactory::SetAdapterForTesting(adapter);
+
   MockNearbyProcessManagerObserver observer;
   base::RunLoop run_loop_started;
   base::RunLoop run_loop_stopped;
@@ -291,10 +298,40 @@
   FakeSharingMojoService fake_sharing_service;
   manager.BindSharingProcess(fake_sharing_service.BindSharingService());
 
+  auto adapter = base::MakeRefCounted<device::MockBluetoothAdapter>();
+  EXPECT_CALL(*adapter, IsPresent()).WillOnce(testing::Return(true));
+  device::BluetoothAdapterFactory::SetAdapterForTesting(adapter);
+
   // Request a new Nearby Connections interface.
   EXPECT_NE(nullptr, manager.GetOrStartNearbyConnections(profile));
   // Expect the manager to bind a new Nearby Connections pipe.
   fake_sharing_service.WaitForConnections();
+
+  EXPECT_TRUE(fake_sharing_service.dependencies()->bluetooth_adapter);
+  EXPECT_TRUE(fake_sharing_service.dependencies()->webrtc_signaling_messenger);
+}
+
+TEST_F(NearbyProcessManagerTest,
+       GetOrStartNearbyConnections_BluetoothNotPresent) {
+  auto& manager = NearbyProcessManager::GetInstance();
+  Profile* profile = CreateProfile("name");
+  manager.SetActiveProfile(profile);
+
+  // Inject fake Nearby process mojo connection.
+  FakeSharingMojoService fake_sharing_service;
+  manager.BindSharingProcess(fake_sharing_service.BindSharingService());
+
+  auto adapter = base::MakeRefCounted<device::MockBluetoothAdapter>();
+  EXPECT_CALL(*adapter, IsPresent()).WillOnce(testing::Return(false));
+  device::BluetoothAdapterFactory::SetAdapterForTesting(adapter);
+
+  // Request a new Nearby Connections interface.
+  EXPECT_NE(nullptr, manager.GetOrStartNearbyConnections(profile));
+  // Expect the manager to bind a new Nearby Connections pipe.
+  fake_sharing_service.WaitForConnections();
+
+  EXPECT_FALSE(fake_sharing_service.dependencies()->bluetooth_adapter);
+  EXPECT_TRUE(fake_sharing_service.dependencies()->webrtc_signaling_messenger);
 }
 
 TEST_F(NearbyProcessManagerTest, ResetNearbyProcess) {
@@ -377,6 +414,10 @@
   FakeSharingMojoService fake_sharing_service;
   manager.BindSharingProcess(fake_sharing_service.BindSharingService());
 
+  auto adapter = base::MakeRefCounted<device::MockBluetoothAdapter>();
+  EXPECT_CALL(*adapter, IsPresent()).WillOnce(testing::Return(true));
+  device::BluetoothAdapterFactory::SetAdapterForTesting(adapter);
+
   MockNearbyProcessManagerObserver observer;
   base::RunLoop run_loop_started;
   base::RunLoop run_loop_stopped;
@@ -404,26 +445,3 @@
 
   manager.RemoveObserver(&observer);
 }
-
-TEST_F(NearbyProcessManagerTest, GetBluetoothAdapter) {
-  auto& manager = NearbyProcessManager::GetInstance();
-
-  auto adapter = base::MakeRefCounted<device::MockBluetoothAdapter>();
-  device::BluetoothAdapterFactory::SetAdapterForTesting(adapter);
-
-  base::RunLoop loop;
-  manager.GetBluetoothAdapter(base::BindLambdaForTesting(
-      [&](mojo::PendingRemote<::bluetooth::mojom::Adapter>
-              pending_remote_adapter) { loop.Quit(); }));
-  loop.Run();
-}
-
-TEST_F(NearbyProcessManagerTest, GetWebRtcSignalingMessenger) {
-  auto& manager = NearbyProcessManager::GetInstance();
-
-  base::RunLoop loop;
-  manager.GetWebRtcSignalingMessenger(base::BindLambdaForTesting(
-      [&](mojo::PendingRemote<sharing::mojom::WebRtcSignalingMessenger>
-              messenger) { loop.Quit(); }));
-  loop.Run();
-}
diff --git a/chrome/browser/nearby_sharing/scheduling/BUILD.gn b/chrome/browser/nearby_sharing/scheduling/BUILD.gn
index 1e0de896..375d81ca 100644
--- a/chrome/browser/nearby_sharing/scheduling/BUILD.gn
+++ b/chrome/browser/nearby_sharing/scheduling/BUILD.gn
@@ -14,6 +14,8 @@
     "nearby_share_scheduler.h",
     "nearby_share_scheduler_base.cc",
     "nearby_share_scheduler_base.h",
+    "nearby_share_scheduler_factory.cc",
+    "nearby_share_scheduler_factory.h",
   ]
 
   deps = [
@@ -25,6 +27,22 @@
   ]
 }
 
+source_set("test_support") {
+  testonly = true
+
+  sources = [
+    "fake_nearby_share_scheduler.cc",
+    "fake_nearby_share_scheduler.h",
+    "fake_nearby_share_scheduler_factory.cc",
+    "fake_nearby_share_scheduler_factory.h",
+  ]
+
+  deps = [
+    ":scheduling",
+    "//base",
+  ]
+}
+
 source_set("unit_tests") {
   testonly = true
 
diff --git a/chrome/browser/nearby_sharing/scheduling/fake_nearby_share_scheduler.cc b/chrome/browser/nearby_sharing/scheduling/fake_nearby_share_scheduler.cc
new file mode 100644
index 0000000..cdd70d9
--- /dev/null
+++ b/chrome/browser/nearby_sharing/scheduling/fake_nearby_share_scheduler.cc
@@ -0,0 +1,64 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/nearby_sharing/scheduling/fake_nearby_share_scheduler.h"
+
+#include <utility>
+
+FakeNearbyShareScheduler::FakeNearbyShareScheduler(OnRequestCallback callback)
+    : NearbyShareScheduler(std::move(callback)) {}
+
+FakeNearbyShareScheduler::~FakeNearbyShareScheduler() = default;
+
+void FakeNearbyShareScheduler::MakeImmediateRequest() {
+  ++num_immediate_requests_;
+}
+
+void FakeNearbyShareScheduler::HandleResult(bool success) {
+  handled_results_.push_back(success);
+}
+
+base::Optional<base::Time> FakeNearbyShareScheduler::GetLastSuccessTime()
+    const {
+  return last_success_time_;
+}
+
+base::Optional<base::TimeDelta>
+FakeNearbyShareScheduler::GetTimeUntilNextRequest() const {
+  return time_until_next_request_;
+}
+
+bool FakeNearbyShareScheduler::IsWaitingForResult() const {
+  return is_waiting_for_result_;
+}
+
+size_t FakeNearbyShareScheduler::GetNumConsecutiveFailures() const {
+  return num_consecutive_failures_;
+}
+
+void FakeNearbyShareScheduler::OnStart() {}
+
+void FakeNearbyShareScheduler::OnStop() {}
+
+void FakeNearbyShareScheduler::InvokeRequestCallback() {
+  NotifyOfRequest();
+}
+
+void FakeNearbyShareScheduler::SetLastSuccessTime(
+    base::Optional<base::Time> time) {
+  last_success_time_ = time;
+}
+
+void FakeNearbyShareScheduler::SetTimeUntilNextRequest(
+    base::Optional<base::TimeDelta> time_delta) {
+  time_until_next_request_ = time_delta;
+}
+
+void FakeNearbyShareScheduler::SetIsWaitingForResult(bool is_waiting) {
+  is_waiting_for_result_ = is_waiting;
+}
+
+void FakeNearbyShareScheduler::SetNumConsecutiveFailures(size_t num_failures) {
+  num_consecutive_failures_ = num_failures;
+}
diff --git a/chrome/browser/nearby_sharing/scheduling/fake_nearby_share_scheduler.h b/chrome/browser/nearby_sharing/scheduling/fake_nearby_share_scheduler.h
new file mode 100644
index 0000000..33a6feb3
--- /dev/null
+++ b/chrome/browser/nearby_sharing/scheduling/fake_nearby_share_scheduler.h
@@ -0,0 +1,54 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NEARBY_SHARING_SCHEDULING_FAKE_NEARBY_SHARE_SCHEDULER_H_
+#define CHROME_BROWSER_NEARBY_SHARING_SCHEDULING_FAKE_NEARBY_SHARE_SCHEDULER_H_
+
+#include <vector>
+
+#include "base/optional.h"
+#include "base/time/time.h"
+#include "chrome/browser/nearby_sharing/scheduling/nearby_share_scheduler.h"
+
+// A fake implementation of NearbyShareScheduler that allows the user to set all
+// scheduling data. It tracks the number of immediate requests and the handled
+// results. The on-request callback can be invoked using
+// InvokeRequestCallback().
+class FakeNearbyShareScheduler : public NearbyShareScheduler {
+ public:
+  explicit FakeNearbyShareScheduler(OnRequestCallback callback);
+  ~FakeNearbyShareScheduler() override;
+
+  // NearbyShareScheduler:
+  void MakeImmediateRequest() override;
+  void HandleResult(bool success) override;
+  base::Optional<base::Time> GetLastSuccessTime() const override;
+  base::Optional<base::TimeDelta> GetTimeUntilNextRequest() const override;
+  bool IsWaitingForResult() const override;
+  size_t GetNumConsecutiveFailures() const override;
+
+  void SetLastSuccessTime(base::Optional<base::Time> time);
+  void SetTimeUntilNextRequest(base::Optional<base::TimeDelta> time_delta);
+  void SetIsWaitingForResult(bool is_waiting);
+  void SetNumConsecutiveFailures(size_t num_failures);
+
+  void InvokeRequestCallback();
+
+  size_t num_immediate_requests() const { return num_immediate_requests_; }
+  const std::vector<bool>& handled_results() const { return handled_results_; }
+
+ private:
+  // NearbyShareScheduler:
+  void OnStart() override;
+  void OnStop() override;
+
+  size_t num_immediate_requests_ = 0;
+  std::vector<bool> handled_results_;
+  base::Optional<base::Time> last_success_time_;
+  base::Optional<base::TimeDelta> time_until_next_request_;
+  bool is_waiting_for_result_ = false;
+  size_t num_consecutive_failures_ = 0;
+};
+
+#endif  // CHROME_BROWSER_NEARBY_SHARING_SCHEDULING_FAKE_NEARBY_SHARE_SCHEDULER_H_
diff --git a/chrome/browser/nearby_sharing/scheduling/fake_nearby_share_scheduler_factory.cc b/chrome/browser/nearby_sharing/scheduling/fake_nearby_share_scheduler_factory.cc
new file mode 100644
index 0000000..699ff4e
--- /dev/null
+++ b/chrome/browser/nearby_sharing/scheduling/fake_nearby_share_scheduler_factory.cc
@@ -0,0 +1,89 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/nearby_sharing/scheduling/fake_nearby_share_scheduler_factory.h"
+
+#include <utility>
+
+FakeNearbyShareSchedulerFactory::ExpirationInstance::ExpirationInstance() =
+    default;
+
+FakeNearbyShareSchedulerFactory::ExpirationInstance::~ExpirationInstance() =
+    default;
+
+FakeNearbyShareSchedulerFactory::FakeNearbyShareSchedulerFactory() = default;
+
+FakeNearbyShareSchedulerFactory::~FakeNearbyShareSchedulerFactory() = default;
+
+std::unique_ptr<NearbyShareScheduler>
+FakeNearbyShareSchedulerFactory::CreateExpirationSchedulerInstance(
+    NearbyShareExpirationScheduler::ExpirationTimeCallback
+        expiration_time_callback,
+    bool retry_failures,
+    bool require_connectivity,
+    const std::string& pref_name,
+    PrefService* pref_service,
+    NearbyShareScheduler::OnRequestCallback on_request_callback,
+    const base::Clock* clock) {
+  ExpirationInstance instance;
+  instance.expiration_time_callback = std::move(expiration_time_callback);
+  instance.retry_failures = retry_failures;
+  instance.require_connectivity = require_connectivity;
+  instance.pref_service = pref_service;
+  instance.clock = clock;
+
+  auto scheduler = std::make_unique<FakeNearbyShareScheduler>(
+      std::move(on_request_callback));
+  instance.fake_scheduler = scheduler.get();
+
+  return scheduler;
+}
+
+std::unique_ptr<NearbyShareScheduler>
+FakeNearbyShareSchedulerFactory::CreateOnDemandSchedulerInstance(
+    bool retry_failures,
+    bool require_connectivity,
+    const std::string& pref_name,
+    PrefService* pref_service,
+    NearbyShareScheduler::OnRequestCallback callback,
+    const base::Clock* clock) {
+  OnDemandInstance instance;
+  instance.retry_failures = retry_failures;
+  instance.require_connectivity = require_connectivity;
+  instance.pref_service = pref_service;
+  instance.clock = clock;
+
+  auto scheduler =
+      std::make_unique<FakeNearbyShareScheduler>(std::move(callback));
+  instance.fake_scheduler = scheduler.get();
+
+  pref_name_to_on_demand_instance_.insert_or_assign(pref_name, instance);
+
+  return scheduler;
+}
+
+std::unique_ptr<NearbyShareScheduler>
+FakeNearbyShareSchedulerFactory::CreatePeriodicSchedulerInstance(
+    base::TimeDelta request_period,
+    bool retry_failures,
+    bool require_connectivity,
+    const std::string& pref_name,
+    PrefService* pref_service,
+    NearbyShareScheduler::OnRequestCallback callback,
+    const base::Clock* clock) {
+  PeriodicInstance instance;
+  instance.request_period = request_period;
+  instance.retry_failures = retry_failures;
+  instance.require_connectivity = require_connectivity;
+  instance.pref_service = pref_service;
+  instance.clock = clock;
+
+  auto scheduler =
+      std::make_unique<FakeNearbyShareScheduler>(std::move(callback));
+  instance.fake_scheduler = scheduler.get();
+
+  pref_name_to_periodic_instance_.insert_or_assign(pref_name, instance);
+
+  return scheduler;
+}
diff --git a/chrome/browser/nearby_sharing/scheduling/fake_nearby_share_scheduler_factory.h b/chrome/browser/nearby_sharing/scheduling/fake_nearby_share_scheduler_factory.h
new file mode 100644
index 0000000..973956ce
--- /dev/null
+++ b/chrome/browser/nearby_sharing/scheduling/fake_nearby_share_scheduler_factory.h
@@ -0,0 +1,110 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NEARBY_SHARING_SCHEDULING_FAKE_NEARBY_SHARE_SCHEDULER_FACTORY_H_
+#define CHROME_BROWSER_NEARBY_SHARING_SCHEDULING_FAKE_NEARBY_SHARE_SCHEDULER_FACTORY_H_
+
+#include <memory>
+#include <string>
+
+#include "base/containers/flat_map.h"
+#include "base/optional.h"
+#include "base/time/time.h"
+#include "chrome/browser/nearby_sharing/scheduling/fake_nearby_share_scheduler.h"
+#include "chrome/browser/nearby_sharing/scheduling/nearby_share_expiration_scheduler.h"
+#include "chrome/browser/nearby_sharing/scheduling/nearby_share_scheduler.h"
+#include "chrome/browser/nearby_sharing/scheduling/nearby_share_scheduler_factory.h"
+
+class NearbyShareScheduler;
+class PrefService;
+
+// A fake NearbyShareScheduler factory that creates instances of
+// FakeNearbyShareScheduler instead of expiration, on-demand, or periodic
+// scheduler. It stores the factory input parameters as well as a raw pointer to
+// the fake scheduler for each instance created.
+class FakeNearbyShareSchedulerFactory : public NearbyShareSchedulerFactory {
+ public:
+  struct ExpirationInstance {
+    ExpirationInstance();
+    ~ExpirationInstance();
+
+    FakeNearbyShareScheduler* fake_scheduler = nullptr;
+    NearbyShareExpirationScheduler::ExpirationTimeCallback
+        expiration_time_callback;
+    bool retry_failures;
+    bool require_connectivity;
+    PrefService* pref_service = nullptr;
+    const base::Clock* clock = nullptr;
+  };
+
+  struct OnDemandInstance {
+    FakeNearbyShareScheduler* fake_scheduler = nullptr;
+    bool retry_failures;
+    bool require_connectivity;
+    PrefService* pref_service = nullptr;
+    const base::Clock* clock = nullptr;
+  };
+
+  struct PeriodicInstance {
+    FakeNearbyShareScheduler* fake_scheduler = nullptr;
+    base::TimeDelta request_period;
+    bool retry_failures;
+    bool require_connectivity;
+    PrefService* pref_service = nullptr;
+    const base::Clock* clock = nullptr;
+  };
+
+  FakeNearbyShareSchedulerFactory();
+  ~FakeNearbyShareSchedulerFactory() override;
+
+  const base::flat_map<std::string, ExpirationInstance>&
+  pref_name_to_expiration_instance() const {
+    return pref_name_to_expiration_instance_;
+  }
+
+  const base::flat_map<std::string, OnDemandInstance>&
+  pref_name_to_on_demand_instance() const {
+    return pref_name_to_on_demand_instance_;
+  }
+
+  const base::flat_map<std::string, PeriodicInstance>&
+  pref_name_to_periodic_instance() const {
+    return pref_name_to_periodic_instance_;
+  }
+
+ private:
+  // NearbyShareSchedulerFactory:
+  std::unique_ptr<NearbyShareScheduler> CreateExpirationSchedulerInstance(
+      NearbyShareExpirationScheduler::ExpirationTimeCallback
+          expiration_time_callback,
+      bool retry_failures,
+      bool require_connectivity,
+      const std::string& pref_name,
+      PrefService* pref_service,
+      NearbyShareScheduler::OnRequestCallback on_request_callback,
+      const base::Clock* clock) override;
+  std::unique_ptr<NearbyShareScheduler> CreateOnDemandSchedulerInstance(
+      bool retry_failures,
+      bool require_connectivity,
+      const std::string& pref_name,
+      PrefService* pref_service,
+      NearbyShareScheduler::OnRequestCallback callback,
+      const base::Clock* clock) override;
+  std::unique_ptr<NearbyShareScheduler> CreatePeriodicSchedulerInstance(
+      base::TimeDelta request_period,
+      bool retry_failures,
+      bool require_connectivity,
+      const std::string& pref_name,
+      PrefService* pref_service,
+      NearbyShareScheduler::OnRequestCallback callback,
+      const base::Clock* clock) override;
+
+  base::flat_map<std::string, ExpirationInstance>
+      pref_name_to_expiration_instance_;
+  base::flat_map<std::string, OnDemandInstance>
+      pref_name_to_on_demand_instance_;
+  base::flat_map<std::string, PeriodicInstance> pref_name_to_periodic_instance_;
+};
+
+#endif  // CHROME_BROWSER_NEARBY_SHARING_SCHEDULING_FAKE_NEARBY_SHARE_SCHEDULER_FACTORY_H_
diff --git a/chrome/browser/nearby_sharing/scheduling/nearby_share_scheduler_factory.cc b/chrome/browser/nearby_sharing/scheduling/nearby_share_scheduler_factory.cc
new file mode 100644
index 0000000..f202659
--- /dev/null
+++ b/chrome/browser/nearby_sharing/scheduling/nearby_share_scheduler_factory.cc
@@ -0,0 +1,86 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/nearby_sharing/scheduling/nearby_share_scheduler_factory.h"
+
+#include <utility>
+
+#include "chrome/browser/nearby_sharing/scheduling/nearby_share_on_demand_scheduler.h"
+#include "chrome/browser/nearby_sharing/scheduling/nearby_share_periodic_scheduler.h"
+
+// static
+NearbyShareSchedulerFactory* NearbyShareSchedulerFactory::test_factory_ =
+    nullptr;
+
+// static
+std::unique_ptr<NearbyShareScheduler>
+NearbyShareSchedulerFactory::CreateExpirationScheduler(
+    NearbyShareExpirationScheduler::ExpirationTimeCallback
+        expiration_time_callback,
+    bool retry_failures,
+    bool require_connectivity,
+    const std::string& pref_name,
+    PrefService* pref_service,
+    NearbyShareScheduler::OnRequestCallback on_request_callback,
+    const base::Clock* clock) {
+  if (test_factory_) {
+    return test_factory_->CreateExpirationSchedulerInstance(
+        std::move(expiration_time_callback), retry_failures,
+        require_connectivity, pref_name, pref_service,
+        std::move(on_request_callback), clock);
+  }
+
+  return std::make_unique<NearbyShareExpirationScheduler>(
+      std::move(expiration_time_callback), retry_failures, require_connectivity,
+      pref_name, pref_service, std::move(on_request_callback), clock);
+}
+
+// static
+std::unique_ptr<NearbyShareScheduler>
+NearbyShareSchedulerFactory::CreateOnDemandScheduler(
+    bool retry_failures,
+    bool require_connectivity,
+    const std::string& pref_name,
+    PrefService* pref_service,
+    NearbyShareScheduler::OnRequestCallback callback,
+    const base::Clock* clock) {
+  if (test_factory_) {
+    return test_factory_->CreateOnDemandSchedulerInstance(
+        retry_failures, require_connectivity, pref_name, pref_service,
+        std::move(callback), clock);
+  }
+
+  return std::make_unique<NearbyShareOnDemandScheduler>(
+      retry_failures, require_connectivity, pref_name, pref_service,
+      std::move(callback), clock);
+}
+
+// static
+std::unique_ptr<NearbyShareScheduler>
+NearbyShareSchedulerFactory::CreatePeriodicScheduler(
+    base::TimeDelta request_period,
+    bool retry_failures,
+    bool require_connectivity,
+    const std::string& pref_name,
+    PrefService* pref_service,
+    NearbyShareScheduler::OnRequestCallback callback,
+    const base::Clock* clock) {
+  if (test_factory_) {
+    return test_factory_->CreatePeriodicSchedulerInstance(
+        request_period, retry_failures, require_connectivity, pref_name,
+        pref_service, std::move(callback), clock);
+  }
+
+  return std::make_unique<NearbySharePeriodicScheduler>(
+      request_period, retry_failures, require_connectivity, pref_name,
+      pref_service, std::move(callback), clock);
+}
+
+// static
+void NearbyShareSchedulerFactory::SetFactoryForTesting(
+    NearbyShareSchedulerFactory* test_factory) {
+  test_factory_ = test_factory;
+}
+
+NearbyShareSchedulerFactory::~NearbyShareSchedulerFactory() = default;
diff --git a/chrome/browser/nearby_sharing/scheduling/nearby_share_scheduler_factory.h b/chrome/browser/nearby_sharing/scheduling/nearby_share_scheduler_factory.h
new file mode 100644
index 0000000..7ed1b47
--- /dev/null
+++ b/chrome/browser/nearby_sharing/scheduling/nearby_share_scheduler_factory.h
@@ -0,0 +1,87 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NEARBY_SHARING_SCHEDULING_NEARBY_SHARE_SCHEDULER_FACTORY_H_
+#define CHROME_BROWSER_NEARBY_SHARING_SCHEDULING_NEARBY_SHARE_SCHEDULER_FACTORY_H_
+
+#include <memory>
+#include <string>
+
+#include "base/time/default_clock.h"
+#include "chrome/browser/nearby_sharing/scheduling/nearby_share_expiration_scheduler.h"
+#include "chrome/browser/nearby_sharing/scheduling/nearby_share_scheduler.h"
+
+class NearbyShareScheduler;
+class PrefService;
+
+// Used to create instances of NearbyShareExpirationScheduler,
+// NearbyShareOnDemandScheduler, and NearbySharePeriodicScheduler. A fake
+// factory can also be set for testing purposes.
+class NearbyShareSchedulerFactory {
+ public:
+  static std::unique_ptr<NearbyShareScheduler> CreateExpirationScheduler(
+      NearbyShareExpirationScheduler::ExpirationTimeCallback
+          expiration_time_callback,
+      bool retry_failures,
+      bool require_connectivity,
+      const std::string& pref_name,
+      PrefService* pref_service,
+      NearbyShareScheduler::OnRequestCallback on_request_callback,
+      const base::Clock* clock = base::DefaultClock::GetInstance());
+
+  static std::unique_ptr<NearbyShareScheduler> CreateOnDemandScheduler(
+      bool retry_failures,
+      bool require_connectivity,
+      const std::string& pref_name,
+      PrefService* pref_service,
+      NearbyShareScheduler::OnRequestCallback callback,
+      const base::Clock* clock = base::DefaultClock::GetInstance());
+
+  static std::unique_ptr<NearbyShareScheduler> CreatePeriodicScheduler(
+      base::TimeDelta request_period,
+      bool retry_failures,
+      bool require_connectivity,
+      const std::string& pref_name,
+      PrefService* pref_service,
+      NearbyShareScheduler::OnRequestCallback callback,
+      const base::Clock* clock = base::DefaultClock::GetInstance());
+
+  static void SetFactoryForTesting(NearbyShareSchedulerFactory* test_factory);
+
+ protected:
+  virtual ~NearbyShareSchedulerFactory();
+
+  virtual std::unique_ptr<NearbyShareScheduler>
+  CreateExpirationSchedulerInstance(
+      NearbyShareExpirationScheduler::ExpirationTimeCallback
+          expiration_time_callback,
+      bool retry_failures,
+      bool require_connectivity,
+      const std::string& pref_name,
+      PrefService* pref_service,
+      NearbyShareScheduler::OnRequestCallback on_request_callback,
+      const base::Clock* clock) = 0;
+
+  virtual std::unique_ptr<NearbyShareScheduler> CreateOnDemandSchedulerInstance(
+      bool retry_failures,
+      bool require_connectivity,
+      const std::string& pref_name,
+      PrefService* pref_service,
+      NearbyShareScheduler::OnRequestCallback callback,
+      const base::Clock* clock) = 0;
+
+  virtual std::unique_ptr<NearbyShareScheduler> CreatePeriodicSchedulerInstance(
+      base::TimeDelta request_period,
+      bool retry_failures,
+      bool require_connectivity,
+      const std::string& pref_name,
+      PrefService* pref_service,
+      NearbyShareScheduler::OnRequestCallback callback,
+      const base::Clock* clock) = 0;
+
+ private:
+  static NearbyShareSchedulerFactory* test_factory_;
+};
+
+#endif  // CHROME_BROWSER_NEARBY_SHARING_SCHEDULING_NEARBY_SHARE_SCHEDULER_FACTORY_H_
diff --git a/chrome/browser/notifications/web_page_notifier_controller.cc b/chrome/browser/notifications/web_page_notifier_controller.cc
index a44d894..36986d0 100644
--- a/chrome/browser/notifications/web_page_notifier_controller.cc
+++ b/chrome/browser/notifications/web_page_notifier_controller.cc
@@ -6,6 +6,7 @@
 
 #include "ash/public/cpp/notifier_metadata.h"
 #include "base/bind.h"
+#include "base/logging.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/cancelable_task_tracker.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
diff --git a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.cc
index 4628ed1..ab8ce73 100644
--- a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.cc
@@ -153,6 +153,11 @@
   start_url_is_home_page_ =
       IsUserHomePage(browser_context_, navigation_handle->GetURL());
 
+  if (started_in_foreground) {
+    last_time_shown_ = navigation_handle->NavigationStart();
+  }
+  currently_in_foreground_ = started_in_foreground;
+
   if (!started_in_foreground) {
     was_hidden_ = true;
     return CONTINUE_OBSERVING;
@@ -227,13 +232,17 @@
   if (is_portal_)
     return STOP_OBSERVING;
 
+  base::TimeTicks current_time = base::TimeTicks::Now();
   if (!was_hidden_) {
     RecordNavigationTimingMetrics();
-    RecordPageLoadMetrics(base::TimeTicks::Now(), true /* became_hidden */);
+    RecordPageLoadMetrics(current_time, true /* became_hidden */);
     RecordTimingMetrics(timing);
     RecordInputTimingMetrics();
   }
   ReportLayoutStability();
+  // Assume that page ends on this method, as the app could be evicted right
+  // after.
+  ReportAbortMetrics(timing, current_time);
   return STOP_OBSERVING;
 }
 
@@ -242,6 +251,10 @@
   if (is_portal_)
     return CONTINUE_OBSERVING;
 
+  if (currently_in_foreground_ && !last_time_shown_.is_null()) {
+    total_foreground_duration_ += base::TimeTicks::Now() - last_time_shown_;
+  }
+  currently_in_foreground_ = false;
   if (!was_hidden_) {
     RecordNavigationTimingMetrics();
     RecordPageLoadMetrics(base::TimeTicks() /* no app_background_time */,
@@ -253,6 +266,16 @@
   return CONTINUE_OBSERVING;
 }
 
+UkmPageLoadMetricsObserver::ObservePolicy
+UkmPageLoadMetricsObserver::OnShown() {
+  if (is_portal_)
+    return CONTINUE_OBSERVING;
+
+  currently_in_foreground_ = true;
+  last_time_shown_ = base::TimeTicks::Now();
+  return CONTINUE_OBSERVING;
+}
+
 void UkmPageLoadMetricsObserver::OnFailedProvisionalLoad(
     const page_load_metrics::FailedProvisionalLoadInfo& failed_load_info) {
   if (is_portal_)
@@ -280,14 +303,16 @@
   if (is_portal_)
     return;
 
+  base::TimeTicks current_time = base::TimeTicks::Now();
   if (!was_hidden_) {
     RecordNavigationTimingMetrics();
-    RecordPageLoadMetrics(base::TimeTicks() /* no app_background_time */,
+    RecordPageLoadMetrics(current_time /* no app_background_time */,
                           false /* became_hidden */);
     RecordTimingMetrics(timing);
     RecordInputTimingMetrics();
   }
   ReportLayoutStability();
+  ReportAbortMetrics(timing, current_time);
 }
 
 void UkmPageLoadMetricsObserver::OnResourceDataUseObserved(
@@ -844,6 +869,30 @@
   }
 }
 
+void UkmPageLoadMetricsObserver::ReportAbortMetrics(
+    const page_load_metrics::mojom::PageLoadTiming& timing,
+    base::TimeTicks page_end_time) {
+  PageLoadType page_load_type = PageLoadType::kNeverForegrounded;
+  if (page_load_metrics::WasInForeground(GetDelegate())) {
+    page_load_type = timing.paint_timing->first_contentful_paint.has_value()
+                         ? PageLoadType::kReachedFCP
+                         : PageLoadType::kAborted;
+  }
+  if (currently_in_foreground_ && !last_time_shown_.is_null()) {
+    total_foreground_duration_ += page_end_time - last_time_shown_;
+  }
+  UMA_HISTOGRAM_ENUMERATION("PageLoad.Experimental.PageLoadType",
+                            page_load_type);
+  PAGE_LOAD_LONG_HISTOGRAM("PageLoad.Experimental.TotalForegroundDuration",
+                           total_foreground_duration_);
+  ukm::builders::PageLoad(GetDelegate().GetSourceId())
+      .SetExperimental_PageLoadType(static_cast<int>(page_load_type))
+      .SetExperimental_TotalForegroundDuration(
+          ukm::GetExponentialBucketMinForUserTiming(
+              total_foreground_duration_.InMilliseconds()))
+      .Record(ukm::UkmRecorder::Get());
+}
+
 void UkmPageLoadMetricsObserver::RecordInputTimingMetrics() {
   ukm::builders::PageLoad(GetDelegate().GetSourceId())
       .SetInteractiveTiming_NumInputEvents(
diff --git a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.h b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.h
index 959d82e..46506633 100644
--- a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.h
+++ b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.h
@@ -28,6 +28,20 @@
 }
 }  // namespace ukm
 
+// This enum represents the type of page load: abort, non-abort, or neither.
+// A page is of type NEVER_FOREGROUND if it was never in the foreground.
+// A page is of type ABORT if it was in the foreground at some point but did not
+// reach FCP. A page is of type REACHED_FCP if it was in the foreground at some
+// point and reached FCP. These values are persisted to logs. Entries should not
+// be renumbered and numeric values should never be reused. For any additions,
+// also update the corresponding enum in enums.xml.
+enum class PageLoadType {
+  kNeverForegrounded = 0,
+  kAborted = 1,
+  kReachedFCP = 2,
+  kMaxValue = kReachedFCP,
+};
+
 // If URL-Keyed-Metrics (UKM) is enabled in the system, this is used to
 // populate it with top-level page-load metrics.
 class UkmPageLoadMetricsObserver
@@ -61,6 +75,8 @@
   ObservePolicy OnHidden(
       const page_load_metrics::mojom::PageLoadTiming& timing) override;
 
+  ObservePolicy OnShown() override;
+
   void OnFailedProvisionalLoad(
       const page_load_metrics::FailedProvisionalLoadInfo& failed_load_info)
       override;
@@ -126,6 +142,10 @@
 
   void ReportLayoutStability();
 
+  void ReportAbortMetrics(
+      const page_load_metrics::mojom::PageLoadTiming& timing,
+      base::TimeTicks page_end_time);
+
   void RecordInputTimingMetrics();
 
   // Report throughput to Ukm.
@@ -230,6 +250,12 @@
 
   bool font_preload_started_before_rendering_observed_ = false;
 
+  bool currently_in_foreground_ = false;
+  // The last time the page became foregrounded, or navigation start if the page
+  // started in the foreground and has not been backgrounded.
+  base::TimeTicks last_time_shown_;
+  base::TimeDelta total_foreground_duration_;
+
   // The connection info for the committed URL.
   base::Optional<net::HttpResponseInfo::ConnectionInfo> connection_info_;
 
diff --git a/chrome/browser/paint_preview/android/java/src/org/chromium/chrome/browser/paint_preview/TabbedPaintPreviewPlayer.java b/chrome/browser/paint_preview/android/java/src/org/chromium/chrome/browser/paint_preview/TabbedPaintPreviewPlayer.java
index 83ce6e4..a592917b 100644
--- a/chrome/browser/paint_preview/android/java/src/org/chromium/chrome/browser/paint_preview/TabbedPaintPreviewPlayer.java
+++ b/chrome/browser/paint_preview/android/java/src/org/chromium/chrome/browser/paint_preview/TabbedPaintPreviewPlayer.java
@@ -9,6 +9,7 @@
 import androidx.annotation.Nullable;
 
 import org.chromium.base.UserData;
+import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.browser.paint_preview.services.PaintPreviewTabService;
 import org.chromium.chrome.browser.paint_preview.services.PaintPreviewTabServiceFactory;
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
@@ -100,6 +101,7 @@
         mTab.getTabViewManager().removeTabViewProvider(this);
         mPlayerManager.destroy();
         mPlayerManager = null;
+        RecordUserAction.record("PaintPreview.TabbedPlayer.Removed");
     }
 
     public boolean isShowing() {
diff --git a/chrome/browser/payments/android/service_worker_payment_app_bridge.cc b/chrome/browser/payments/android/service_worker_payment_app_bridge.cc
index d1cd578..ac949fc 100644
--- a/chrome/browser/payments/android/service_worker_payment_app_bridge.cc
+++ b/chrome/browser/payments/android/service_worker_payment_app_bridge.cc
@@ -24,6 +24,7 @@
 #include "components/payments/content/service_worker_payment_app_finder.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/installed_payment_apps_finder.h"
 #include "content/public/browser/payment_app_provider.h"
 #include "content/public/browser/web_contents.h"
 #include "services/metrics/public/cpp/ukm_recorder.h"
@@ -64,7 +65,7 @@
 
 void OnHasServiceWorkerPaymentAppsResponse(
     const JavaRef<jobject>& jcallback,
-    content::PaymentAppProvider::PaymentApps apps) {
+    content::InstalledPaymentAppsFinder::PaymentApps apps) {
   JNIEnv* env = AttachCurrentThread();
 
   Java_ServiceWorkerPaymentAppBridge_onHasServiceWorkerPaymentApps(
@@ -73,7 +74,7 @@
 
 void OnGetServiceWorkerPaymentAppsInfo(
     const JavaRef<jobject>& jcallback,
-    content::PaymentAppProvider::PaymentApps apps) {
+    content::InstalledPaymentAppsFinder::PaymentApps apps) {
   JNIEnv* env = AttachCurrentThread();
 
   base::android::ScopedJavaLocalRef<jobject> jappsInfo =
@@ -100,19 +101,21 @@
     const JavaParamRef<jobject>& jcallback) {
   // Checks whether there is a installed service worker payment app through
   // GetAllPaymentApps.
-  content::PaymentAppProvider::GetInstance()->GetAllPaymentApps(
-      ProfileManager::GetActiveUserProfile(),
-      base::BindOnce(&OnHasServiceWorkerPaymentAppsResponse,
-                     ScopedJavaGlobalRef<jobject>(env, jcallback)));
+  content::InstalledPaymentAppsFinder::GetInstance(
+      ProfileManager::GetActiveUserProfile())
+      ->GetAllPaymentApps(
+          base::BindOnce(&OnHasServiceWorkerPaymentAppsResponse,
+                         ScopedJavaGlobalRef<jobject>(env, jcallback)));
 }
 
 static void JNI_ServiceWorkerPaymentAppBridge_GetServiceWorkerPaymentAppsInfo(
     JNIEnv* env,
     const JavaParamRef<jobject>& jcallback) {
-  content::PaymentAppProvider::GetInstance()->GetAllPaymentApps(
-      ProfileManager::GetActiveUserProfile(),
-      base::BindOnce(&OnGetServiceWorkerPaymentAppsInfo,
-                     ScopedJavaGlobalRef<jobject>(env, jcallback)));
+  content::InstalledPaymentAppsFinder::GetInstance(
+      ProfileManager::GetActiveUserProfile())
+      ->GetAllPaymentApps(
+          base::BindOnce(&OnGetServiceWorkerPaymentAppsInfo,
+                         ScopedJavaGlobalRef<jobject>(env, jcallback)));
 }
 
 static void JNI_ServiceWorkerPaymentAppBridge_OnClosingPaymentAppWindow(
diff --git a/chrome/browser/payments/manifest_verifier_browsertest.cc b/chrome/browser/payments/manifest_verifier_browsertest.cc
index 813b518..2dcf3aa 100644
--- a/chrome/browser/payments/manifest_verifier_browsertest.cc
+++ b/chrome/browser/payments/manifest_verifier_browsertest.cc
@@ -47,7 +47,7 @@
 
   // Runs the verifier on the |apps| and blocks until the verifier has finished
   // using all resources.
-  void Verify(content::PaymentAppProvider::PaymentApps apps) {
+  void Verify(content::InstalledPaymentAppsFinder::PaymentApps apps) {
     content::WebContents* web_contents =
         browser()->tab_strip_model()->GetActiveWebContents();
     content::BrowserContext* context = web_contents->GetBrowserContext();
@@ -75,7 +75,8 @@
   }
 
   // Returns the apps that have been verified by the Verify() method.
-  const content::PaymentAppProvider::PaymentApps& verified_apps() const {
+  const content::InstalledPaymentAppsFinder::PaymentApps& verified_apps()
+      const {
     return verified_apps_;
   }
 
@@ -102,8 +103,9 @@
  private:
   // Called by the verifier upon completed verification. These |apps| have only
   // valid payment methods.
-  void OnPaymentAppsVerified(content::PaymentAppProvider::PaymentApps apps,
-                             const std::string& error_message) {
+  void OnPaymentAppsVerified(
+      content::InstalledPaymentAppsFinder::PaymentApps apps,
+      const std::string& error_message) {
     verified_apps_ = std::move(apps);
     error_message_ = error_message;
   }
@@ -112,7 +114,7 @@
   std::unique_ptr<net::EmbeddedTestServer> https_server_;
 
   // The apps that have been verified by the Verify() method.
-  content::PaymentAppProvider::PaymentApps verified_apps_;
+  content::InstalledPaymentAppsFinder::PaymentApps verified_apps_;
 
   std::string error_message_;
 
@@ -123,7 +125,7 @@
 // handlers.
 IN_PROC_BROWSER_TEST_F(ManifestVerifierBrowserTest, NoApps) {
   {
-    Verify(content::PaymentAppProvider::PaymentApps());
+    Verify(content::InstalledPaymentAppsFinder::PaymentApps());
 
     EXPECT_TRUE(verified_apps().empty());
     EXPECT_TRUE(error_message().empty()) << error_message();
@@ -131,7 +133,7 @@
 
   // Repeat verifications should have identical results.
   {
-    Verify(content::PaymentAppProvider::PaymentApps());
+    Verify(content::InstalledPaymentAppsFinder::PaymentApps());
 
     EXPECT_TRUE(verified_apps().empty());
     EXPECT_TRUE(error_message().empty()) << error_message();
@@ -141,7 +143,7 @@
 // A payment handler without any payment method names is not valid.
 IN_PROC_BROWSER_TEST_F(ManifestVerifierBrowserTest, NoMethods) {
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://bobpay.com/webpay");
 
@@ -153,7 +155,7 @@
 
   // Repeat verifications should have identical results.
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://bobpay.com/webpay");
 
@@ -168,7 +170,7 @@
 IN_PROC_BROWSER_TEST_F(ManifestVerifierBrowserTest,
                        UnknownPaymentMethodNameIsRemoved) {
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://bobpay.com/webpay");
     apps[0]->enabled_methods.push_back("unknown");
@@ -181,7 +183,7 @@
 
   // Repeat verifications should have identical results.
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://bobpay.com/webpay");
     apps[0]->enabled_methods.push_back("unknown");
@@ -196,7 +198,7 @@
 // A payment handler with "basic-card" payment method name is valid.
 IN_PROC_BROWSER_TEST_F(ManifestVerifierBrowserTest, KnownPaymentMethodName) {
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://bobpay.com/webpay");
     apps[0]->enabled_methods.push_back("basic-card");
@@ -210,7 +212,7 @@
 
   // Repeat verifications should have identical results.
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://bobpay.com/webpay");
     apps[0]->enabled_methods.push_back("basic-card");
@@ -228,7 +230,7 @@
 IN_PROC_BROWSER_TEST_F(ManifestVerifierBrowserTest,
                        TwoKnownPaymentMethodNames) {
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://bobpay.com/webpay");
     apps[0]->enabled_methods.push_back("basic-card");
@@ -244,7 +246,7 @@
 
   // Repeat verifications should have identical results.
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://bobpay.com/webpay");
     apps[0]->enabled_methods.push_back("basic-card");
@@ -263,7 +265,7 @@
 IN_PROC_BROWSER_TEST_F(ManifestVerifierBrowserTest,
                        TwoAppsWithKnownPaymentMethodNames) {
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://bobpay.com/webpay");
     apps[0]->enabled_methods.push_back("basic-card");
@@ -281,7 +283,7 @@
 
   // Repeat verifications should have identical results.
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://bobpay.com/webpay");
     apps[0]->enabled_methods.push_back("basic-card");
@@ -304,7 +306,7 @@
 IN_PROC_BROWSER_TEST_F(ManifestVerifierBrowserTest,
                        BobPayHandlerCanNotUseMethodWithOriginWildcard) {
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://bobpay.com/webpay");
     apps[0]->enabled_methods.push_back("https://frankpay.com/webpay");
@@ -317,7 +319,7 @@
 
   // Repeat verifications should have identical results.
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://bobpay.com/webpay");
     apps[0]->enabled_methods.push_back("https://frankpay.com/webpay");
@@ -335,7 +337,7 @@
 IN_PROC_BROWSER_TEST_F(ManifestVerifierBrowserTest,
                        Handler404CanNotUseMethodWithOriginWildcard) {
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://404.com/webpay");
     apps[0]->enabled_methods.push_back("https://frankpay.com/webpay");
@@ -348,7 +350,7 @@
 
   // Repeat verifications should have identical results.
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://404.com/webpay");
     apps[0]->enabled_methods.push_back("https://frankpay.com/webpay");
@@ -365,7 +367,7 @@
 IN_PROC_BROWSER_TEST_F(ManifestVerifierBrowserTest,
                        BobPayCanUseAnyMethodOnOwnOrigin) {
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://bobpay.com/anything/here");
     apps[0]->enabled_methods.push_back(
@@ -381,7 +383,7 @@
 
   // Repeat verifications should have identical results.
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://bobpay.com/anything/here");
     apps[0]->enabled_methods.push_back(
@@ -401,7 +403,7 @@
 IN_PROC_BROWSER_TEST_F(ManifestVerifierBrowserTest,
                        Handler404CanUseAnyMethodOnOwnOrigin) {
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://404.com/anything/here");
     apps[0]->enabled_methods.push_back(
@@ -417,7 +419,7 @@
 
   // Repeat verifications should have identical results.
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://404.com/anything/here");
     apps[0]->enabled_methods.push_back(
@@ -439,7 +441,7 @@
 // cannot use these payment methods, however.
 IN_PROC_BROWSER_TEST_F(ManifestVerifierBrowserTest, OneSupportedOrigin) {
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://alicepay.com/webpay");
     apps[0]->enabled_methods.push_back("https://georgepay.com/webpay");
@@ -460,7 +462,7 @@
 
   // Repeat verifications should have identical results.
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://alicepay.com/webpay");
     apps[0]->enabled_methods.push_back("https://georgepay.com/webpay");
@@ -485,7 +487,7 @@
 // and different-origin URL payment method name.
 IN_PROC_BROWSER_TEST_F(ManifestVerifierBrowserTest, ThreeTypesOfMethods) {
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://alicepay.com/webpay");
     apps[0]->enabled_methods.push_back("basic-card");
@@ -504,7 +506,7 @@
 
   // Repeat verifications should have identical results.
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://alicepay.com/webpay");
     apps[0]->enabled_methods.push_back("basic-card");
@@ -531,7 +533,7 @@
       "Unable to download payment manifest "
       "\"https://127.0.0.1:\\d+/404.test/webpay\".";
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://bobpay.test/webpay");
     apps[0]->enabled_methods.push_back("https://404.test/webpay");
@@ -546,7 +548,7 @@
 
   // Repeat verifications should have identical results.
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://bobpay.test/webpay");
     apps[0]->enabled_methods.push_back("https://404.test/webpay");
@@ -570,7 +572,7 @@
       "Unable to download payment manifest "
       "\"https://127.0.0.1:\\d+/404(aswell)?.test/webpay\".";
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://bobpay.test/webpay");
     apps[0]->enabled_methods.push_back("https://404.test/webpay");
@@ -586,7 +588,7 @@
 
   // Repeat verifications should have identical results.
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://bobpay.test/webpay");
     apps[0]->enabled_methods.push_back("https://404.test/webpay");
@@ -605,7 +607,7 @@
 IN_PROC_BROWSER_TEST_F(ManifestVerifierBrowserTest,
                        AllKnownPaymentMethodNames) {
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://bobpay.com/webpay");
     apps[0]->enabled_methods.push_back("basic-card");
@@ -627,7 +629,7 @@
 
   // Repeat verifications should have identical results.
   {
-    content::PaymentAppProvider::PaymentApps apps;
+    content::InstalledPaymentAppsFinder::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
     apps[0]->scope = GURL("https://bobpay.com/webpay");
     apps[0]->enabled_methods.push_back("basic-card");
diff --git a/chrome/browser/payments/service_worker_payment_app_finder_browsertest.cc b/chrome/browser/payments/service_worker_payment_app_finder_browsertest.cc
index cb794b0..0162a3e 100644
--- a/chrome/browser/payments/service_worker_payment_app_finder_browsertest.cc
+++ b/chrome/browser/payments/service_worker_payment_app_finder_browsertest.cc
@@ -206,7 +206,9 @@
 
   // Returns the installed apps that have been found in
   // GetAllPaymentAppsForMethods().
-  const content::PaymentAppProvider::PaymentApps& apps() const { return apps_; }
+  const content::InstalledPaymentAppsFinder::PaymentApps& apps() const {
+    return apps_;
+  }
 
   // Returns the installable apps that have been found in
   // GetAllPaymentAppsForMethods().
@@ -266,7 +268,7 @@
   // Called by the factory upon completed app lookup. These |apps| have only
   // valid payment methods.
   void OnGotAllPaymentApps(
-      content::PaymentAppProvider::PaymentApps apps,
+      content::InstalledPaymentAppsFinder::PaymentApps apps,
       ServiceWorkerPaymentAppFinder::InstallablePaymentApps installable_apps,
       const std::string& error_message) {
     apps_ = std::move(apps);
@@ -346,7 +348,7 @@
 
   // The installed apps that have been found by the factory in
   // GetAllPaymentAppsForMethods() method.
-  content::PaymentAppProvider::PaymentApps apps_;
+  content::InstalledPaymentAppsFinder::PaymentApps apps_;
 
   // The installable apps that have been found by the factory in
   // GetAllPaymentAppsForMethods() method.
diff --git a/chrome/browser/performance_manager/mechanisms/page_discarder_browsertest.cc b/chrome/browser/performance_manager/mechanisms/page_discarder_browsertest.cc
new file mode 100644
index 0000000..f31c4173
--- /dev/null
+++ b/chrome/browser/performance_manager/mechanisms/page_discarder_browsertest.cc
@@ -0,0 +1,61 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/performance_manager/mechanisms/page_discarder.h"
+
+#include "base/callback_forward.h"
+#include "base/run_loop.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "components/performance_manager/public/performance_manager.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/page_navigator.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/test_utils.h"
+#include "url/gurl.h"
+
+namespace performance_manager {
+
+using PageDiscarderBrowserTest = InProcessBrowserTest;
+
+IN_PROC_BROWSER_TEST_F(PageDiscarderBrowserTest, DiscardPageNode) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  content::WindowedNotificationObserver load(
+      content::NOTIFICATION_NAV_ENTRY_COMMITTED,
+      content::NotificationService::AllSources());
+  content::OpenURLParams page(
+      embedded_test_server()->GetURL("a.com", "/foo.html"), content::Referrer(),
+      WindowOpenDisposition::NEW_BACKGROUND_TAB, ui::PAGE_TRANSITION_TYPED,
+      false);
+  auto* contents = browser()->OpenURL(page);
+  load.Wait();
+
+  base::RunLoop run_loop;
+  auto quit_closure = run_loop.QuitClosure();
+  PerformanceManager::CallOnGraph(
+      FROM_HERE,
+      base::BindOnce(
+          [](base::WeakPtr<PageNode> page_node,
+             base::OnceClosure quit_closure) {
+            EXPECT_TRUE(page_node);
+            mechanism::PageDiscarder discarder;
+            discarder.DiscardPageNode(
+                page_node.get(),
+                base::BindOnce(
+                    [](base::OnceClosure quit_closure, bool success) {
+                      EXPECT_TRUE(success);
+                      std::move(quit_closure).Run();
+                    },
+                    std::move(quit_closure)));
+          },
+          PerformanceManager::GetPageNodeForWebContents(contents),
+          std::move(quit_closure)));
+  run_loop.Run();
+
+  auto* new_contents = browser()->tab_strip_model()->GetWebContentsAt(1);
+  EXPECT_TRUE(new_contents->WasDiscarded());
+}
+
+}  // namespace performance_manager
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index b4f2927..1624278 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -61,6 +61,7 @@
 #include "components/metrics/metrics_pref_names.h"
 #include "components/network_time/network_time_pref_names.h"
 #include "components/ntp_snippets/pref_names.h"
+#include "components/omnibox/browser/omnibox_prefs.h"
 #include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/payments/core/payment_prefs.h"
 #include "components/policy/core/browser/configuration_policy_handler.h"
@@ -1319,6 +1320,9 @@
   { key::kUserAgentClientHintsEnabled,
     policy::policy_prefs::kUserAgentClientHintsEnabled,
     base::Value::Type::BOOLEAN },
+  { key::kShowFullUrlsInAddressBar,
+    omnibox::kPreventUrlElisionsInOmnibox,
+    base::Value::Type::BOOLEAN },
 };
 // clang-format on
 
@@ -1802,9 +1806,13 @@
       std::make_unique<extensions::ExtensionListPolicyHandler>(
           key::kExtensionInstallAllowlist,
           extensions::pref_names::kInstallAllowList, false)));
-  handlers->AddHandler(std::make_unique<extensions::ExtensionListPolicyHandler>(
-      key::kExtensionInstallBlacklist, extensions::pref_names::kInstallDenyList,
-      true));
+  handlers->AddHandler(std::make_unique<policy::SimpleDeprecatingPolicyHandler>(
+      std::make_unique<extensions::ExtensionListPolicyHandler>(
+          key::kExtensionInstallBlacklist,
+          extensions::pref_names::kInstallDenyList, true),
+      std::make_unique<extensions::ExtensionListPolicyHandler>(
+          key::kExtensionInstallBlocklist,
+          extensions::pref_names::kInstallDenyList, true)));
   handlers->AddHandler(
       std::make_unique<extensions::ExtensionInstallForcelistPolicyHandler>());
   handlers->AddHandler(
diff --git a/chrome/browser/policy/extension_policy_browsertest.cc b/chrome/browser/policy/extension_policy_browsertest.cc
index 3aa1aa9..59dd36f 100644
--- a/chrome/browser/policy/extension_policy_browsertest.cc
+++ b/chrome/browser/policy/extension_policy_browsertest.cc
@@ -376,13 +376,13 @@
 }  // namespace
 
 #if defined(OS_CHROMEOS)
-// Check that component extension can't be blacklisted, besides the camera app
+// Check that component extension can't be blocklisted, besides the camera app
 // that can be disabled by extension policy. This is a temporary solution until
 // there's a dedicated policy to disable the camera, at which point the special
 // check should be removed.
 // TODO(http://crbug.com/1002935)
 IN_PROC_BROWSER_TEST_F(ExtensionPolicyTest,
-                       ExtensionInstallBlacklistComponentApps) {
+                       ExtensionInstallBlocklistComponentApps) {
   extensions::ExtensionPrefs* extension_prefs =
       extensions::ExtensionPrefs::Get(browser()->profile());
 
@@ -398,14 +398,14 @@
       registry->enabled_extensions().GetByID(extensions::kWebStoreAppId));
   const size_t enabled_count = registry->enabled_extensions().size();
 
-  // Verify that only Camera app can be blacklisted.
-  base::ListValue blacklist;
-  blacklist.AppendString(extension_misc::kCameraAppId);
-  blacklist.AppendString(extensions::kWebStoreAppId);
+  // Verify that only Camera app can be blocklisted.
+  base::ListValue blocklist;
+  blocklist.AppendString(extension_misc::kCameraAppId);
+  blocklist.AppendString(extensions::kWebStoreAppId);
   PolicyMap policies;
-  policies.Set(key::kExtensionInstallBlacklist, POLICY_LEVEL_MANDATORY,
+  policies.Set(key::kExtensionInstallBlocklist, POLICY_LEVEL_MANDATORY,
                POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
-               blacklist.CreateDeepCopy(), nullptr);
+               blocklist.CreateDeepCopy(), nullptr);
   UpdateProviderPolicy(policies);
 
   ASSERT_FALSE(
@@ -420,22 +420,22 @@
   EXPECT_EQ(enabled_count - 1, registry->enabled_extensions().size());
 }
 
-// Ensures that OS Settings can't be disabled by ExtensionInstallBlacklist
+// Ensures that OS Settings can't be disabled by ExtensionInstallBlocklist
 // policy.
 IN_PROC_BROWSER_TEST_F(ExtensionPolicyTest,
-                       ExtensionInstallBlacklistOsSettings) {
+                       ExtensionInstallBlocklistOsSettings) {
   extensions::ExtensionRegistry* registry = extension_registry();
   const extensions::Extension* bookmark_app = InstallOSSettings();
   ASSERT_TRUE(bookmark_app);
   ASSERT_TRUE(registry->enabled_extensions().GetByID(
       chromeos::default_web_apps::kOsSettingsAppId));
 
-  base::ListValue blacklist;
-  blacklist.AppendString(chromeos::default_web_apps::kOsSettingsAppId);
+  base::ListValue blocklist;
+  blocklist.AppendString(chromeos::default_web_apps::kOsSettingsAppId);
   PolicyMap policies;
-  policies.Set(key::kExtensionInstallBlacklist, POLICY_LEVEL_MANDATORY,
+  policies.Set(key::kExtensionInstallBlocklist, POLICY_LEVEL_MANDATORY,
                POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
-               blacklist.CreateDeepCopy(), nullptr);
+               blocklist.CreateDeepCopy(), nullptr);
   UpdateProviderPolicy(policies);
 
   extensions::ExtensionService* service = extension_service();
@@ -445,22 +445,22 @@
 #endif  // defined(OS_CHROMEOS)
 
 IN_PROC_BROWSER_TEST_F(ExtensionPolicyTest,
-                       ExtensionInstallBlacklistSelective) {
-  // Verifies that blacklisted extensions can't be installed.
+                       ExtensionInstallBlocklistSelective) {
+  // Verifies that blocklisted extensions can't be installed.
   extensions::ExtensionRegistry* registry = extension_registry();
   ASSERT_FALSE(registry->GetExtensionById(
       kGoodCrxId, extensions::ExtensionRegistry::EVERYTHING));
   ASSERT_FALSE(registry->GetExtensionById(
       kSimpleWithIconCrxId, extensions::ExtensionRegistry::EVERYTHING));
-  base::ListValue blacklist;
-  blacklist.AppendString(kGoodCrxId);
+  base::ListValue blocklist;
+  blocklist.AppendString(kGoodCrxId);
   PolicyMap policies;
-  policies.Set(key::kExtensionInstallBlacklist, POLICY_LEVEL_MANDATORY,
+  policies.Set(key::kExtensionInstallBlocklist, POLICY_LEVEL_MANDATORY,
                POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
-               blacklist.CreateDeepCopy(), nullptr);
+               blocklist.CreateDeepCopy(), nullptr);
   UpdateProviderPolicy(policies);
 
-  // "good.crx" is blacklisted.
+  // "good.crx" is blocklisted.
   EXPECT_FALSE(InstallExtension(kGoodCrxName));
   EXPECT_FALSE(registry->GetExtensionById(
       kGoodCrxId, extensions::ExtensionRegistry::EVERYTHING));
@@ -474,12 +474,12 @@
             registry->enabled_extensions().GetByID(kSimpleWithIconCrxId));
 }
 
-// Ensure that bookmark apps are not blocked by the ExtensionInstallBlacklist
+// Ensure that bookmark apps are not blocked by the ExtensionInstallBlocklist
 // policy.
-// Also see ExtensionInstallBlacklist_WebApp counterpart.
+// Also see ExtensionInstallBlocklist_WebApp counterpart.
 // TODO(https://crbug.com/1079435): Delete this test for bookmark apps removal.
 IN_PROC_BROWSER_TEST_F(ExtensionPolicyTest,
-                       ExtensionInstallBlacklist_BookmarkApp) {
+                       ExtensionInstallBlocklist_BookmarkApp) {
   const extensions::Extension* bookmark_app = InstallBookmarkApp();
   ASSERT_TRUE(bookmark_app);
   EXPECT_TRUE(InstallExtension(kGoodCrxName));
@@ -488,9 +488,9 @@
   EXPECT_TRUE(service->IsExtensionEnabled(kGoodCrxId));
   EXPECT_TRUE(service->IsExtensionEnabled(bookmark_app->id()));
 
-  // Now set ExtensionInstallBlacklist policy to block all extensions.
+  // Now set ExtensionInstallBlocklist policy to block all extensions.
   PolicyMap policies;
-  policies.Set(key::kExtensionInstallBlacklist, POLICY_LEVEL_MANDATORY,
+  policies.Set(key::kExtensionInstallBlocklist, POLICY_LEVEL_MANDATORY,
                POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
                extensions::ListBuilder().Append("*").Build(), nullptr);
   UpdateProviderPolicy(policies);
@@ -500,8 +500,8 @@
   EXPECT_TRUE(service->IsExtensionEnabled(bookmark_app->id()));
 }
 
-// Ensure that web apps are not blocked by the ExtensionInstallBlacklist policy.
-IN_PROC_BROWSER_TEST_F(ExtensionPolicyTest, ExtensionInstallBlacklist_WebApp) {
+// Ensure that web apps are not blocked by the ExtensionInstallBlocklist policy.
+IN_PROC_BROWSER_TEST_F(ExtensionPolicyTest, ExtensionInstallBlocklist_WebApp) {
   web_app::AppId web_app_id = InstallWebApp();
   EXPECT_TRUE(InstallExtension(kGoodCrxName));
 
@@ -510,9 +510,9 @@
   extensions::ExtensionService* service = extension_service();
   EXPECT_TRUE(service->IsExtensionEnabled(kGoodCrxId));
 
-  // Now set ExtensionInstallBlacklist policy to block all extensions.
+  // Now set ExtensionInstallBlocklist policy to block all extensions.
   PolicyMap policies;
-  policies.Set(key::kExtensionInstallBlacklist, POLICY_LEVEL_MANDATORY,
+  policies.Set(key::kExtensionInstallBlocklist, POLICY_LEVEL_MANDATORY,
                POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
                extensions::ListBuilder().Append("*").Build(), nullptr);
   UpdateProviderPolicy(policies);
@@ -523,7 +523,7 @@
 }
 
 // Ensure that when INSTALLATION_REMOVED is set
-// that blacklisted extensions are removed from the device.
+// that blocklisted extensions are removed from the device.
 IN_PROC_BROWSER_TEST_F(ExtensionPolicyTest, ExtensionInstallRemovedPolicy) {
   EXPECT_TRUE(InstallExtension(kGoodCrxName));
 
@@ -547,7 +547,7 @@
 }
 
 // Ensure that when INSTALLATION_REMOVED is set for wildcard
-// that blacklisted extensions are removed from the device.
+// that blocklisted extensions are removed from the device.
 IN_PROC_BROWSER_TEST_F(ExtensionPolicyTest, ExtensionWildcardRemovedPolicy) {
   EXPECT_TRUE(InstallExtension(kGoodCrxName));
 
@@ -596,7 +596,7 @@
   EXPECT_TRUE(service->IsExtensionEnabled(bookmark_app->id()));
 }
 
-// Ensure that web apps are not blocked by the ExtensionInstallBlacklist policy.
+// Ensure that web apps are not blocked by the ExtensionInstallBlocklist policy.
 IN_PROC_BROWSER_TEST_F(ExtensionPolicyTest, ExtensionAllowedTypes_WebApp) {
   web_app::AppId web_app_id = InstallWebApp();
   EXPECT_TRUE(InstallExtension(kGoodCrxName));
@@ -673,7 +673,7 @@
   EXPECT_TRUE(service->IsExtensionEnabled(bookmark_app->id()));
 }
 
-// Ensure that web apps are not blocked by the ExtensionInstallBlacklist policy.
+// Ensure that web apps are not blocked by the ExtensionInstallBlocklist policy.
 IN_PROC_BROWSER_TEST_F(ExtensionPolicyTest, ExtensionSettings_WebApp) {
   web_app::AppId web_app_id = InstallWebApp();
   EXPECT_TRUE(InstallExtension(kGoodCrxName));
@@ -702,27 +702,27 @@
 
 // Flaky on windows; http://crbug.com/307994.
 #if defined(OS_WIN)
-#define MAYBE_ExtensionInstallBlacklistWildcard \
-  DISABLED_ExtensionInstallBlacklistWildcard
+#define MAYBE_ExtensionInstallBlocklistWildcard \
+  DISABLED_ExtensionInstallBlocklistWildcard
 #else
-#define MAYBE_ExtensionInstallBlacklistWildcard \
-  ExtensionInstallBlacklistWildcard
+#define MAYBE_ExtensionInstallBlocklistWildcard \
+  ExtensionInstallBlocklistWildcard
 #endif
 IN_PROC_BROWSER_TEST_F(ExtensionPolicyTest,
-                       MAYBE_ExtensionInstallBlacklistWildcard) {
-  // Verify that a wildcard blacklist takes effect.
+                       MAYBE_ExtensionInstallBlocklistWildcard) {
+  // Verify that a wildcard blocklist takes effect.
   EXPECT_TRUE(InstallExtension(kSimpleWithIconCrxName));
   extensions::ExtensionService* service = extension_service();
   extensions::ExtensionRegistry* registry = extension_registry();
   ASSERT_FALSE(registry->GetExtensionById(
       kGoodCrxId, extensions::ExtensionRegistry::EVERYTHING));
   ASSERT_TRUE(registry->enabled_extensions().GetByID(kSimpleWithIconCrxId));
-  base::ListValue blacklist;
-  blacklist.AppendString("*");
+  base::ListValue blocklist;
+  blocklist.AppendString("*");
   PolicyMap policies;
-  policies.Set(key::kExtensionInstallBlacklist, POLICY_LEVEL_MANDATORY,
+  policies.Set(key::kExtensionInstallBlocklist, POLICY_LEVEL_MANDATORY,
                POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
-               blacklist.CreateDeepCopy(), nullptr);
+               blocklist.CreateDeepCopy(), nullptr);
   UpdateProviderPolicy(policies);
 
   // "simple_with_icon" should be disabled.
@@ -741,8 +741,8 @@
 }
 
 IN_PROC_BROWSER_TEST_F(ExtensionPolicyTest,
-                       ExtensionInstallBlacklistSharedModules) {
-  // Verifies that shared_modules are not affected by the blacklist.
+                       ExtensionInstallBlocklistSharedModules) {
+  // Verifies that shared_modules are not affected by the blocklist.
 
   base::FilePath base_path;
   GetTestDataDirectory(&base_path);
@@ -779,15 +779,15 @@
   extension_test_util::SetGalleryUpdateURL(update_xml_url);
   ui_test_utils::NavigateToURL(browser(), update_xml_url);
 
-  // Blacklist "*" but force-install the importer extension. The shared module
+  // Blocklist "*" but force-install the importer extension. The shared module
   // should be automatically installed too.
-  base::ListValue blacklist;
-  blacklist.AppendString("*");
+  base::ListValue blocklist;
+  blocklist.AppendString("*");
   PolicyMap policies;
   AddExtensionToForceList(&policies, kImporterId, update_xml_url);
-  policies.Set(key::kExtensionInstallBlacklist, POLICY_LEVEL_MANDATORY,
+  policies.Set(key::kExtensionInstallBlocklist, POLICY_LEVEL_MANDATORY,
                POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
-               blacklist.CreateDeepCopy(), nullptr);
+               blocklist.CreateDeepCopy(), nullptr);
 
   extensions::TestExtensionRegistryObserver observe_importer(registry,
                                                              kImporterId);
@@ -822,25 +822,25 @@
 }
 
 IN_PROC_BROWSER_TEST_F(ExtensionPolicyTest, ExtensionInstallAllowlist) {
-  // Verifies that the allowlist can open exceptions to the blacklist.
+  // Verifies that the allowlist can open exceptions to the blocklist.
   extensions::ExtensionRegistry* registry = extension_registry();
   ASSERT_FALSE(registry->GetExtensionById(
       kGoodCrxId, extensions::ExtensionRegistry::EVERYTHING));
   ASSERT_FALSE(registry->GetExtensionById(
       kSimpleWithIconCrxId, extensions::ExtensionRegistry::EVERYTHING));
-  base::ListValue blacklist;
-  blacklist.AppendString("*");
+  base::ListValue blocklist;
+  blocklist.AppendString("*");
   base::ListValue allowlist;
   allowlist.AppendString(kGoodCrxId);
   PolicyMap policies;
-  policies.Set(key::kExtensionInstallBlacklist, POLICY_LEVEL_MANDATORY,
+  policies.Set(key::kExtensionInstallBlocklist, POLICY_LEVEL_MANDATORY,
                POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
-               blacklist.CreateDeepCopy(), nullptr);
+               blocklist.CreateDeepCopy(), nullptr);
   policies.Set(key::kExtensionInstallAllowlist, POLICY_LEVEL_MANDATORY,
                POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
                allowlist.CreateDeepCopy(), nullptr);
   UpdateProviderPolicy(policies);
-  // "simple_with_icon.crx" is blacklisted.
+  // "simple_with_icon.crx" is blocklisted.
   EXPECT_FALSE(InstallExtension(kSimpleWithIconCrxName));
   EXPECT_FALSE(registry->GetExtensionById(
       kSimpleWithIconCrxId, extensions::ExtensionRegistry::EVERYTHING));
@@ -964,7 +964,6 @@
 
   // Explicitly re-enable the extension.
   extension_service()->EnableExtension(kGoodCrxId);
-
   // Extensions that are force-installed come from an update URL, which defaults
   // to the webstore. Use a test URL for this test with an update manifest
   // that includes "good_v1.crx".
@@ -1478,8 +1477,7 @@
     const std::string kCorruptedContent("// corrupted\n");
     ASSERT_TRUE(base::WriteFile(resource_path, kCorruptedContent));
     ASSERT_TRUE(base::DeleteFile(
-        extensions::file_util::GetComputedHashesPath(extension->path()),
-        /*recursive=*/false));
+        extensions::file_util::GetComputedHashesPath(extension->path())));
 
     service->EnableExtension(kGoodCrxId);
   }
diff --git a/chrome/browser/predictors/loading_predictor_browsertest.cc b/chrome/browser/predictors/loading_predictor_browsertest.cc
index 772c5ddc..a517d0ad 100644
--- a/chrome/browser/predictors/loading_predictor_browsertest.cc
+++ b/chrome/browser/predictors/loading_predictor_browsertest.cc
@@ -1887,7 +1887,7 @@
     expected_requests_.erase(it);
 
     // Finish if done.
-    if (expected_requests_.empty())
+    if (expected_requests_.empty() && quit_)
       std::move(quit_).Run();
   }
 
diff --git a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
index efb2675..e386cf2d 100644
--- a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
+++ b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
@@ -593,6 +593,9 @@
             "org.chromium.chrome.browser.settings.privacy."
             + "PREF_OTHER_FORMS_OF_HISTORY_DIALOG_SHOWN";
 
+    /** Stores the number of times the user has performed Safety check. */
+    public static final String SETTINGS_SAFETY_CHECK_RUN_COUNTER = "Chrome.SafetyCheck.RunCounter";
+
     public static final String SETTINGS_WEBSITE_FAILED_BUILD_VERSION =
             "ManagedSpace.FailedBuildVersion";
 
@@ -797,6 +800,7 @@
                 HOMEPAGE_USE_CHROME_NTP,
                 PROMO_IS_DISMISSED.pattern(),
                 PROMO_TIMES_SEEN.pattern(),
+                SETTINGS_SAFETY_CHECK_RUN_COUNTER,
                 TWA_DISCLOSURE_SEEN_PACKAGES
         );
         // clang-format on
diff --git a/chrome/browser/prerender/isolated/isolated_prerender_tab_helper.cc b/chrome/browser/prerender/isolated/isolated_prerender_tab_helper.cc
index bfb4ded7..9509ab0 100644
--- a/chrome/browser/prerender/isolated/isolated_prerender_tab_helper.cc
+++ b/chrome/browser/prerender/isolated/isolated_prerender_tab_helper.cc
@@ -253,6 +253,12 @@
     return;
   }
 
+  // This check is only relevant for detecting AMP pages. For this feature, AMP
+  // pages won't get sped up any so just ignore them.
+  if (navigation_handle->IsSameDocument()) {
+    return;
+  }
+
   // Don't take any actions during a prerender since it was probably triggered
   // by another instance of this class and we don't want to interfere.
   prerender::PrerenderManager* prerender_manager =
@@ -413,6 +419,13 @@
   if (!navigation_handle->IsInMainFrame()) {
     return;
   }
+
+  // This check is only relevant for detecting AMP pages. For this feature, AMP
+  // pages won't get sped up any so just ignore them.
+  if (navigation_handle->IsSameDocument()) {
+    return;
+  }
+
   if (!navigation_handle->HasCommitted()) {
     return;
   }
diff --git a/chrome/browser/prerender/isolated/isolated_prerender_tab_helper_unittest.cc b/chrome/browser/prerender/isolated/isolated_prerender_tab_helper_unittest.cc
index 74b35b8..27e31f48 100644
--- a/chrome/browser/prerender/isolated/isolated_prerender_tab_helper_unittest.cc
+++ b/chrome/browser/prerender/isolated/isolated_prerender_tab_helper_unittest.cc
@@ -197,6 +197,15 @@
 
   void NavigateSomewhere() { Navigate(GURL("https://test.com")); }
 
+  void NavigateSameDocument() {
+    content::MockNavigationHandle handle(web_contents());
+    handle.set_url(GURL("https://test.com"));
+    handle.set_is_same_document(true);
+    tab_helper_->DidStartNavigation(&handle);
+    handle.set_has_committed(true);
+    tab_helper_->DidFinishNavigation(&handle);
+  }
+
   void NavigateAndVerifyPrefetchStatus(
       const GURL& url,
       IsolatedPrerenderTabHelper::PrefetchStatus expected_status) {
@@ -864,6 +873,52 @@
   EXPECT_EQ(RequestCount(), 0);
 }
 
+TEST_F(IsolatedPrerenderTabHelperTest, IgnoreSameDocNavigations) {
+  base::HistogramTester histogram_tester;
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(features::kIsolatePrerenders);
+
+  NavigateSomewhere();
+  GURL doc_url("https://www.google.com/search?q=cats");
+  GURL prediction_url("https://www.cat-food.com/");
+  MakeNavigationPrediction(web_contents(), doc_url, {prediction_url});
+
+  network::ResourceRequest request = VerifyCommonRequestState(prediction_url);
+  MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
+                      {{"X-Testing", "Hello World"}}, kHTMLBody);
+
+  NavigateSameDocument();
+
+  EXPECT_EQ(predicted_urls_count(), 1U);
+  EXPECT_EQ(prefetch_eligible_count(), 1U);
+  EXPECT_EQ(prefetch_attempted_count(), 1U);
+  EXPECT_EQ(prefetch_successful_count(), 1U);
+  EXPECT_EQ(prefetch_total_redirect_count(), 0U);
+  EXPECT_TRUE(navigation_to_prefetch_start().has_value());
+
+  histogram_tester.ExpectUniqueSample(
+      "IsolatedPrerender.Prefetch.Mainframe.NetError", net::OK, 1);
+  histogram_tester.ExpectUniqueSample(
+      "IsolatedPrerender.Prefetch.Mainframe.RespCode", net::HTTP_OK, 1);
+  histogram_tester.ExpectUniqueSample(
+      "IsolatedPrerender.Prefetch.Mainframe.BodyLength", base::size(kHTMLBody),
+      1);
+  histogram_tester.ExpectUniqueSample(
+      "IsolatedPrerender.Prefetch.Mainframe.TotalTime", kTotalTimeDuration, 1);
+  histogram_tester.ExpectUniqueSample(
+      "IsolatedPrerender.Prefetch.Mainframe.ConnectTime", kConnectTimeDuration,
+      1);
+
+  NavigateAndVerifyPrefetchStatus(
+      prediction_url,
+      IsolatedPrerenderTabHelper::PrefetchStatus::kPrefetchSuccessful);
+  EXPECT_EQ(after_srp_prefetch_eligible_count(), 1U);
+  EXPECT_EQ(base::Optional<size_t>(0), after_srp_clicked_link_srp_position());
+
+  histogram_tester.ExpectUniqueSample(
+      "IsolatedPrerender.Prefetch.Mainframe.TotalRedirects", 0, 1);
+}
+
 TEST_F(IsolatedPrerenderTabHelperTest, SuccessCase) {
   base::HistogramTester histogram_tester;
   base::test::ScopedFeatureList scoped_feature_list;
diff --git a/chrome/browser/prerender/prerender_browsertest.cc b/chrome/browser/prerender/prerender_browsertest.cc
index 4b91e11..25d856b 100644
--- a/chrome/browser/prerender/prerender_browsertest.cc
+++ b/chrome/browser/prerender/prerender_browsertest.cc
@@ -449,27 +449,6 @@
         expect_swap_to_succeed);
   }
 
-  void NavigateToURL(const std::string& dest_html_file) const {
-    NavigateToURLWithDisposition(dest_html_file,
-                                 WindowOpenDisposition::CURRENT_TAB, true);
-  }
-
-  void NavigateToURLWithDisposition(const std::string& dest_html_file,
-                                    WindowOpenDisposition disposition,
-                                    bool expect_swap_to_succeed) const {
-    GURL dest_url = embedded_test_server()->GetURL(dest_html_file);
-    NavigateToURLWithDisposition(dest_url, disposition, expect_swap_to_succeed);
-  }
-
-  void NavigateToURLWithDisposition(const GURL& dest_url,
-                                    WindowOpenDisposition disposition,
-                                    bool expect_swap_to_succeed) const {
-    NavigateToURLWithParams(
-        content::OpenURLParams(dest_url, Referrer(), disposition,
-                               ui::PAGE_TRANSITION_TYPED, false),
-        expect_swap_to_succeed);
-  }
-
   void NavigateToURLWithParams(const content::OpenURLParams& params,
                                bool expect_swap_to_succeed) const {
     NavigateToURLImpl(params, expect_swap_to_succeed);
@@ -977,50 +956,6 @@
   ASSERT_NO_FATAL_FAILURE(WaitForTaskManagerRows(0, any_prerender));
 }
 
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderPageWithFragment) {
-  PrerenderTestURL("/prerender/prerender_page.html#fragment", FINAL_STATUS_USED,
-                   1);
-
-  ChannelDestructionWatcher channel_close_watcher;
-  channel_close_watcher.WatchChannel(browser()
-                                         ->tab_strip_model()
-                                         ->GetActiveWebContents()
-                                         ->GetMainFrame()
-                                         ->GetProcess());
-  NavigateToDestURL();
-  channel_close_watcher.WaitForChannelClose();
-
-  ASSERT_TRUE(IsEmptyPrerenderLinkManager());
-}
-
-// Checks that we do not use a prerendered page when navigating from
-// the main page to a fragment.
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderPageNavigateFragment) {
-  PrerenderTestURL("/prerender/no_prerender_page.html",
-                   FINAL_STATUS_APP_TERMINATING, 1);
-  NavigateToURLWithDisposition("/prerender/no_prerender_page.html#fragment",
-                               WindowOpenDisposition::CURRENT_TAB, false);
-}
-
-// Checks that we do not use a prerendered page when we prerender a fragment
-// but navigate to the main page.
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderFragmentNavigatePage) {
-  PrerenderTestURL("/prerender/no_prerender_page.html#fragment",
-                   FINAL_STATUS_APP_TERMINATING, 1);
-  NavigateToURLWithDisposition("/prerender/no_prerender_page.html",
-                               WindowOpenDisposition::CURRENT_TAB, false);
-}
-
-// Checks that we do not use a prerendered page when we prerender a fragment
-// but navigate to a different fragment on the same page.
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
-                       PrerenderFragmentNavigateFragment) {
-  PrerenderTestURL("/prerender/no_prerender_page.html#other_fragment",
-                   FINAL_STATUS_APP_TERMINATING, 1);
-  NavigateToURLWithDisposition("/prerender/no_prerender_page.html#fragment",
-                               WindowOpenDisposition::CURRENT_TAB, false);
-}
-
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderCancelAll) {
   std::unique_ptr<TestPrerender> prerender = PrerenderTestURL(
       "/prerender/prerender_page.html", FINAL_STATUS_CANCELLED, 1);
diff --git a/chrome/browser/prerender/prerender_nostate_prefetch_browsertest.cc b/chrome/browser/prerender/prerender_nostate_prefetch_browsertest.cc
index 36209bd..f54a382 100644
--- a/chrome/browser/prerender/prerender_nostate_prefetch_browsertest.cc
+++ b/chrome/browser/prerender/prerender_nostate_prefetch_browsertest.cc
@@ -99,6 +99,8 @@
 const char kPrefetchMetaCSP[] = "/prerender/prefetch_meta_csp.html";
 const char kPrefetchNostorePage[] = "/prerender/prefetch_nostore_page.html";
 const char kPrefetchPage[] = "/prerender/prefetch_page.html";
+const char kPrefetchPageWithFragment[] =
+    "/prerender/prefetch_page.html#fragment";
 const char kPrefetchPage2[] = "/prerender/prefetch_page2.html";
 const char kPrefetchPageBigger[] = "/prerender/prefetch_page_bigger.html";
 const char kPrefetchPageMultipleResourceTypes[] =
@@ -1033,6 +1035,15 @@
   WaitForRequestCount(src_server()->GetURL(kPrefetchDownloadFile), 0);
 }
 
+IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest, PrefetchPageWithFragment) {
+  std::unique_ptr<TestPrerender> test_prerender = PrefetchFromFile(
+      kPrefetchPageWithFragment, FINAL_STATUS_NOSTATE_PREFETCH_FINISHED);
+
+  test_prerender->WaitForLoads(0);
+  WaitForRequestCount(src_server()->GetURL(kPrefetchScript), 1);
+  WaitForRequestCount(src_server()->GetURL(kPrefetchScript2), 0);
+}
+
 // Checks that a prefetch of a CRX will result in a cancellation due to
 // download.
 IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest, PrefetchCrx) {
diff --git a/chrome/browser/printing/print_browsertest.cc b/chrome/browser/printing/print_browsertest.cc
index cc9c1fd..30bb2907 100644
--- a/chrome/browser/printing/print_browsertest.cc
+++ b/chrome/browser/printing/print_browsertest.cc
@@ -37,7 +37,6 @@
 #include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
-#include "content/public/test/no_renderer_crashes_assertion.h"
 #include "extensions/common/extension.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
 #include "net/dns/mock_host_resolver.h"
@@ -725,9 +724,21 @@
 
   KillPrintRenderFrame frame_content(subframe_rph);
   frame_content.OverrideBinderForTesting(subframe);
-  content::ScopedAllowRendererCrashes allow_renderer_crashes(subframe_rph);
 
-  PrintAndWaitUntilPreviewIsReady(/*print_only_selection=*/false);
+  // Waits for the renderer to be down.
+  content::RenderProcessHostWatcher process_watcher(
+      subframe_rph, content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
+
+  // Adds the observer to get the status for the preview.
+  PrintPreviewObserver print_preview_observer(/*wait_for_loaded=*/false);
+  StartPrint(browser()->tab_strip_model()->GetActiveWebContents(),
+             /*print_renderer=*/mojo::NullAssociatedRemote(),
+             /*print_preview_disabled=*/false, /*has_selection*/ false);
+
+  // Makes sure that |subframe_rph| is terminated.
+  process_watcher.Wait();
+  // Confirms that the preview pages are rendered.
+  print_preview_observer.WaitUntilPreviewIsReady();
 }
 
 // Printing preview a web page with an iframe from an isolated origin.
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/menu_manager.js b/chrome/browser/resources/chromeos/accessibility/switch_access/menu_manager.js
index 22017745..9c30ba9 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/menu_manager.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/menu_manager.js
@@ -217,9 +217,17 @@
       return;
     }
 
+    // Before overriding the reference to the current menu node, remove the
+    // event handler.
+    if (this.menuAutomationNode_) {
+      this.menuAutomationNode_.removeEventListener(
+          chrome.automation.EventType.CLICKED, MenuManager.onButtonClicked_,
+          false);
+    }
+
     this.menuAutomationNode_ = node;
     this.menuAutomationNode_.addEventListener(
-        chrome.automation.EventType.CLICKED, this.onButtonClicked_.bind(this),
+        chrome.automation.EventType.CLICKED, MenuManager.onButtonClicked_,
         false);
     NavigationManager.jumpToSwitchAccessMenu(this.menuAutomationNode_);
   }
@@ -230,15 +238,16 @@
    * @param {!chrome.automation.AutomationEvent} event
    * @private
    */
-  onButtonClicked_(event) {
-    const selectedAction = this.asAction_(event.target.value);
-    if (!this.isMenuOpen_ || !selectedAction ||
-        this.handleGlobalActions_(selectedAction)) {
+  static onButtonClicked_(event) {
+    const manager = MenuManager.instance;
+    const selectedAction = manager.asAction_(event.target.value);
+    if (!manager.isMenuOpen_ || !selectedAction ||
+        manager.handleGlobalActions_(selectedAction)) {
       return;
     }
 
-    if (!this.actionNode_.hasAction(selectedAction)) {
-      this.refreshActions_();
+    if (!manager.actionNode_.hasAction(selectedAction)) {
+      manager.refreshActions_();
       return;
     }
 
@@ -246,21 +255,21 @@
     // having the menu on the group stack interferes with some actions. We do
     // not close the menu bubble until we receive the ActionResponse CLOSE_MENU.
     // If we receive a different response, we re-enter the menu.
-    NavigationManager.exitIfInGroup(this.menuAutomationNode_);
-    const response = this.actionNode_.performAction(selectedAction);
+    NavigationManager.exitIfInGroup(manager.menuAutomationNode_);
+    const response = manager.actionNode_.performAction(selectedAction);
     if (response === SAConstants.ActionResponse.CLOSE_MENU ||
-        !this.hasValidMenuAutomationNode_()) {
+        !manager.hasValidMenuAutomationNode_()) {
       MenuManager.exit();
     } else {
-      NavigationManager.jumpToSwitchAccessMenu(this.menuAutomationNode_);
+      NavigationManager.jumpToSwitchAccessMenu(manager.menuAutomationNode_);
     }
 
     switch (response) {
       case SAConstants.ActionResponse.RELOAD_MAIN_MENU:
-        this.refreshActions_();
+        manager.refreshActions_();
         break;
       case SAConstants.ActionResponse.OPEN_TEXT_NAVIGATION_MENU:
-        this.openTextNavigation_();
+        manager.openTextNavigation_();
     }
   }
 
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/navigation_manager.js b/chrome/browser/resources/chromeos/accessibility/switch_access/navigation_manager.js
index a4237b51..5ab0b31 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/navigation_manager.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/navigation_manager.js
@@ -178,6 +178,9 @@
       }
     }
 
+    // Make sure the menu isn't open.
+    MenuManager.exit();
+
     const child = navigator.group_.firstValidChild();
     if (groupIsValid && child) {
       navigator.setNode_(child);
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/desktop_node.js b/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/desktop_node.js
index 36d752c8..80b8358 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/desktop_node.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/desktop_node.js
@@ -75,7 +75,6 @@
     const interestingChildren = RootNodeWrapper.getInterestingChildren(root);
 
     if (interestingChildren.length < 1) {
-      setTimeout(NavigationManager.moveToValidNode, 0);
       throw SwitchAccess.error(
           SAConstants.ErrorType.MALFORMED_DESKTOP,
           'Desktop node must have at least 1 interesting child.');
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access_constants.js b/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access_constants.js
index 51509af6..d1246c54 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access_constants.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access_constants.js
@@ -80,6 +80,7 @@
     MISSING_BASE_NODE: 12,
     NEXT_INVALID: 13,
     PREVIOUS_INVALID: 14,
+    INVALID_SELECTION_BOUNDS: 15,
   },
 
   /**
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/text_navigation_manager.js b/chrome/browser/resources/chromeos/accessibility/switch_access/text_navigation_manager.js
index 4eee364..556d5de1 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/text_navigation_manager.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/text_navigation_manager.js
@@ -322,24 +322,18 @@
   }
 
   /**
-   * Sets the selection using the selectionStart and selectionEnd
-   * as the offset input for setDocumentSelection and the parameter
-   * textNode as the object input for setDocumentSelection.
+   * Sets the selection after verifying that the bounds are set.
    * @private
    */
   saveSelection_() {
     if (this.selectionStartIndex_ == TextNavigationManager.NO_SELECT_INDEX ||
         this.selectionEndIndex_ == TextNavigationManager.NO_SELECT_INDEX) {
-      console.log(
-          'Selection bounds are not set properly:', this.selectionStartIndex_,
-          this.selectionEndIndex_);
+      console.error(SwitchAccess.error(
+          SAConstants.ErrorType.INVALID_SELECTION_BOUNDS,
+          'Selection bounds are not set properly: ' +
+              this.selectionStartIndex_ + ' ' + this.selectionEndIndex_));
     } else {
-      chrome.automation.setDocumentSelection({
-        anchorObject: this.selectionStartObject_,
-        anchorOffset: this.selectionStartIndex_,
-        focusObject: this.selectionEndObject_,
-        focusOffset: this.selectionEndIndex_
-      });
+      this.setSelection_();
     }
   }
 
@@ -360,17 +354,31 @@
       if (TextNavigationManager.currentlySelecting() &&
           this.selectionEndIndex_ != TextNavigationManager.NO_SELECT_INDEX) {
         // Move the cursor to the end of the existing selection.
-        chrome.automation.setDocumentSelection({
-          anchorObject: this.selectionEndObject_,
-          anchorOffset: this.selectionEndIndex_,
-          focusObject: this.selectionEndObject_,
-          focusOffset: this.selectionEndIndex_
-        });
+        this.setSelection_();
       }
     }
     this.manageNavigationListener_(true /** Add the listener */);
   }
 
+  /**
+   * Sets the selection. If start and end object are equal, uses
+   * AutomationNode.setSelection. Otherwise calls
+   * chrome.automation.setDocumentSelection.
+   */
+  setSelection_() {
+    if (this.selectionStartObject_ === this.selectionEndObject_) {
+      this.selectionStartObject_.setSelection(
+          this.selectionStartIndex_, this.selectionEndIndex_);
+    } else {
+      chrome.automation.setDocumentSelection({
+        anchorObject: this.selectionStartObject_,
+        anchorOffset: this.selectionStartIndex_,
+        focusObject: this.selectionEndObject_,
+        focusOffset: this.selectionEndIndex_
+      });
+    }
+  }
+
   /*
    * TODO(rosalindag): Add functionality to catch when clipboardHasData_ needs
    * to be set to false.
diff --git a/chrome/browser/resources/new_tab_page/BUILD.gn b/chrome/browser/resources/new_tab_page/BUILD.gn
index ded2238..ff836c68 100644
--- a/chrome/browser/resources/new_tab_page/BUILD.gn
+++ b/chrome/browser/resources/new_tab_page/BUILD.gn
@@ -38,7 +38,6 @@
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:cr.m",
   ]
-  externs_list = [ "externs.js" ]
 }
 
 js_library("app") {
diff --git a/chrome/browser/resources/new_tab_page/externs.js b/chrome/browser/resources/new_tab_page/externs.js
deleted file mode 100644
index 126dbec..0000000
--- a/chrome/browser/resources/new_tab_page/externs.js
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview Externs for objects sent from C++ to JS for
- *     chrome://new-tab-page.
- * @externs
- */
-
-// eslint-disable-next-line no-var
-var newTabPage = {};
diff --git a/chrome/browser/resources/new_tab_page/logo.html b/chrome/browser/resources/new_tab_page/logo.html
index 418e6e8..5ad96d0 100644
--- a/chrome/browser/resources/new_tab_page/logo.html
+++ b/chrome/browser/resources/new_tab_page/logo.html
@@ -4,7 +4,6 @@
     display: flex;
     flex-direction: column;
     flex-shrink: 0;
-    justify-content: center;
     min-height: var(--ntp-logo-height);
   }
 
diff --git a/chrome/browser/resources/pdf/elements/icons.html b/chrome/browser/resources/pdf/elements/icons.html
index dca19e1d..578d4cf 100644
--- a/chrome/browser/resources/pdf/elements/icons.html
+++ b/chrome/browser/resources/pdf/elements/icons.html
@@ -8,18 +8,20 @@
       <g id="add"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"></path></g>
       <g id="bookmark"><path d="M17 3H7c-1.1 0-1.99.9-1.99 2L5 21l7-3 7 3V5c0-1.1-.9-2-2-2z"></path></g>
       <g id="bookmark-border"><path d="M17 3H7c-1.1 0-1.99.9-1.99 2L5 21l7-3 7 3V5c0-1.1-.9-2-2-2zm0 15l-5-2.18L7 18V5h10v13z"></path></g>
-      <g id="open-book"><path d="M-74 29h48v48h-48V29zM0 0h24v24H0V0zm0 0h24v24H0V0z" fill="none"></path><path d="M13 12h7v1.5h-7zm0-2.5h7V11h-7zm0 5h7V16h-7zM21 4H3c-1.1 0-2 .9-2 2v13c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 15h-9V6h9v13z"></path></g>
       <g id="create"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"></path></g>
+      <g id="doc-outline"><path d="M0 0h24v24H0z" fill="none"></path><path d="M19 5v14H5V5h14m1.1-2H3.9c-.5 0-.9.4-.9.9v16.2c0 .4.4.9.9.9h16.2c.4 0 .9-.5.9-.9V3.9c0-.5-.5-.9-.9-.9zM11 7h6v2h-6V7zm0 4h6v2h-6v-2zm0 4h6v2h-6zM7 7h2v2H7zm0 4h2v2H7zm0 4h2v2H7z"></path></g>
       <g id="eraser"><path d="M21.41,11.33 L13.04,20 L4.73,20 L2.58,17.86 C1.8,17.08 1.8,15.83 2.58,15.04 L13.62,3.58 C14.4,2.81 15.68,2.81 16.46,3.58 L21.41,8.51 C22.2,9.29 22.2,10.55 21.41,11.33 L21.41,11.33 Z"></path><polygon points="17.26 18 15.26 20 21.96 20 21.96 18"></polygon></g>
       <g id="fit-to-height"><path fill-rule="evenodd" clip-rule="evenodd" d="M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 10l3.01-4.5L15 10H9zm0 4h6l-2.99 4.5L9 14zm-6 5.01h18V4.99H3v14.02z"></path></g>
       <g id="fit-to-width"><path fill-rule="evenodd" clip-rule="evenodd" d="M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM3.5 12.01L8 9v6l-4.5-2.99zM16 15V9l4.5 3.01L16 15zM3 19.01h18V4.99H3v14.02z"></path></g>
       <g id="fullscreen-exit"><path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"></path></g>
       <g id="highlighter"><path d="M10.22,9.49 L4.31,15.49 C3.54,16.29 3.61,17.54 4.39,18.34 L0.77,22 L6.45,22 L7.19,21.25 C7.97,22.06 9.14,22.11 9.92,21.3 L15.88,15.25 L10.22,9.49 L10.22,9.49 Z"></path><path style="fill: var(--pen-tip-fill)"  d="M22.68,5.49 L19.86,2.62 C19.08,1.82 17.79,1.78 17.02,2.58 L11.27,8.43 L16.93,14.18 L22.62,8.4 C23.39,7.59 23.45,6.29 22.68,5.49 L22.68,5.49 Z"></path><path style="fill: var(--pen-tip-border)" d="M18.4,3c0.3,0,0.5,0.1,0.7,0.3L22,6.2c0.4,0.4,0.4,1.1-0.1,1.5l-5,5.1l-4.3-4.3l5.1-5.2 C17.9,3.1,18.1,3,18.4,3 M18.4,2c-0.5,0-1,0.2-1.4,0.6l-5.8,5.9l5.7,5.8l5.7-5.8c0.8-0.8,0.8-2.1,0.1-2.9l-2.8-2.9 C19.5,2.2,18.9,2,18.4,2L18.4,2z"></path></g>
       <g id="marker"><polygon points="3 17.25 3 21 6.74 21 14.28 13.47 10.53 9.72"></polygon><path style="fill: var(--pen-tip-fill)" d="M18.37,3.3 L20.71,5.63 C21.1,6.02 21.11,6.66 20.72,7.05 L15.35,12.41 L11.59,8.65 L14.12,6.12 L13.39,5.39 L7.73,11.05 L6.33,9.65 L12.7,3.29 C13.09,2.9 13.74,2.91 14.12,3.3 L15.54,4.71 L16.96,3.3 C17.34,2.91 17.98,2.91 18.37,3.3 L18.37,3.3 Z"></path><path style="fill: var(--pen-tip-border)" d="M17.7,4L20,6.3L15.4,11L13,8.6l1.8-1.8l0.7-0.7l-0.7-0.7l-0.2-0.2l0.2,0.2l0.7,0.7l0.7-0.7L17.7,4 M13.4,3 c-0.3,0-0.5,0.1-0.7,0.3L6.3,9.6l1.4,1.4l5.7-5.7l0.7,0.7l-2.5,2.5l3.8,3.8L20.7,7c0.4-0.4,0.4-1,0-1.4l-2.3-2.3 C18.2,3.1,17.9,3,17.7,3S17.2,3.1,17,3.3l-1.4,1.4l-1.4-1.4C13.9,3.1,13.7,3,13.4,3L13.4,3z"></path></g>
+      <g id="open-book"><path d="M-74 29h48v48h-48V29zM0 0h24v24H0V0zm0 0h24v24H0V0z" fill="none"></path><path d="M13 12h7v1.5h-7zm0-2.5h7V11h-7zm0 5h7V16h-7zM21 4H3c-1.1 0-2 .9-2 2v13c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 15h-9V6h9v13z"></path></g>
       <g id="redo"><path d="M18.4 10.6C16.55 8.99 14.15 8 11.5 8c-4.65 0-8.58 3.03-9.96 7.22L3.9 16c1.05-3.19 4.05-5.5 7.6-5.5 1.95 0 3.73.72 5.12 1.88L13 16h9V7l-3.6 3.6z"></path></g>
       <g id="remove"><path d="M19 13H5v-2h14v2z"></path></g>
       <g id="rotate-left"><path d="M0 0h24v24H0z" fill="none"></path><path d="M7.34 6.41L.86 12.9l6.49 6.48 6.49-6.48-6.5-6.49zM3.69 12.9l3.66-3.66L11 12.9l-3.66 3.66-3.65-3.66zm15.67-6.26C17.61 4.88 15.3 4 13 4V.76L8.76 5 13 9.24V6c1.79 0 3.58.68 4.95 2.05 2.73 2.73 2.73 7.17 0 9.9C16.58 19.32 14.79 20 13 20c-.97 0-1.94-.21-2.84-.61l-1.49 1.49C10.02 21.62 11.51 22 13 22c2.3 0 4.61-.88 6.36-2.64 3.52-3.51 3.52-9.21 0-12.72z"></path></g>
       <g id="rotate-right"><path d="M15.55 5.55L11 1v3.07C7.06 4.56 4 7.92 4 12s3.05 7.44 7 7.93v-2.02c-2.84-.48-5-2.94-5-5.91s2.16-5.43 5-5.91V10l4.55-4.45zM19.93 11c-.17-1.39-.72-2.73-1.62-3.89l-1.42 1.42c.54.75.88 1.6 1.02 2.47h2.02zM13 17.9v2.02c1.39-.17 2.74-.71 3.9-1.61l-1.44-1.44c-.75.54-1.59.89-2.46 1.03zm3.89-2.42l1.42 1.41c.9-1.16 1.45-2.5 1.62-3.89h-2.02c-.14.87-.48 1.72-1.02 2.48z"></path></g>
+      <g id="thumbnails"><path d="M0 0h24v24H0z" fill="none"></path><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14zm-5.04-6.71l-2.75 3.54-1.96-2.36L6.5 17h11l-3.54-4.71z"></path></g>
       <g id="undo"><path d="M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z"></path></g>
     </defs>
   </svg>
diff --git a/chrome/browser/resources/settings/chromeos/BUILD.gn b/chrome/browser/resources/settings/chromeos/BUILD.gn
index e837a45..86779cd 100644
--- a/chrome/browser/resources/settings/chromeos/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/BUILD.gn
@@ -236,8 +236,9 @@
   deps = [
     # TODO: Uncomment as the Polymer3 migration makes progress.
     #":closure_compile_local_module",
-    #"ambient_mode_page:closure_compile_module",
+    "ambient_mode_page:closure_compile_module",
     "bluetooth_page:closure_compile_module",
+
     #"crostini_page:closure_compile_module",
     #"date_time_page:closure_compile_module",
     #"device_page:closure_compile_module",
@@ -245,6 +246,7 @@
     #"internet_page:closure_compile_module",
     "localized_link:closure_compile_module",
     "multidevice_page:closure_compile_module",
+
     #"os_a11y_page:closure_compile_module",
     #"os_about_page:closure_compile_module",
     #"os_apps_page:closure_compile_module",
@@ -255,6 +257,7 @@
     #"os_printing_page:closure_compile_module",
     #"os_privacy_page:closure_compile_module",
     "os_reset_page:closure_compile_module",
+
     #"os_search_page:closure_compile_module",
     #"os_settings_main:closure_compile_module",
     #"os_settings_menu:closure_compile_module",
@@ -263,7 +266,7 @@
     #"os_settings_ui:closure_compile_module",
     #"os_toolbar:closure_compile_module",
     #"parental_controls_page:closure_compile_module",
-    #"personalization_page:closure_compile_module",
+    "personalization_page:closure_compile_module",
   ]
 }
 
@@ -280,6 +283,7 @@
     #  ":os_settings_icons_css.m",
     ":os_settings_routes.m",
     ":route_origin_behavior.m",
+
     #":search_handler.m",
   ]
 }
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/ambient_mode_page/BUILD.gn
index 10e93ed..17eb709 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/BUILD.gn
@@ -3,6 +3,9 @@
 # found in the LICENSE file.
 
 import("//third_party/closure_compiler/compile_js.gni")
+import("//tools/polymer/polymer.gni")
+import("//ui/webui/resources/tools/js_modulizer.gni")
+import("../os_settings.gni")
 
 js_type_check("closure_compile") {
   deps = [
@@ -42,6 +45,7 @@
     ":constants",
     "..:os_route",
     "../..:router",
+    "//ui/webui/resources/js:assert",
     "//ui/webui/resources/js:cr",
     "//ui/webui/resources/js:i18n_behavior",
     "//ui/webui/resources/js:web_ui_listener_behavior",
@@ -55,6 +59,7 @@
   deps = [
     "//ui/webui/resources/js:cr",
     "//ui/webui/resources/js:i18n_behavior",
+    ":constants"
   ]
 }
 
@@ -62,30 +67,40 @@
   deps = [ "//ui/webui/resources/js:cr" ]
 }
 
-# TODO: Uncomment as the Polymer3 migration makes progress.
-#js_type_check("closure_compile_module") {
-#  is_polymer3 = true
-#  deps = [
-#    ":ambient_mode_browser_proxy.m",
-#    ":ambient_mode_page.m",
-#    ":ambient_mode_photos_page.m",
-#    ":topic_source_item.m",
-#    ":topic_source_list.m",
-#  ]
-#}
+js_type_check("closure_compile_module") {
+  is_polymer3 = true
+  deps = [
+    ":ambient_mode_browser_proxy.m",
+    ":ambient_mode_page.m",
+    ":ambient_mode_photos_page.m",
+    ":topic_source_item.m",
+    ":topic_source_list.m",
+  ]
+}
+
+js_library("constants.m") {
+  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/ambient_mode_page/constants.m.js" ]
+  extra_deps = [ ":modulize" ]
+}
 
 js_library("ambient_mode_browser_proxy.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_browser_proxy.m.js" ]
-  deps = [
-    # TODO: Fill those in.
-  ]
+  deps = [ ":constants.m" ]
+  externs_list = [ "$externs_path/chrome_send.js" ]
   extra_deps = [ ":modulize" ]
 }
 
 js_library("ambient_mode_page.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":ambient_mode_browser_proxy.m",
+    ":constants.m",
+    "..:os_route.m",
+    "../..:router.m",
+    "../../prefs:prefs_behavior.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:i18n_behavior.m",
+    "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
   extra_deps = [ ":ambient_mode_page_module" ]
 }
@@ -93,7 +108,14 @@
 js_library("ambient_mode_photos_page.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_photos_page.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":ambient_mode_browser_proxy.m",
+    ":constants.m",
+    "..:os_route.m",
+    "../..:router.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js:i18n_behavior.m",
+    "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
   extra_deps = [ ":ambient_mode_photos_page_module" ]
 }
@@ -101,7 +123,9 @@
 js_library("topic_source_item.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_item.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":constants.m",
+    "//ui/webui/resources/js:i18n_behavior.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
   extra_deps = [ ":topic_source_item_module" ]
 }
@@ -109,13 +133,11 @@
 js_library("topic_source_list.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_list.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
   extra_deps = [ ":topic_source_list_module" ]
 }
 
-import("//tools/polymer/polymer.gni")
-
 group("polymer3_elements") {
   public_deps = [
     ":ambient_mode_page_module",
@@ -130,31 +152,43 @@
   js_file = "ambient_mode_page.js"
   html_file = "ambient_mode_page.html"
   html_type = "dom-module"
+  migrated_imports = os_settings_migrated_imports
+  namespace_rewrites = os_settings_namespace_rewrites
+  auto_imports = os_settings_auto_imports
 }
 
 polymer_modulizer("ambient_mode_photos_page") {
   js_file = "ambient_mode_photos_page.js"
   html_file = "ambient_mode_photos_page.html"
   html_type = "dom-module"
+  migrated_imports = os_settings_migrated_imports
+  namespace_rewrites = os_settings_namespace_rewrites
+  auto_imports = os_settings_auto_imports +
+                 [ "ui/webui/resources/html/assert.html|assertNotReached" ]
 }
 
 polymer_modulizer("topic_source_item") {
   js_file = "topic_source_item.js"
   html_file = "topic_source_item.html"
   html_type = "dom-module"
+  migrated_imports = os_settings_migrated_imports
+  namespace_rewrites = os_settings_namespace_rewrites
+  auto_imports = os_settings_auto_imports
 }
 
 polymer_modulizer("topic_source_list") {
   js_file = "topic_source_list.js"
   html_file = "topic_source_list.html"
   html_type = "dom-module"
+  migrated_imports = os_settings_migrated_imports
+  namespace_rewrites = os_settings_namespace_rewrites
+  auto_imports = os_settings_auto_imports
 }
 
-import("//ui/webui/resources/tools/js_modulizer.gni")
-
 js_modulizer("modulize") {
   input_files = [
     "ambient_mode_browser_proxy.js",
     "constants.js",
   ]
+  namespace_rewrites = os_settings_namespace_rewrites
 }
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_browser_proxy.js b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_browser_proxy.js
index 24baeb6..c647b96 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_browser_proxy.js
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_browser_proxy.js
@@ -2,6 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// #import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
+// #import {AmbientModeTopicSource, AmbientModeSettings} from './constants.m.js';
+
 /**
  * @fileoverview A helper object used from the ambient mode section to interact
  * with the browser.
@@ -9,7 +12,7 @@
 
 cr.define('settings', function() {
   /** @interface */
-  class AmbientModeBrowserProxy {
+  /* #export */ class AmbientModeBrowserProxy {
     /**
      * Retrieves the initial settings from server, such as topic source. As a
      * response, the C++ sends the 'topic-source-changed' WebUIListener event.
@@ -39,7 +42,7 @@
   }
 
   /** @implements {settings.AmbientModeBrowserProxy} */
-  class AmbientModeBrowserProxyImpl {
+  /* #export */ class AmbientModeBrowserProxyImpl {
     /** @override */
     onAmbientModePageReady() {
       chrome.send('onAmbientModePageReady');
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.html b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.html
index ded78ae..a05d247 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.html
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.html
@@ -10,6 +10,8 @@
 <link rel="import" href="topic_source_list.html">
 <link rel="import" href="../os_route.html">
 <link rel="import" href="../../router.html">
+<link rel="import" href="../../prefs/prefs.html">
+<link rel="import" href="../../prefs/prefs_behavior.html">
 <link rel="import" href="../../controls/settings_radio_group.html">
 <link rel="import" href="../../controls/settings_toggle_button.html">
 <link rel="import" href="../../settings_shared_css.html">
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_photos_page.html b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_photos_page.html
index 052943e..8f61602 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_photos_page.html
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_photos_page.html
@@ -3,6 +3,7 @@
 <link rel="import" href="ambient_mode_browser_proxy.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_checkbox/cr_checkbox.html">
 <link rel="import" href="chrome://resources/html/i18n_behavior.html">
+<link rel="import" href="chrome://resources/html/assert.html">
 <link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
 <link rel="import" href="constants.html">
 <link rel="import" href="../os_route.html">
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_item.html b/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_item.html
index 2dd89e4..e6440e7c 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_item.html
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_item.html
@@ -7,6 +7,7 @@
 <link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
 <link rel="import" href="chrome://resources/html/i18n_behavior.html">
 <link rel="import" href="../../settings_shared_css.html">
+<link rel="import" href="./constants.html">
 
 <dom-module id="topic-source-item">
   <template>
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_list.html b/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_list.html
index 5fa5f72d..f8d24eb 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_list.html
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_list.html
@@ -6,6 +6,7 @@
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
 <link rel="import" href="../../settings_shared_css.html">
+<link rel="import" href="./constants.html">
 
 <dom-module id="topic-source-list">
   <template>
diff --git a/chrome/browser/resources/settings/chromeos/device_page/device_page_browser_proxy.js b/chrome/browser/resources/settings/chromeos/device_page/device_page_browser_proxy.js
index 5a3e4081..215c53c 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/device_page_browser_proxy.js
+++ b/chrome/browser/resources/settings/chromeos/device_page/device_page_browser_proxy.js
@@ -206,6 +206,15 @@
      * @return {!Promise<boolean>} Whether purging of DLC was successful.
      */
     purgeDlc(dlcId) {}
+
+    /**
+     * Updates the position of the dragged display to render preview indicators
+     * as the display is being dragged around.
+     * @param {string} id Display id of selected display.
+     * @param {number} deltaX x-axis position change since the last update.
+     * @param {number} deltaY y-axis position change since the last update.
+     */
+    dragDisplayDelta(id, deltaX, deltaY) {}
   }
 
   /**
@@ -316,6 +325,11 @@
     purgeDlc(dlcId) {
       return cr.sendWithPromise('purgeDlc', dlcId);
     }
+
+    /** @override */
+    dragDisplayDelta(id, deltaX, deltaY) {
+      chrome.send('dragDisplayDelta', [id, deltaX, deltaY]);
+    }
   }
 
   cr.addSingletonGetter(DevicePageBrowserProxyImpl);
diff --git a/chrome/browser/resources/settings/chromeos/device_page/display_layout.js b/chrome/browser/resources/settings/chromeos/device_page/display_layout.js
index eb73912b..666d5dd 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/display_layout.js
+++ b/chrome/browser/resources/settings/chromeos/device_page/display_layout.js
@@ -55,6 +55,28 @@
   /** @private {!{left: number, top: number}} */
   visualOffset_: {left: 0, top: 0},
 
+  /**
+   * Stores the previous coordinates of a display once dragging starts. Used to
+   * calculate the delta during each step of the drag. Null when there is no
+   * drag in progress.
+   * @private {?{x: number, y: number}}
+   */
+  lastDragCoordinates_: null,
+
+  /** @private {?settings.DevicePageBrowserProxy} */
+  browserProxy_: null,
+
+  /** @private {boolean} */
+  allowDisplayAlignmentApi_:
+      loadTimeData.getBoolean('allowDisplayAlignmentApi'),
+
+  /** @override */
+  created() {
+    if (this.allowDisplayAlignmentApi_) {
+      this.browserProxy_ = settings.DevicePageBrowserProxyImpl.getInstance();
+    }
+  },
+
   /** @override */
   detached() {
     this.initializeDrag(false);
@@ -249,6 +271,7 @@
     if (!amount) {
       this.finishUpdateDisplayBounds(id);
       newBounds = this.getCalculatedDisplayBounds(id);
+      this.lastDragCoordinates_ = null;
     } else {
       // Make sure the dragged display is also selected.
       if (id != this.selectedDisplay.id) {
@@ -265,7 +288,30 @@
       if (this.displays.length >= 2) {
         newBounds = this.updateDisplayBounds(id, newBounds);
       }
+
+      if (this.allowDisplayAlignmentApi_) {
+        if (!this.lastDragCoordinates_) {
+          this.hasDragStarted_ = true;
+          this.lastDragCoordinates_ = {
+            x: calculatedBounds.left,
+            y: calculatedBounds.top
+          };
+        }
+
+        const deltaX = newBounds.left - this.lastDragCoordinates_.x;
+        const deltaY = newBounds.top - this.lastDragCoordinates_.y;
+
+        this.lastDragCoordinates_.x = newBounds.left;
+        this.lastDragCoordinates_.y = newBounds.top;
+
+        // Only call dragDisplayDelta() when there is a change in position.
+        if (deltaX != 0 || deltaY != 0) {
+          this.browserProxy_.dragDisplayDelta(
+              id, Math.round(deltaX), Math.round(deltaY));
+        }
+      }
     }
+
     const left =
         this.visualOffset_.left + Math.round(newBounds.left * this.visualScale);
     const top =
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_people_page/BUILD.gn
index 23cc34fa..44fe62a 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/BUILD.gn
@@ -3,6 +3,9 @@
 # found in the LICENSE file.
 
 import("//third_party/closure_compiler/compile_js.gni")
+import("//tools/polymer/polymer.gni")
+import("//ui/webui/resources/tools/js_modulizer.gni")
+import("../os_settings.gni")
 
 js_type_check("closure_compile") {
   deps = [
@@ -30,6 +33,7 @@
 
 js_library("account_manager") {
   deps = [
+    "../..:router",
     "../../people_page:account_manager_browser_proxy",
     "../localized_link:localized_link",
     "//ui/webui/resources/js:cr",
@@ -160,7 +164,7 @@
     ":os_sync_browser_proxy",
     "..:metrics_recorder",
     "../../:router",
-    "../localized_link",
+    "../localized_link:localized_link",
     "//ui/webui/resources/cr_elements/cr_toggle:cr_toggle",
     "//ui/webui/resources/js:assert",
     "//ui/webui/resources/js:web_ui_listener_behavior",
@@ -214,34 +218,40 @@
   ]
 }
 
-# TODO: Uncomment as the Polymer3 migration makes progress.
-#js_type_check("closure_compile_module") {
-#  is_polymer3 = true
-#  deps = [
-#    ":account_manager.m",
-#    ":fingerprint_browser_proxy.m",
-#    ":fingerprint_list.m",
-#    ":kerberos_accounts.m",
-#    ":kerberos_accounts_browser_proxy.m",
-#    ":kerberos_add_account_dialog.m",
-#    ":lock_screen.m",
-#    ":lock_screen_password_prompt_dialog.m",
-#    ":lock_state_behavior.m",
-#    ":os_people_page.m",
-#    ":os_sync_browser_proxy.m",
-#    ":os_sync_controls.m",
-#    ":setup_fingerprint_dialog.m",
-#    ":setup_pin_dialog.m",
-#    ":user_list.m",
-#    ":users_add_user_dialog.m",
-#    ":users_page.m"
-#  ]
-#}
+js_type_check("closure_compile_module") {
+  is_polymer3 = true
+  deps = [
+    ":account_manager.m",
+    ":fingerprint_browser_proxy.m",
+    ":fingerprint_list.m",
+    ":kerberos_accounts.m",
+    ":kerberos_accounts_browser_proxy.m",
+    ":kerberos_add_account_dialog.m",
+    ":lock_screen.m",
+    ":lock_screen_password_prompt_dialog.m",
+    ":lock_state_behavior.m",
+    ":os_people_page.m",
+    ":os_sync_browser_proxy.m",
+    ":os_sync_controls.m",
+    ":setup_fingerprint_dialog.m",
+    ":setup_pin_dialog.m",
+    ":user_list.m",
+    ":users_add_user_dialog.m",
+    ":users_page.m",
+  ]
+}
 
 js_library("account_manager.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_people_page/account_manager.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    "../..:router.m",
+    "../../people_page:account_manager_browser_proxy.m",
+    "../localized_link:localized_link.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:cr.m",
+    "//ui/webui/resources/js:i18n_behavior.m",
+    "//ui/webui/resources/js:icon.m",
+    "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
   extra_deps = [ ":account_manager_module" ]
 }
@@ -249,7 +259,8 @@
 js_library("fingerprint_browser_proxy.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_people_page/fingerprint_browser_proxy.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:cr.m",
   ]
   extra_deps = [ ":modulize" ]
 }
@@ -257,7 +268,18 @@
 js_library("fingerprint_list.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_people_page/fingerprint_list.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":fingerprint_browser_proxy.m",
+    "..:metrics_recorder.m",
+    "..:os_route.m",
+    "../..:router.m",
+    "//third_party/polymer/v3_0/components-chromium/iron-resizable-behavior:iron-resizable-behavior",
+    "//third_party/polymer/v3_0/components-chromium/paper-ripple:paper-ripple",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js:cr.m",
+    "//ui/webui/resources/js:i18n_behavior.m",
+    "//ui/webui/resources/js:web_ui_listener_behavior.m",
+    "//ui/webui/resources/js/cr/ui:focus_without_ink.m",
   ]
   extra_deps = [ ":fingerprint_list_module" ]
 }
@@ -265,7 +287,14 @@
 js_library("kerberos_accounts.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_accounts.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":kerberos_accounts_browser_proxy.m",
+    "..:metrics_recorder.m",
+    "../..:router.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:cr.m",
+    "//ui/webui/resources/js:i18n_behavior.m",
+    "//ui/webui/resources/js:icon.m",
+    "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
   extra_deps = [ ":kerberos_accounts_module" ]
 }
@@ -273,7 +302,8 @@
 js_library("kerberos_accounts_browser_proxy.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_accounts_browser_proxy.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:cr.m",
   ]
   extra_deps = [ ":modulize" ]
 }
@@ -281,7 +311,15 @@
 js_library("kerberos_add_account_dialog.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_add_account_dialog.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":kerberos_accounts_browser_proxy.m",
+    "..:metrics_recorder.m",
+    "//chrome/browser/resources/settings/controls:settings_textarea.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/cr_elements/cr_input:cr_input.m",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js:cr.m",
+    "//ui/webui/resources/js:i18n_behavior.m",
+    "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
   extra_deps = [ ":kerberos_add_account_dialog_module" ]
 }
@@ -289,7 +327,20 @@
 js_library("lock_screen.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":fingerprint_browser_proxy.m",
+    ":lock_screen_password_prompt_dialog.m",
+    ":lock_state_behavior.m",
+    "..:os_route.m",
+    "../..:router.m",
+    "../../controls:settings_dropdown_menu.m",
+    "../../controls:settings_toggle_button.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/cr_components/chromeos/quick_unlock:lock_screen_constants.m",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js:i18n_behavior.m",
+    "//ui/webui/resources/js:load_time_data.m",
+    "//ui/webui/resources/js:web_ui_listener_behavior.m",
+    "//ui/webui/resources/js/cr/ui:focus_without_ink.m",
   ]
   extra_deps = [ ":lock_screen_module" ]
 }
@@ -297,7 +348,10 @@
 js_library("lock_screen_password_prompt_dialog.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen_password_prompt_dialog.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":lock_state_behavior.m",
+    "../../controls:password_prompt_dialog.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/cr_components/chromeos/quick_unlock:lock_screen_constants.m",
   ]
   extra_deps = [ ":lock_screen_password_prompt_dialog_module" ]
 }
@@ -305,15 +359,40 @@
 js_library("lock_state_behavior.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_people_page/lock_state_behavior.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    "../..:router.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:i18n_behavior.m",
+    "//ui/webui/resources/js:load_time_data.m",
+    "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
   extra_deps = [ ":modulize" ]
+  externs_list = [ "$externs_path/quick_unlock_private.js" ]
+  extra_sources = [ "$interfaces_path/quick_unlock_private_interface.js" ]
 }
 
 js_library("os_people_page.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_people_page/os_people_page.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":lock_screen.m",
+    ":lock_state_behavior.m",
+    ":os_sync_controls.m",
+    "..:os_page_visibility.m",
+    "..:os_route.m",
+    "../..:router.m",
+    "../../settings_page:settings_animated_pages.m",
+    "../localized_link:localized_link.m",
+    "//chrome/browser/resources/settings/people_page:profile_info_browser_proxy.m",
+    "//chrome/browser/resources/settings/people_page:signout_dialog.m",
+    "//chrome/browser/resources/settings/people_page:sync_browser_proxy.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/cr_components/chromeos/quick_unlock:lock_screen_constants.m",
+    "//ui/webui/resources/cr_elements/chromeos/cr_picture:png.m",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js:i18n_behavior.m",
+    "//ui/webui/resources/js:icon.m",
+    "//ui/webui/resources/js:load_time_data.m",
+    "//ui/webui/resources/js:web_ui_listener_behavior.m",
+    "//ui/webui/resources/js/cr/ui:focus_without_ink.m",
   ]
   extra_deps = [ ":os_people_page_module" ]
 }
@@ -321,7 +400,9 @@
 js_library("os_sync_browser_proxy.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_browser_proxy.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:cr.m",
+    "//ui/webui/resources/js:load_time_data.m",
   ]
   extra_deps = [ ":modulize" ]
 }
@@ -329,7 +410,14 @@
 js_library("os_sync_controls.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_controls.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":os_sync_browser_proxy.m",
+    "..:metrics_recorder.m",
+    "../../:router.m",
+    "../localized_link:localized_link.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/cr_elements/cr_toggle:cr_toggle.m",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
   extra_deps = [ ":os_sync_controls_module" ]
 }
@@ -337,7 +425,13 @@
 js_library("setup_fingerprint_dialog.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_people_page/setup_fingerprint_dialog.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":fingerprint_browser_proxy.m",
+    "..:metrics_recorder.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/cr_elements/cr_fingerprint:cr_fingerprint_progress_arc.m",
+    "//ui/webui/resources/cr_elements/cr_lottie:cr_lottie.m",
+    "//ui/webui/resources/js:i18n_behavior.m",
+    "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
   extra_deps = [ ":setup_fingerprint_dialog_module" ]
 }
@@ -345,7 +439,11 @@
 js_library("setup_pin_dialog.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_people_page/setup_pin_dialog.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":lock_screen_password_prompt_dialog.m",
+    "../..:router.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/cr_components/chromeos/quick_unlock:setup_pin_keyboard.m",
+    "//ui/webui/resources/js:i18n_behavior.m",
   ]
   extra_deps = [ ":setup_pin_dialog_module" ]
 }
@@ -353,23 +451,38 @@
 js_library("user_list.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_people_page/user_list.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    "..:os_route.m",
+    "../..:router.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/cr_elements:cr_scrollable_behavior.m",
+    "//ui/webui/resources/js:i18n_behavior.m",
   ]
   extra_deps = [ ":user_list_module" ]
+  externs_list = [
+    "$externs_path/settings_private.js",
+    "$externs_path/users_private.js",
+  ]
 }
 
 js_library("users_add_user_dialog.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_people_page/users_add_user_dialog.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js:i18n_behavior.m",
   ]
   extra_deps = [ ":users_add_user_dialog_module" ]
+  externs_list = [ "$externs_path/users_private.js" ]
 }
 
 js_library("users_page.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_people_page/users_page.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":user_list.m",
+    ":users_add_user_dialog.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js/cr/ui:focus_without_ink.m",
   ]
   extra_deps = [ ":users_page_module" ]
 }
@@ -392,6 +505,7 @@
     ":user_list_module",
     ":users_add_user_dialog_module",
     ":users_page_module",
+    "../../people_page:modulize",
   ]
 }
 
@@ -399,82 +513,121 @@
   js_file = "account_manager.js"
   html_file = "account_manager.html"
   html_type = "dom-module"
+  migrated_imports = settings_migrated_imports
+  namespace_rewrites = os_settings_namespace_rewrites
+  auto_imports = os_settings_auto_imports
 }
 
 polymer_modulizer("fingerprint_list") {
   js_file = "fingerprint_list.js"
   html_file = "fingerprint_list.html"
   html_type = "dom-module"
+  migrated_imports = os_settings_migrated_imports
+  namespace_rewrites = os_settings_namespace_rewrites
+  auto_imports = os_settings_auto_imports
 }
 
 polymer_modulizer("kerberos_accounts") {
   js_file = "kerberos_accounts.js"
   html_file = "kerberos_accounts.html"
   html_type = "dom-module"
+  migrated_imports = os_settings_migrated_imports
+  namespace_rewrites = os_settings_namespace_rewrites
+  auto_imports = os_settings_auto_imports
 }
 
 polymer_modulizer("kerberos_add_account_dialog") {
   js_file = "kerberos_add_account_dialog.js"
   html_file = "kerberos_add_account_dialog.html"
   html_type = "dom-module"
+  migrated_imports = os_settings_migrated_imports
+  namespace_rewrites = os_settings_namespace_rewrites
+  auto_imports =
+      os_settings_auto_imports +
+      [ "ui/webui/resources/html/assert.html|assert,assertNotReached" ]
 }
 
 polymer_modulizer("lock_screen") {
   js_file = "lock_screen.js"
   html_file = "lock_screen.html"
   html_type = "dom-module"
+  migrated_imports = os_settings_migrated_imports
+  namespace_rewrites = os_settings_namespace_rewrites
+  auto_imports = os_settings_auto_imports
 }
 
 polymer_modulizer("lock_screen_password_prompt_dialog") {
   js_file = "lock_screen_password_prompt_dialog.js"
   html_file = "lock_screen_password_prompt_dialog.html"
   html_type = "dom-module"
+  migrated_imports = os_settings_migrated_imports
+  namespace_rewrites = os_settings_namespace_rewrites
+  auto_imports = os_settings_auto_imports
 }
 
 polymer_modulizer("os_people_page") {
   js_file = "os_people_page.js"
   html_file = "os_people_page.html"
   html_type = "dom-module"
+  migrated_imports = os_settings_migrated_imports
+  namespace_rewrites = os_settings_namespace_rewrites
+  auto_imports = os_settings_auto_imports
 }
 
 polymer_modulizer("os_sync_controls") {
   js_file = "os_sync_controls.js"
   html_file = "os_sync_controls.html"
   html_type = "dom-module"
+  migrated_imports = os_settings_migrated_imports
+  namespace_rewrites = os_settings_namespace_rewrites
+  auto_imports = os_settings_auto_imports
 }
 
 polymer_modulizer("setup_fingerprint_dialog") {
   js_file = "setup_fingerprint_dialog.js"
   html_file = "setup_fingerprint_dialog.html"
   html_type = "dom-module"
+  migrated_imports = os_settings_migrated_imports
+  namespace_rewrites = os_settings_namespace_rewrites
+  auto_imports = os_settings_auto_imports
 }
 
 polymer_modulizer("setup_pin_dialog") {
   js_file = "setup_pin_dialog.js"
   html_file = "setup_pin_dialog.html"
   html_type = "dom-module"
+  migrated_imports = os_settings_migrated_imports
+  namespace_rewrites = os_settings_namespace_rewrites
+  auto_imports = os_settings_auto_imports
 }
 
 polymer_modulizer("user_list") {
   js_file = "user_list.js"
   html_file = "user_list.html"
   html_type = "dom-module"
+  migrated_imports = os_settings_migrated_imports
+  namespace_rewrites = os_settings_namespace_rewrites
+  auto_imports = os_settings_auto_imports
 }
 
 polymer_modulizer("users_add_user_dialog") {
   js_file = "users_add_user_dialog.js"
   html_file = "users_add_user_dialog.html"
   html_type = "dom-module"
+  migrated_imports = os_settings_migrated_imports
+  namespace_rewrites = os_settings_namespace_rewrites
+  auto_imports = os_settings_auto_imports
 }
 
 polymer_modulizer("users_page") {
   js_file = "users_page.js"
   html_file = "users_page.html"
   html_type = "dom-module"
+  migrated_imports = os_settings_migrated_imports
+  namespace_rewrites = os_settings_namespace_rewrites
+  auto_imports = os_settings_auto_imports
 }
 
-import("//ui/webui/resources/tools/js_modulizer.gni")
-
 js_modulizer("modulize") {
   input_files = [
     "fingerprint_browser_proxy.js",
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/account_manager.html b/chrome/browser/resources/settings/chromeos/os_people_page/account_manager.html
index 58f176e..69acbafd 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/account_manager.html
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/account_manager.html
@@ -7,12 +7,12 @@
 <link rel="import" href="chrome://resources/cr_elements/policy/cr_tooltip_icon.html">
 <link rel="import" href="chrome://resources/html/i18n_behavior.html">
 <link rel="import" href="chrome://resources/html/icon.html">
-<link rel="import" href="chrome://resources/html/util.html">
 <link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
 <link rel="import" href="../localized_link/localized_link.html">
 <link rel="import" href="../../i18n_setup.html">
 <link rel="import" href="../os_route.html">
+<link rel="import" href="../../router.html">
 <link rel="import" href="../../settings_shared_css.html">
 <link rel="import" href="../../people_page/account_manager_browser_proxy.html">
 
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/fingerprint_browser_proxy.js b/chrome/browser/resources/settings/chromeos/os_people_page/fingerprint_browser_proxy.js
index 6c81026..97b276f 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/fingerprint_browser_proxy.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/fingerprint_browser_proxy.js
@@ -2,13 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {addSingletonGetter, sendWithPromise} from 'chrome://resources/js/cr.m.js';
+// clang-format on
+
 cr.define('settings', function() {
   /**
    * @enum {number}
    * These values must be kept in sync with the values in
    * third_party/cros_system_api/dbus/service_constants.h.
    */
-  const FingerprintResultType = {
+  /* #export */ const FingerprintResultType = {
     SUCCESS: 0,
     PARTIAL: 1,
     INSUFFICIENT: 2,
@@ -26,7 +30,7 @@
    *   indexes: !Array<number>,
    * }}
    */
-  let FingerprintAttempt;
+  /* #export */ let FingerprintAttempt;
 
   /**
    * An object describing a scan from the fingerprint hardware. The structure of
@@ -37,7 +41,7 @@
    *   percentComplete: number,
    * }}
    */
-  let FingerprintScan;
+  /* #export */ let FingerprintScan;
 
   /**
    * An object describing the necessary info to display on the fingerprint
@@ -48,10 +52,10 @@
    *   isMaxed: boolean,
    * }}
    */
-  let FingerprintInfo;
+  /* #export */ let FingerprintInfo;
 
   /** @interface */
-  class FingerprintBrowserProxy {
+  /* #export */ class FingerprintBrowserProxy {
     /**
      * @return {!Promise<!settings.FingerprintInfo>}
      */
@@ -102,7 +106,7 @@
   /**
    * @implements {settings.FingerprintBrowserProxy}
    */
-  class FingerprintBrowserProxyImpl {
+  /* #export */ class FingerprintBrowserProxyImpl {
     /** @override */
     getFingerprintsList() {
       return cr.sendWithPromise('getFingerprintsList');
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_accounts.html b/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_accounts.html
index eccad61..7c2ae9b 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_accounts.html
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_accounts.html
@@ -5,8 +5,8 @@
 <link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_toast/cr_toast.html">
 <link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_indicator.html">
+<link rel="import" href="chrome://resources/html/i18n_behavior.html">
 <link rel="import" href="chrome://resources/html/icon.html">
-<link rel="import" href="chrome://resources/html/util.html">
 <link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
 <link rel="import" href="../../i18n_setup.html">
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_accounts_browser_proxy.js b/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_accounts_browser_proxy.js
index 3e962d49..6f63111 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_accounts_browser_proxy.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_accounts_browser_proxy.js
@@ -2,6 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {addSingletonGetter, sendWithPromise} from 'chrome://resources/js/cr.m.js';
+// clang-format on
+
 /**
  * @fileoverview A helper object used from the "Kerberos Accounts" subsection of
  * the "People" section of Settings, to interact with the browser. Chrome OS
@@ -22,7 +26,7 @@
    *   validForDuration: string
    * }}
    */
-  let KerberosAccount;
+  /* #export */ let KerberosAccount;
 
   /**
    * @typedef {{
@@ -33,14 +37,14 @@
    *   }
    * }}
    */
-  let ValidateKerberosConfigResult;
+  /* #export */ let ValidateKerberosConfigResult;
 
   /**
    *  @enum {number}
    *  These values must be kept in sync with the ErrorType enum in
    *  third_party/cros_system_api/dbus/kerberos/kerberos_service.proto.
    */
-  const KerberosErrorType = {
+  /* #export */ const KerberosErrorType = {
     kNone: 0,
     kUnknown: 1,
     kDBusFailure: 2,
@@ -70,7 +74,7 @@
    *  These values must be kept in sync with the KerberosConfigErrorCode enum in
    *  third_party/cros_system_api/dbus/kerberos/kerberos_service.proto.
    */
-  const KerberosConfigErrorCode = {
+  /* #export */ const KerberosConfigErrorCode = {
     kNone: 0,
     kSectionNestedInGroup: 1,
     kSectionSyntax: 2,
@@ -83,7 +87,7 @@
   };
 
   /** @interface */
-  class KerberosAccountsBrowserProxy {
+  /* #export */ class KerberosAccountsBrowserProxy {
     /**
      * Returns a Promise for the list of Kerberos accounts held in the kerberosd
      * system daemon.
@@ -129,7 +133,7 @@
   /**
    * @implements {settings.KerberosAccountsBrowserProxy}
    */
-  class KerberosAccountsBrowserProxyImpl {
+  /* #export */ class KerberosAccountsBrowserProxyImpl {
     /** @override */
     getAccounts() {
       return cr.sendWithPromise('getKerberosAccounts');
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_add_account_dialog.html b/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_add_account_dialog.html
index d63002340..5342e11 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_add_account_dialog.html
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_add_account_dialog.html
@@ -10,6 +10,7 @@
 <link rel="import" href="chrome://resources/html/action_link.html">
 <link rel="import" href="chrome://resources/html/i18n_behavior.html">
 <link rel="import" href="chrome://resources/html/load_time_data.html">
+<link rel="import" href="chrome://resources/html/assert.html">
 <link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
 <link rel="import" href="../../controls/settings_textarea.html">
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/lock_state_behavior.js b/chrome/browser/resources/settings/chromeos/os_people_page/lock_state_behavior.js
index 4e17698..9ba6619 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/lock_state_behavior.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/lock_state_behavior.js
@@ -9,7 +9,7 @@
  */
 
 /** @enum {string} */
-const LockScreenUnlockType = {
+/* #export */ const LockScreenUnlockType = {
   VALUE_PENDING: 'value_pending',
   PASSWORD: 'password',
   PIN_PASSWORD: 'pin+password'
@@ -29,7 +29,7 @@
 let cachedHasPinLogin = undefined;
 
 /** @polymerBehavior */
-const LockStateBehaviorImpl = {
+/* #export */ const LockStateBehaviorImpl = {
   properties: {
     /**
      * The currently selected unlock type.
@@ -145,5 +145,5 @@
 };
 
 /** @polymerBehavior */
-const LockStateBehavior =
+/* #export */ const LockStateBehavior =
     [I18nBehavior, WebUIListenerBehavior, LockStateBehaviorImpl];
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/os_people_page.html b/chrome/browser/resources/settings/chromeos/os_people_page/os_people_page.html
index 47d97d4..550dbe8 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/os_people_page.html
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/os_people_page.html
@@ -10,7 +10,6 @@
 <link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
 <link rel="import" href="chrome://resources/html/i18n_behavior.html">
 <link rel="import" href="chrome://resources/html/icon.html">
-<link rel="import" href="chrome://resources/html/util.html">
 <link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
 <link rel="import" href="../../controls/settings_toggle_button.html">
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_browser_proxy.js b/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_browser_proxy.js
index 46e7848..0b7c7947 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_browser_proxy.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_browser_proxy.js
@@ -2,6 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
+// clang-format on
+
 cr.define('settings', function() {
   /**
    * User preferences for OS sync. 'Registered' means the user has the option to
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/setup_fingerprint_dialog.js b/chrome/browser/resources/settings/chromeos/os_people_page/setup_fingerprint_dialog.js
index 8e87830..8554a23 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/setup_fingerprint_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/setup_fingerprint_dialog.js
@@ -7,7 +7,7 @@
    * The steps in the fingerprint setup flow.
    * @enum {number}
    */
-  const FingerprintSetupStep = {
+  /* #export */ const FingerprintSetupStep = {
     LOCATE_SCANNER: 1,  // The user needs to locate the scanner.
     MOVE_FINGER: 2,     // The user needs to move finger around the scanner.
     READY: 3            // The scanner has read the fingerprint successfully.
@@ -19,7 +19,7 @@
    * /chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.h
    * @enum {number}
    */
-  const FingerprintLocation = {
+  /* #export */ const FingerprintLocation = {
     TABLET_POWER_BUTTON: 0,
     KEYBOARD_BOTTOM_LEFT: 1,
     KEYBOARD_BOTTOM_RIGHT: 2,
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/users_add_user_dialog.html b/chrome/browser/resources/settings/chromeos/os_people_page/users_add_user_dialog.html
index bae9bad..e4cc5bf 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/users_add_user_dialog.html
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/users_add_user_dialog.html
@@ -3,6 +3,7 @@
 <link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
+<link rel="import" href="chrome://resources/html/i18n_behavior.html">
 <link rel="import" href="chrome://resources/html/assert.html">
 <link rel="import" href="../../settings_shared_css.html">
 <link rel="import" href="../../settings_vars_css.html">
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.gni b/chrome/browser/resources/settings/chromeos/os_settings.gni
index 9380471..6c3a5b6 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.gni
+++ b/chrome/browser/resources/settings/chromeos/os_settings.gni
@@ -4,37 +4,57 @@
 
 import("//third_party/closure_compiler/compile_js.gni")
 import("//ui/webui/resources/cr_components/chromeos/os_cr_components.gni")
+import("//ui/webui/resources/cr_elements/chromeos/os_cr_elements.gni")
 import("../settings.gni")
 
 os_settings_namespace_rewrites =
-    settings_namespace_rewrites + cr_components_chromeos_namespace_rewrites + [
-      "settings.OsResetBrowserProxy|OsResetBrowserProxy",
+    settings_namespace_rewrites + cr_components_chromeos_namespace_rewrites +
+    cr_elements_chromeos_namespace_rewrites +
+    [
+      "settings.AccountManagerBrowserProxy|AccountManagerBrowserProxy",
+      "settings.AmbientModeBrowserProxy|AmbientModeBrowserProxy",
+      "settings.ChangePictureBrowserProxy|ChangePictureBrowserProxy",
+      "settings.DefaultImage|DefaultImage",
+      "settings.KerberosAccountsBrowserProxy|KerberosAccountsBrowserProxy",
+      "settings.KerberosErrorType|KerberosErrorType",
+      "settings.KerberosConfigErrorCode|KerberosConfigErrorCode",
       "settings.MultiDeviceBrowserProxy|MultiDeviceBrowserProxy",
-      "settings.MultiDeviceBrowserProxyImpl|MultiDeviceBrowserProxyImpl",
       "settings.MultiDeviceSettingsMode|MultiDeviceSettingsMode",
       "settings.MultiDeviceFeature|MultiDeviceFeature",
       "settings.MultiDeviceFeatureState|MultiDeviceFeatureState",
       "settings.MultiDevicePageContentData|MultiDevicePageContentData",
+      "settings.OsResetBrowserProxy|OsResetBrowserProxy",
       "settings.RouteObserverBehavior|RouteObserverBehavior",
       "settings.Route|Route",
+      "settings.routes|routes",
       "settings.recordSettingChange|recordSettingChange",
       "settings.SmartLockSignInEnabledState|SmartLockSignInEnabledState",
+      "settings.WallpaperBrowserProxy|WallpaperBrowserProxy",
     ]
 
 os_settings_auto_imports =
-    settings_auto_imports + cr_components_chromeos_auto_imports + [
+    settings_auto_imports + cr_components_chromeos_auto_imports +
+    cr_elements_chromeos_auto_imports + [
+      "chrome/browser/resources/settings/chromeos/ambient_mode_page/constants.html|AmbientModeTopicSource,AmbientModeSettings",
+      "chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_browser_proxy.html|AmbientModeBrowserProxy,AmbientModeBrowserProxyImpl",
       "chrome/browser/resources/settings/chromeos/metrics_recorder.html|recordSettingChange",
       "chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_constants.html|MultiDeviceSettingsMode,MultiDeviceFeature,MultiDeviceFeatureState,MultiDevicePageContentData,SmartLockSignInEnabledState",
       "chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_behavior.html|MultiDeviceFeatureBehavior",
       "chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_browser_proxy.html|MultiDeviceBrowserProxy,MultiDeviceBrowserProxyImpl",
+      "chrome/browser/resources/settings/chromeos/os_people_page/kerberos_accounts_browser_proxy.html|KerberosAccount,KerberosAccountsBrowserProxyImpl,KerberosAccountsBrowserProxy,KerberosErrorType,KerberosConfigErrorCode,ValidateKerberosConfigResult",
+      "chrome/browser/resources/settings/chromeos/os_people_page/os_sync_browser_proxy.html|OsSyncBrowserProxy,OsSyncBrowserProxyImpl,OsSyncPrefs",
       "chrome/browser/resources/settings/chromeos/os_reset_page/os_reset_browser_proxy.html|OsResetBrowserProxy,OsResetBrowserProxyImpl",
+      "chrome/browser/resources/settings/chromeos/os_route.html|routes",
       "chrome/browser/resources/settings/chromeos/os_settings_routes.html|OsSettingsRoutes",
+      "chrome/browser/resources/settings/chromeos/personalization_page/change_picture_browser_proxy.html|ChangePictureBrowserProxy,ChangePictureBrowserProxyImpl,DefaultImage",
+      "chrome/browser/resources/settings/chromeos/personalization_page/wallpaper_browser_proxy.html|WallpaperBrowserProxy,WallpaperBrowserProxyImpl",
       "chrome/browser/resources/settings/chromeos/route_origin_behavior.html|RouteOriginBehaviorImpl,RouteOriginBehavior",
       "chrome/browser/resources/settings/lifetime_browser_proxy.html|LifetimeBrowserProxy,LifetimeBrowserProxyImpl",
-      "chrome/browser/resources/settings/chromeos/os_route.html|routes",
+      "chrome/browser/resources/settings/people_page/account_manager_browser_proxy.html|AccountManagerBrowserProxy,AccountManagerBrowserProxyImpl",
       "chrome/browser/resources/settings/route.html|routes",
       "chrome/browser/resources/settings/router.html|Router,Route,RouteObserverBehavior",
       "ui/webui/resources/html/polymer.html|Polymer,html,flush",
+      "ui/webui/resources/html/icon.html|getImage",
     ]
 
 os_settings_migrated_imports = settings_migrated_imports
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.js b/chrome/browser/resources/settings/chromeos/os_settings.js
index 0d2e48e..90adcc82 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings.js
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import './ambient_mode_page/ambient_mode_page.m.js';
 import './os_reset_page/os_powerwash_dialog.m.js';
 import './os_reset_page/os_reset_page.m.js';
 import './localized_link/localized_link.m.js';
@@ -10,13 +11,24 @@
 import './bluetooth_page/bluetooth_device_list_item.m.js';
 import '../nearby_share_page/nearby_share_subpage.m.js';
 import './multidevice_page/multidevice_page.m.js';
+import '../prefs/prefs.m.js';
+import './personalization_page/personalization_page.m.js';
+import './personalization_page/change_picture.m.js';
+import './os_people_page/account_manager.m.js';
+import './os_people_page/kerberos_accounts.m.js';
 
 // clang-format off
+export {AmbientModeBrowserProxyImpl} from './ambient_mode_page/ambient_mode_browser_proxy.m.js';
+export {CrSettingsPrefs} from '../prefs/prefs_types.m.js';
+export {AccountManagerBrowserProxy,AccountManagerBrowserProxyImpl} from '../people_page/account_manager_browser_proxy.m.js';
 export {LifetimeBrowserProxy, LifetimeBrowserProxyImpl} from '../lifetime_browser_proxy.m.js';
 export {bluetoothApis} from './bluetooth_page/bluetooth_page.m.js';
 export {OsResetBrowserProxyImpl} from './os_reset_page/os_reset_browser_proxy.m.js';
 export {MultiDeviceSettingsMode, MultiDeviceFeature, MultiDeviceFeatureState, MultiDevicePageContentData, SmartLockSignInEnabledState} from './multidevice_page/multidevice_constants.m.js';
 export {MultiDeviceBrowserProxy, MultiDeviceBrowserProxyImpl} from './multidevice_page/multidevice_browser_proxy.m.js';
+export {KerberosAccountsBrowserProxyImpl, KerberosConfigErrorCode,KerberosErrorType} from './os_people_page/kerberos_accounts_browser_proxy.m.js';
 export {Route, Router} from '../router.m.js';
 export {routes} from './os_route.m.js';
-// clang-format off
+export {WallpaperBrowserProxyImpl} from './personalization_page/wallpaper_browser_proxy.m.js';
+export {ChangePictureBrowserProxy,ChangePictureBrowserProxyImpl} from './personalization_page/change_picture_browser_proxy.m.js';
+// clang-format on
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_resources_v3.grdp b/chrome/browser/resources/settings/chromeos/os_settings_resources_v3.grdp
index cb6ad09..0546b448 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_resources_v3.grdp
+++ b/chrome/browser/resources/settings/chromeos/os_settings_resources_v3.grdp
@@ -1,7 +1,89 @@
 <?xml version="1.0" encoding="utf-8"?>
 <grit-part>
-  <include name="IDR_OS_SETTINGS_RESET_BROWSER_PROXY_M_JS"
-           file="${root_gen_dir}/chrome/browser/resources/settings/chromeos/os_reset_page/os_reset_browser_proxy.m.js"
+  <include name="IDR_OS_SETTINGS_TOPIC_SOURCE_ITEM_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_item.m.js"
+           use_base_dir="false"
+           compress="false"
+           type="BINDATA" />
+  <include name="IDR_OS_SETTINGS_TOPIC_SOURCE_LIST_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_list.m.js"
+           use_base_dir="false"
+           compress="false"
+           type="BINDATA" />
+  <include name="IDR_OS_SETTINGS_AMBIENT_MODE_PAGE_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.m.js"
+           use_base_dir="false"
+           compress="false"
+           type="BINDATA" />
+  <include name="IDR_OS_SETTINGS_AMBIENT_MODE_PHOTOS_PAGE_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_photos_page.m.js"
+           use_base_dir="false"
+           compress="false"
+           type="BINDATA" />
+  <include name="IDR_OS_SETTINGS_PERSONALIZATION_PAGE_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/chromeos/personalization_page/personalization_page.m.js"
+           use_base_dir="false"
+           compress="false"
+           type="BINDATA" />
+  <include name="IDR_OS_SETTINGS_WALLPAPER_PROXY_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/chromeos/personalization_page/wallpaper_browser_proxy.m.js"
+           use_base_dir="false"
+           compress="false"
+           type="BINDATA" />
+  <include name="IDR_OS_SETTINGS_PERSONALIZATION_PAGE_CHANGE_PICTURE_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/chromeos/personalization_page/change_picture.m.js"
+           use_base_dir="false"
+           compress="false"
+           preprocess="true"
+           type="BINDATA" />
+  <include name="IDR_OS_SETTINGS_PERSONALIZATION_PAGE_CHANGE_PICTURE_BROWSER_PROXY_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/chromeos/personalization_page/change_picture_browser_proxy.m.js"
+           use_base_dir="false"
+           compress="false"
+           preprocess="true"
+           type="BINDATA" />
+  <include name="IDR_OS_SETTINGS_AMBIENT_MODE_BROWSER_PROXY_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_browser_proxy.m.js"
+           use_base_dir="false"
+           compress="false"
+           type="BINDATA" />
+  <include name="IDR_OS_SETTINGS_AMBIENT_MODE_CONSTANTS_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/chromeos/ambient_mode_page/constants.m.js"
+           use_base_dir="false"
+           compress="false"
+           type="BINDATA" />
+  <include name="IDR_OS_SETTINGS_SETTINGS_RADIO_GROUP_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/controls/settings_radio_group.m.js"
+           use_base_dir="false"
+           compress="false"
+           type="BINDATA" />
+  <include name="IDR_OS_SETTINGS_SETTINGS_TEXTAREA_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/controls/settings_textarea.m.js"
+           use_base_dir="false"
+           compress="false"
+           type="BINDATA" />
+  <include name="IDR_OS_SETTINGS_PEOPLE_PAGE_ACCOUNT_MANAGER_BROWSER_PROXY_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/people_page/account_manager_browser_proxy.m.js"
+           use_base_dir="false"
+           compress="false"
+           type="BINDATA" />
+  <include name="IDR_OS_SETTINGS_PEOPLE_PAGE_KERBEROS_ACCOUNTS_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_accounts.m.js"
+           use_base_dir="false"
+           compress="false"
+           type="BINDATA" />
+  <include name="IDR_OS_SETTINGS_PEOPLE_PAGE_KERBEROS_ACCOUNTS_BROWSER_PROXY_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_accounts_browser_proxy.m.js"
+           use_base_dir="false"
+           compress="false"
+           type="BINDATA" />
+  <include name="IDR_OS_SETTINGS_PEOPLE_PAGE_KERBEROS_ADD_ACCOUNT_DIALOG_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_add_account_dialog.m.js"
+           use_base_dir="false"
+           compress="false"
+           type="BINDATA" />
+  <include name="IDR_OS_SETTINGS_PEOPLE_PAGE_ACCOUNT_MANAGER_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/chromeos/os_people_page/account_manager.m.js"
            use_base_dir="false"
            compress="false"
            type="BINDATA" />
@@ -15,6 +97,11 @@
            use_base_dir="false"
            compress="false"
            type="BINDATA" />
+  <include name="IDR_OS_SETTINGS_RESET_BROWSER_PROXY_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/chromeos/os_reset_page/os_reset_browser_proxy.m.js"
+           use_base_dir="false"
+           compress="false"
+           type="BINDATA" />
   <include name="IDR_OS_SETTINGS_BLUETOOTH_PAGE_M_JS"
            file="${root_gen_dir}/chrome/browser/resources/settings/chromeos/bluetooth_page/bluetooth_page.m.js"
            use_base_dir="false"
@@ -151,6 +238,10 @@
            use_base_dir="false"
            compress="false"
            type="BINDATA"/>
+  <include name="IDR_OS_SETTINGS_PREFS_PREF_UTIL_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/prefs/pref_util.m.js"
+           use_base_dir="false"
+           compress="false" type="BINDATA" />
   <include name="IDR_OS_SETTINGS_PREFS_BEHAVIOR_M_JS"
            file="${root_gen_dir}/chrome/browser/resources/settings/prefs/prefs_behavior.m.js"
            use_base_dir="false"
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.html b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.html
index 9ab921c..33dc858 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.html
+++ b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.html
@@ -22,6 +22,7 @@
         --cr-toolbar-focused-min-height: 40px;
         --cr-toolbar-icon-container-size: 32px;
         --cr-toolbar-icon-margin: 8px 16px;
+        --cr-toolbar-search-field-narrow-mode-prompt-opacity: 0.7;
         --cr-toolbar-search-icon-margin-inline-start: 16px;
         --cr-toolbar-query-exists-min-height:
             var(--cr-toolbar-focused-min-height);
@@ -32,6 +33,12 @@
         transition: width 150ms cubic-bezier(0.4, 0, 0.2, 1);
       }
 
+      @media (prefers-color-scheme: dark) {
+        :host {
+          --cr-toolbar-search-field-narrow-mode-prompt-opacity: 1;
+        }
+      }
+
       /* Only search icon is visible in this mode */
       :host([narrow]:not([showing-search])) {
         /* Row flex keeps magnifying glass on the rightmost edge. */
@@ -50,12 +57,16 @@
         --cr-toolbar-search-field-term-margin: 0;
         --cr-toolbar-search-field-border-radius: 20px;
         --cr-toolbar-search-field-paper-spinner-margin: 0 12px;
-        --cr-toolbar-search-field-prompt-margin-inline-start: 140px;
         --cr-toolbar-search-field-input-text-color:
             var(--cr-primary-text-color);
         font-size: 13px;
       }
 
+      :host([showing-search]) cr-toolbar-search-field {
+        background:
+            var(--cr-toolbar-search-field-background, rgba(0, 0, 0, 0.22));
+      }
+
       :host(:focus-within[showing-search]) cr-toolbar-search-field {
         --cr-toolbar-search-field-background: var(--cr-card-background-color);
         box-shadow: var(--cr-elevation-1);
diff --git a/chrome/browser/resources/settings/chromeos/personalization_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/personalization_page/BUILD.gn
index 766826b..dddafc3 100644
--- a/chrome/browser/resources/settings/chromeos/personalization_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/personalization_page/BUILD.gn
@@ -3,6 +3,9 @@
 # found in the LICENSE file.
 
 import("//third_party/closure_compiler/compile_js.gni")
+import("//tools/polymer/polymer.gni")
+import("//ui/webui/resources/tools/js_modulizer.gni")
+import("../os_settings.gni")
 
 js_type_check("closure_compile") {
   deps = [
@@ -17,14 +20,15 @@
     ":change_picture_browser_proxy",
     "..:metrics_recorder",
     "..:os_route",
+    "//third_party/polymer/v1_0/components-chromium/iron-a11y-announcer:iron-a11y-announcer-extracted",
     "//third_party/polymer/v1_0/components-chromium/iron-selector:iron-selector-extracted",
     "//ui/webui/resources/cr_elements/chromeos/cr_picture:cr_picture_list",
     "//ui/webui/resources/cr_elements/chromeos/cr_picture:cr_picture_pane",
     "//ui/webui/resources/cr_elements/chromeos/cr_picture:cr_picture_types",
     "//ui/webui/resources/cr_elements/chromeos/cr_picture:png",
+    "//ui/webui/resources/js:assert",
     "//ui/webui/resources/js:i18n_behavior",
     "//ui/webui/resources/js:load_time_data",
-    "//ui/webui/resources/js:util",
     "//ui/webui/resources/js:web_ui_listener_behavior",
   ]
 }
@@ -50,51 +54,65 @@
   externs_list = [ "$externs_path/chrome_send.js" ]
 }
 
-# TODO: Uncomment as the Polymer3 migration makes progress.
-#js_type_check("closure_compile_module") {
-#  is_polymer3 = true
-#  deps = [
-#    ":change_picture.m",
-#    ":change_picture_browser_proxy.m",
-#    ":personalization_page.m",
-#    ":wallpaper_browser_proxy.m"
-#  ]
-#}
+js_type_check("closure_compile_module") {
+  is_polymer3 = true
+  deps = [
+    ":change_picture.m",
+    ":change_picture_browser_proxy.m",
+    ":personalization_page.m",
+    ":wallpaper_browser_proxy.m",
+  ]
+}
 
 js_library("change_picture.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/personalization_page/change_picture.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":change_picture_browser_proxy.m",
+    "..:metrics_recorder.m",
+    "..:os_route.m",
+    "../..:router.m",
+    "//third_party/polymer/v3_0/components-chromium/iron-a11y-announcer:iron-a11y-announcer",
+    "//third_party/polymer/v3_0/components-chromium/iron-selector:iron-selector",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/cr_elements/chromeos/cr_picture:cr_picture_list.m",
+    "//ui/webui/resources/cr_elements/chromeos/cr_picture:cr_picture_pane.m",
+    "//ui/webui/resources/cr_elements/chromeos/cr_picture:cr_picture_types.m",
+    "//ui/webui/resources/cr_elements/chromeos/cr_picture:png.m",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js:i18n_behavior.m",
+    "//ui/webui/resources/js:load_time_data.m",
+    "//ui/webui/resources/js:util.m",
+    "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
   extra_deps = [ ":change_picture_module" ]
 }
 
 js_library("change_picture_browser_proxy.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/personalization_page/change_picture_browser_proxy.m.js" ]
-  deps = [
-    # TODO: Fill those in.
-  ]
   extra_deps = [ ":modulize" ]
 }
 
 js_library("personalization_page.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/personalization_page/personalization_page.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":wallpaper_browser_proxy.m",
+    "..:os_route.m",
+    "../..:router.m",
+    "../../prefs:prefs.m",
+    "../../settings_page:settings_animated_pages.m",
+    "../ambient_mode_page:ambient_mode_browser_proxy.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:load_time_data.m",
   ]
   extra_deps = [ ":personalization_page_module" ]
 }
 
 js_library("wallpaper_browser_proxy.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/personalization_page/wallpaper_browser_proxy.m.js" ]
-  deps = [
-    # TODO: Fill those in.
-  ]
+  externs_list = [ "$externs_path/chrome_send.js" ]
   extra_deps = [ ":modulize" ]
 }
 
-import("//tools/polymer/polymer.gni")
-
 group("polymer3_elements") {
   public_deps = [
     ":change_picture_module",
@@ -107,19 +125,28 @@
   js_file = "change_picture.js"
   html_file = "change_picture.html"
   html_type = "dom-module"
+  migrated_imports = os_settings_migrated_imports
+  namespace_rewrites = os_settings_namespace_rewrites +
+                       [ "Polymer.IronA11yAnnouncer|IronA11yAnnouncer" ]
+  auto_imports = os_settings_auto_imports + [
+                   "ui/webui/resources/html/assert.html|assertNotReached",
+                   "ui/webui/resources/polymer/v3_0/iron-a11y-announcer/iron-a11y-announcer.html|IronA11yAnnouncer",
+                 ]
 }
 
 polymer_modulizer("personalization_page") {
   js_file = "personalization_page.js"
   html_file = "personalization_page.html"
   html_type = "dom-module"
+  migrated_imports = os_settings_migrated_imports
+  namespace_rewrites = os_settings_namespace_rewrites
+  auto_imports = os_settings_auto_imports
 }
 
-import("//ui/webui/resources/tools/js_modulizer.gni")
-
 js_modulizer("modulize") {
   input_files = [
     "change_picture_browser_proxy.js",
     "wallpaper_browser_proxy.js",
   ]
+  namespace_rewrites = os_settings_namespace_rewrites
 }
diff --git a/chrome/browser/resources/settings/chromeos/personalization_page/change_picture.html b/chrome/browser/resources/settings/chromeos/personalization_page/change_picture.html
index b297915..5f18549 100644
--- a/chrome/browser/resources/settings/chromeos/personalization_page/change_picture.html
+++ b/chrome/browser/resources/settings/chromeos/personalization_page/change_picture.html
@@ -3,12 +3,14 @@
 <link rel="import" href="chrome://resources/cr_elements/chromeos/cr_picture/cr_picture_list.html">
 <link rel="import" href="chrome://resources/cr_elements/chromeos/cr_picture/cr_picture_pane.html">
 <link rel="import" href="chrome://resources/cr_elements/chromeos/cr_picture/cr_picture_types.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-announcer/iron-a11y-announcer.html">
 <link rel="import" href="chrome://resources/cr_elements/chromeos/cr_picture/png.html">
 <link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/util.html">
 <link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
+<link rel="import" href="chrome://resources/html/assert.html">
 <link rel="import" href="../../i18n_setup.html">
 <link rel="import" href="../os_route.html">
+<link rel="import" href="../../router.html">
 <link rel="import" href="../../settings_shared_css.html">
 <link rel="import" href="change_picture_browser_proxy.html">
 <link rel="import" href="../metrics_recorder.html">
diff --git a/chrome/browser/resources/settings/chromeos/personalization_page/change_picture.js b/chrome/browser/resources/settings/chromeos/personalization_page/change_picture.js
index 55b2a7c..3fd0f2d 100644
--- a/chrome/browser/resources/settings/chromeos/personalization_page/change_picture.js
+++ b/chrome/browser/resources/settings/chromeos/personalization_page/change_picture.js
@@ -104,6 +104,9 @@
         'profile-image-changed', this.receiveProfileImage_.bind(this));
     this.addWebUIListener(
         'camera-presence-changed', this.receiveCameraPresence_.bind(this));
+
+    // Initialize the announcer once.
+    Polymer.IronA11yAnnouncer.requestAvailability();
   },
 
 
@@ -233,8 +236,9 @@
     this.browserProxy_.photoTaken(event.detail.photoDataUrl);
     this.pictureList_.setOldImageUrl(event.detail.photoDataUrl);
     this.pictureList_.setFocus();
-    announceAccessibleMessage(
-        loadTimeData.getString('photoCaptureAccessibleText'));
+    this.fire(
+        'iron-announce',
+        {text: loadTimeData.getString('photoCaptureAccessibleText')});
   },
 
   /**
@@ -243,8 +247,10 @@
    */
   onSwitchMode_(event) {
     const videomode = event.detail;
-    announceAccessibleMessage(this.i18n(
-        videomode ? 'videoModeAccessibleText' : 'photoModeAccessibleText'));
+    this.fire('iron-announce', {
+      text: this.i18n(
+          videomode ? 'videoModeAccessibleText' : 'photoModeAccessibleText')
+    });
   },
 
   /**
@@ -267,7 +273,7 @@
     this.pictureList_.setOldImageUrl(CrPicture.kDefaultImageUrl);
     // Revert to profile image as we don't know what last used default image is.
     this.browserProxy_.selectProfileImage();
-    announceAccessibleMessage(this.i18n('photoDiscardAccessibleText'));
+    this.fire('iron-announce', {text: this.i18n('photoDiscardAccessibleText')});
   },
 
   /**
diff --git a/chrome/browser/resources/settings/chromeos/personalization_page/change_picture_browser_proxy.js b/chrome/browser/resources/settings/chromeos/personalization_page/change_picture_browser_proxy.js
index 83ba35a..eb3a032 100644
--- a/chrome/browser/resources/settings/chromeos/personalization_page/change_picture_browser_proxy.js
+++ b/chrome/browser/resources/settings/chromeos/personalization_page/change_picture_browser_proxy.js
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// #import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
+
 cr.define('settings', function() {
   /**
    * An object describing a default image.
@@ -13,10 +15,10 @@
    *   website: (string|undefined)
    * }}
    */
-  let DefaultImage;
+  /* #export */ let DefaultImage;
 
   /** @interface */
-  class ChangePictureBrowserProxy {
+  /* #export */ class ChangePictureBrowserProxy {
     /**
      * Retrieves the initial set of default images, profile image, etc. As a
      * response, the C++ sends these WebUIListener events:
@@ -65,7 +67,7 @@
   /**
    * @implements {settings.ChangePictureBrowserProxy}
    */
-  class ChangePictureBrowserProxyImpl {
+  /* #export */ class ChangePictureBrowserProxyImpl {
     /** @override */
     initialize() {
       chrome.send('onChangePicturePageInitialized');
diff --git a/chrome/browser/resources/settings/chromeos/personalization_page/personalization_page.html b/chrome/browser/resources/settings/chromeos/personalization_page/personalization_page.html
index 7aeaed6..1597b4c9 100644
--- a/chrome/browser/resources/settings/chromeos/personalization_page/personalization_page.html
+++ b/chrome/browser/resources/settings/chromeos/personalization_page/personalization_page.html
@@ -7,6 +7,7 @@
 <link rel="import" href="change_picture.html">
 <link rel="import" href="../os_route.html">
 <link rel="import" href="../../router.html">
+<link rel="import" href="../../i18n_setup.html">
 <link rel="import" href="../../settings_page/settings_animated_pages.html">
 <link rel="import" href="../../settings_page/settings_subpage.html">
 <link rel="import" href="../../settings_shared_css.html">
diff --git a/chrome/browser/resources/settings/chromeos/personalization_page/personalization_page.js b/chrome/browser/resources/settings/chromeos/personalization_page/personalization_page.js
index afd03cf..da8c0b1 100644
--- a/chrome/browser/resources/settings/chromeos/personalization_page/personalization_page.js
+++ b/chrome/browser/resources/settings/chromeos/personalization_page/personalization_page.js
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-(function() {
-'use strict';
-
 /**
  * 'settings-personalization-page' is the settings page containing
  * personalization settings.
@@ -98,4 +95,3 @@
         toggleValue ? 'ambientModeEnabled' : 'ambientModeDisabled');
   },
 });
-})();
diff --git a/chrome/browser/resources/settings/chromeos/personalization_page/wallpaper_browser_proxy.js b/chrome/browser/resources/settings/chromeos/personalization_page/wallpaper_browser_proxy.js
index e299486..c4526d0 100644
--- a/chrome/browser/resources/settings/chromeos/personalization_page/wallpaper_browser_proxy.js
+++ b/chrome/browser/resources/settings/chromeos/personalization_page/wallpaper_browser_proxy.js
@@ -2,9 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {addSingletonGetter, sendWithPromise} from 'chrome://resources/js/cr.m.js';
+// clang-format on
+
 cr.define('settings', function() {
   /** @interface */
-  class WallpaperBrowserProxy {
+  /* #export */ class WallpaperBrowserProxy {
     /**
      * @return {!Promise<boolean>} Whether the wallpaper setting row should be
      *     visible.
@@ -22,7 +26,7 @@
   /**
    * @implements {settings.WallpaperBrowserProxy}
    */
-  class WallpaperBrowserProxyImpl {
+  /* #export */ class WallpaperBrowserProxyImpl {
     /** @override */
     isWallpaperSettingVisible() {
       return cr.sendWithPromise('isWallpaperSettingVisible');
diff --git a/chrome/browser/resources/settings/people_page/account_manager_browser_proxy.js b/chrome/browser/resources/settings/people_page/account_manager_browser_proxy.js
index 01c6e771e..988af02 100644
--- a/chrome/browser/resources/settings/people_page/account_manager_browser_proxy.js
+++ b/chrome/browser/resources/settings/people_page/account_manager_browser_proxy.js
@@ -24,7 +24,7 @@
   let Account;
 
   /** @interface */
-  class AccountManagerBrowserProxy {
+  /* #export */ class AccountManagerBrowserProxy {
     /**
      * Returns a Promise for the list of GAIA accounts held in AccountManager.
      * @return {!Promise<!Array<settings.Account>>}
diff --git a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.cc b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.cc
index b9a86fc..6a0a2bb 100644
--- a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.cc
+++ b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.cc
@@ -31,47 +31,18 @@
 ChromeEnterpriseRealTimeUrlLookupService::
     ~ChromeEnterpriseRealTimeUrlLookupService() = default;
 
-void ChromeEnterpriseRealTimeUrlLookupService::StartLookup(
-    const GURL& url,
-    RTLookupRequestCallback request_callback,
-    RTLookupResponseCallback response_callback) {
-  DCHECK(CurrentlyOnThread(ThreadID::UI));
-  DCHECK(url.is_valid());
-
-  // Check cache.
-  std::unique_ptr<RTLookupResponse> cache_response =
-      GetCachedRealTimeUrlVerdict(url);
-  if (cache_response) {
-    base::PostTask(FROM_HERE, CreateTaskTraits(ThreadID::IO),
-                   base::BindOnce(std::move(response_callback),
-                                  /* is_rt_lookup_successful */ true,
-                                  std::move(cache_response)));
-    return;
-  }
-
-  auto request = std::make_unique<RTLookupRequest>();
-  request->set_url(SanitizeURL(url).spec());
-  request->set_lookup_type(RTLookupRequest::NAVIGATION);
-  DCHECK(GetDMToken().is_valid());
-  request->set_dm_token(GetDMToken().value());
-
-  std::string req_data;
-  request->SerializeToString(&req_data);
-
-  SendRequestInternal(GetResourceRequest(), req_data, url,
-                      std::move(response_callback));
-
-  base::PostTask(FROM_HERE, CreateTaskTraits(ThreadID::IO),
-                 base::BindOnce(std::move(request_callback), std::move(request),
-                                /*access_token=*/""));
-}
-
 bool ChromeEnterpriseRealTimeUrlLookupService::CanPerformFullURLLookup() const {
   return RealTimePolicyEngine::CanPerformEnterpriseFullURLLookup(
       profile_->GetPrefs(), GetDMToken().is_valid(),
       profile_->IsOffTheRecord());
 }
 
+bool ChromeEnterpriseRealTimeUrlLookupService::
+    CanPerformFullURLLookupWithToken() const {
+  // URL lookup with token is disabled for enterprise users.
+  return false;
+}
+
 bool ChromeEnterpriseRealTimeUrlLookupService::CanCheckSubresourceURL() const {
   return false;
 }
@@ -80,6 +51,25 @@
   return safe_browsing::IsSafeBrowsingEnabled(*profile_->GetPrefs());
 }
 
+void ChromeEnterpriseRealTimeUrlLookupService::GetAccessToken(
+    const GURL& url,
+    RTLookupRequestCallback request_callback,
+    RTLookupResponseCallback response_callback) {
+  NOTREACHED() << "URL lookup with token is disabled for enterprise users.";
+}
+
+std::unique_ptr<RTLookupRequest>
+ChromeEnterpriseRealTimeUrlLookupService::FillRequestProto(const GURL& url) {
+  DCHECK(GetDMToken().is_valid())
+      << "Send a request only if the dm token is valid.";
+  auto request = std::make_unique<RTLookupRequest>();
+  request->set_url(SanitizeURL(url).spec());
+  request->set_lookup_type(RTLookupRequest::NAVIGATION);
+  request->set_dm_token(GetDMToken().value());
+  // TODO(crbug.com/1085261): Fill in user population.
+  return request;
+}
+
 policy::DMToken ChromeEnterpriseRealTimeUrlLookupService::GetDMToken() const {
   return ::safe_browsing::GetDMToken(profile_);
 }
diff --git a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.h b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.h
index 0a83f893..0184100 100644
--- a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.h
+++ b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.h
@@ -41,18 +41,17 @@
 
   // RealTimeUrlLookupServiceBase:
   bool CanPerformFullURLLookup() const override;
-
   bool CanCheckSubresourceURL() const override;
-
   bool CanCheckSafeBrowsingDb() const override;
 
-  void StartLookup(const GURL& url,
-                   RTLookupRequestCallback request_callback,
-                   RTLookupResponseCallback response_callback) override;
-
  private:
   // RealTimeUrlLookupServiceBase:
   net::NetworkTrafficAnnotationTag GetTrafficAnnotationTag() const override;
+  bool CanPerformFullURLLookupWithToken() const override;
+  void GetAccessToken(const GURL& url,
+                      RTLookupRequestCallback request_callback,
+                      RTLookupResponseCallback response_callback) override;
+  std::unique_ptr<RTLookupRequest> FillRequestProto(const GURL& url) override;
 
   policy::DMToken GetDMToken() const;
 
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_delegate.cc b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_delegate.cc
index 8b9bd4c..b030913f 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_delegate.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_delegate.cc
@@ -142,18 +142,13 @@
 }
 
 bool ContentAnalysisActionAllowsDataUse(
-    enterprise_connectors::ContentAnalysisResponse::Result::TriggeredRule::
-        Action action) {
+    enterprise_connectors::TriggeredRule::Action action) {
   switch (action) {
-    case enterprise_connectors::ContentAnalysisResponse::Result::TriggeredRule::
-        ACTION_UNSPECIFIED:
-    case enterprise_connectors::ContentAnalysisResponse::Result::TriggeredRule::
-        REPORT_ONLY:
+    case enterprise_connectors::TriggeredRule::ACTION_UNSPECIFIED:
+    case enterprise_connectors::TriggeredRule::REPORT_ONLY:
       return true;
-    case enterprise_connectors::ContentAnalysisResponse::Result::TriggeredRule::
-        WARN:
-    case enterprise_connectors::ContentAnalysisResponse::Result::TriggeredRule::
-        BLOCK:
+    case enterprise_connectors::TriggeredRule::WARN:
+    case enterprise_connectors::TriggeredRule::BLOCK:
       return false;
   }
 }
@@ -191,6 +186,11 @@
   return &enabled;
 }
 
+EventResult CalculateEventResult(bool allowed, bool should_warn) {
+  return allowed ? EventResult::ALLOWED
+                 : (should_warn ? EventResult::WARNED : EventResult::BLOCKED);
+}
+
 }  // namespace
 
 DeepScanningDialogDelegate::Data::Data() = default;
@@ -474,21 +474,23 @@
                         base::TimeTicks::Now() - upload_start_time_,
                         content_size, result, response);
 
+  text_request_complete_ = true;
+  bool text_complies = ResultShouldAllowDataUse(result, data_.settings) &&
+                       DlpVerdictAllowsDataUse(response.dlp_scan_verdict());
+  bool should_warn = ShouldShowWarning(response.dlp_scan_verdict());
+  std::fill(result_.text_results.begin(), result_.text_results.end(),
+            text_complies);
+
   MaybeReportDeepScanningVerdict(
       Profile::FromBrowserContext(web_contents_->GetBrowserContext()),
       web_contents_->GetLastCommittedURL(), "Text data", std::string(),
       "text/plain",
       extensions::SafeBrowsingPrivateEventRouter::kTriggerWebContentUpload,
-      access_point_, content_size, result, response);
-
-  text_request_complete_ = true;
-  bool text_complies = ResultShouldAllowDataUse(result, data_.settings) &&
-                       DlpVerdictAllowsDataUse(response.dlp_scan_verdict());
-  std::fill(result_.text_results.begin(), result_.text_results.end(),
-            text_complies);
+      access_point_, content_size, result, response,
+      CalculateEventResult(text_complies, should_warn));
 
   if (!text_complies) {
-    if (ShouldShowWarning(response.dlp_scan_verdict())) {
+    if (should_warn) {
       text_warning_ = true;
       text_response_ = std::move(response);
       UpdateFinalResult(DeepScanningFinalResult::WARNING);
@@ -510,23 +512,26 @@
                         base::TimeTicks::Now() - upload_start_time_,
                         content_size, result, response);
 
+  text_request_complete_ = true;
+  auto action = enterprise_connectors::GetHighestPrecedenceAction(response);
+  bool text_complies = ResultShouldAllowDataUse(result, data_.settings) &&
+                       ContentAnalysisActionAllowsDataUse(action);
+  bool should_warn = action == enterprise_connectors::ContentAnalysisResponse::
+                                   Result::TriggeredRule::WARN;
+
+  std::fill(result_.text_results.begin(), result_.text_results.end(),
+            text_complies);
+
   MaybeReportDeepScanningVerdict(
       Profile::FromBrowserContext(web_contents_->GetBrowserContext()),
       web_contents_->GetLastCommittedURL(), "Text data", std::string(),
       "text/plain",
       extensions::SafeBrowsingPrivateEventRouter::kTriggerWebContentUpload,
-      access_point_, content_size, result, response);
-
-  text_request_complete_ = true;
-  auto action = enterprise_connectors::GetHighestPrecedenceAction(response);
-  bool text_complies = ResultShouldAllowDataUse(result, data_.settings) &&
-                       ContentAnalysisActionAllowsDataUse(action);
-  std::fill(result_.text_results.begin(), result_.text_results.end(),
-            text_complies);
+      access_point_, content_size, result, response,
+      CalculateEventResult(text_complies, should_warn));
 
   if (!text_complies) {
-    if (action == enterprise_connectors::ContentAnalysisResponse::Result::
-                      TriggeredRule::WARN) {
+    if (should_warn) {
       text_warning_ = true;
       content_analysis_text_response_ = std::move(response);
       UpdateFinalResult(DeepScanningFinalResult::WARNING);
@@ -545,12 +550,6 @@
     DeepScanningClientResponse response,
     std::string mime_type) {
   file_info_[index].mime_type = mime_type;
-  MaybeReportDeepScanningVerdict(
-      Profile::FromBrowserContext(web_contents_->GetBrowserContext()),
-      web_contents_->GetLastCommittedURL(), path.AsUTF8Unsafe(),
-      file_info_[index].sha256, mime_type,
-      extensions::SafeBrowsingPrivateEventRouter::kTriggerFileUpload,
-      access_point_, file_info_[index].size, result, response);
 
   bool dlp_ok = DlpVerdictAllowsDataUse(response.dlp_scan_verdict());
   bool malware_ok = true;
@@ -561,8 +560,17 @@
 
   bool file_complies =
       ResultShouldAllowDataUse(result, data_.settings) && dlp_ok && malware_ok;
+  bool should_warn = ShouldShowWarning(response.dlp_scan_verdict());
   result_.paths_results[index] = file_complies;
 
+  MaybeReportDeepScanningVerdict(
+      Profile::FromBrowserContext(web_contents_->GetBrowserContext()),
+      web_contents_->GetLastCommittedURL(), path.AsUTF8Unsafe(),
+      file_info_[index].sha256, mime_type,
+      extensions::SafeBrowsingPrivateEventRouter::kTriggerFileUpload,
+      access_point_, file_info_[index].size, result, response,
+      CalculateEventResult(file_complies, should_warn));
+
   ++file_result_count_;
 
   if (!file_complies) {
@@ -570,7 +578,7 @@
       UpdateFinalResult(DeepScanningFinalResult::LARGE_FILES);
     } else if (result == BinaryUploadService::Result::FILE_ENCRYPTED) {
       UpdateFinalResult(DeepScanningFinalResult::ENCRYPTED_FILES);
-    } else if (ShouldShowWarning(response.dlp_scan_verdict())) {
+    } else if (should_warn) {
       file_warnings_[index] = std::move(response);
       UpdateFinalResult(DeepScanningFinalResult::WARNING);
     } else {
@@ -588,17 +596,20 @@
     enterprise_connectors::ContentAnalysisResponse response,
     std::string mime_type) {
   file_info_[index].mime_type = mime_type;
+
+  auto action = GetHighestPrecedenceAction(response);
+  bool file_complies = ResultShouldAllowDataUse(result, data_.settings) &&
+                       ContentAnalysisActionAllowsDataUse(action);
+  bool should_warn = action == enterprise_connectors::TriggeredRule::WARN;
+  result_.paths_results[index] = file_complies;
+
   MaybeReportDeepScanningVerdict(
       Profile::FromBrowserContext(web_contents_->GetBrowserContext()),
       web_contents_->GetLastCommittedURL(), path.AsUTF8Unsafe(),
       file_info_[index].sha256, mime_type,
       extensions::SafeBrowsingPrivateEventRouter::kTriggerFileUpload,
-      access_point_, file_info_[index].size, result, response);
-
-  auto action = GetHighestPrecedenceAction(response);
-  bool file_complies = ResultShouldAllowDataUse(result, data_.settings) &&
-                       ContentAnalysisActionAllowsDataUse(action);
-  result_.paths_results[index] = file_complies;
+      access_point_, file_info_[index].size, result, response,
+      CalculateEventResult(file_complies, should_warn));
 
   ++file_result_count_;
 
@@ -607,8 +618,7 @@
       UpdateFinalResult(DeepScanningFinalResult::LARGE_FILES);
     } else if (result == BinaryUploadService::Result::FILE_ENCRYPTED) {
       UpdateFinalResult(DeepScanningFinalResult::ENCRYPTED_FILES);
-    } else if (action == enterprise_connectors::ContentAnalysisResponse::
-                             Result::TriggeredRule::WARN) {
+    } else if (should_warn) {
       content_analysis_file_warnings_[index] = std::move(response);
       UpdateFinalResult(DeepScanningFinalResult::WARNING);
     } else {
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_delegate_browsertest.cc b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_delegate_browsertest.cc
index cde581e..a888a22 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_delegate_browsertest.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_delegate_browsertest.cc
@@ -395,8 +395,7 @@
         enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
     bad_result->set_tag("malware");
     auto* bad_rule = bad_result->add_triggered_rules();
-    bad_rule->set_action(enterprise_connectors::ContentAnalysisResponse::
-                             Result::TriggeredRule::BLOCK);
+    bad_rule->set_action(enterprise_connectors::TriggeredRule::BLOCK);
     bad_rule->set_rule_name("MALWARE");
 
     FakeBinaryUploadServiceStorage()->SetResponseForFile(
@@ -490,14 +489,12 @@
     result->set_tag("dlp");
 
     auto* rule1 = result->add_triggered_rules();
-    rule1->set_action(enterprise_connectors::ContentAnalysisResponse::Result::
-                          TriggeredRule::REPORT_ONLY);
+    rule1->set_action(enterprise_connectors::TriggeredRule::REPORT_ONLY);
     rule1->set_rule_id("1");
     rule1->set_rule_name("resource rule 1");
 
     auto* rule2 = result->add_triggered_rules();
-    rule2->set_action(enterprise_connectors::ContentAnalysisResponse::Result::
-                          TriggeredRule::BLOCK);
+    rule2->set_action(enterprise_connectors::TriggeredRule::BLOCK);
     rule2->set_rule_id("3");
     rule2->set_rule_name("resource rule 2");
 
@@ -629,7 +626,11 @@
       /*reason*/ "FILE_PASSWORD_PROTECTED",
       /*mimetypes*/ ZipMimeTypes(),
       // du chrome/test/data/safe_browsing/download_protection/encrypted.zip -b
-      /*size*/ 20015);
+      /*size*/ 20015,
+      /*result*/
+      expected_result()
+          ? EventResultToString(safe_browsing::EventResult::ALLOWED)
+          : EventResultToString(safe_browsing::EventResult::BLOCKED));
 
   // Start test.
   DeepScanningDialogDelegate::ShowForWebContents(
@@ -721,7 +722,11 @@
       /*trigger*/ SafeBrowsingPrivateEventRouter::kTriggerFileUpload,
       /*reason*/ "DLP_SCAN_UNSUPPORTED_FILE_TYPE",
       /*mimetype*/ ShellScriptMimeTypes(),
-      /*size*/ std::string("file content").size());
+      /*size*/ std::string("file content").size(),
+      /*result*/
+      expected_result()
+          ? EventResultToString(safe_browsing::EventResult::ALLOWED)
+          : EventResultToString(safe_browsing::EventResult::BLOCKED));
 
   bool called = false;
   base::RunLoop run_loop;
@@ -822,7 +827,11 @@
       /*trigger*/ SafeBrowsingPrivateEventRouter::kTriggerFileUpload,
       /*reason*/ "FILE_TOO_LARGE",
       /*mimetypes*/ DocMimeTypes(),
-      /*size*/ BinaryUploadService::kMaxUploadSizeBytes + 1);
+      /*size*/ BinaryUploadService::kMaxUploadSizeBytes + 1,
+      /*result*/
+      expected_result()
+          ? EventResultToString(safe_browsing::EventResult::ALLOWED)
+          : EventResultToString(safe_browsing::EventResult::BLOCKED));
 
   bool called = false;
   base::RunLoop run_loop;
@@ -930,8 +939,7 @@
         enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
     malware_result->set_tag("malware");
     auto* malware_rule = malware_result->add_triggered_rules();
-    malware_rule->set_action(enterprise_connectors::ContentAnalysisResponse::
-                                 Result::TriggeredRule::BLOCK);
+    malware_rule->set_action(enterprise_connectors::TriggeredRule::BLOCK);
     malware_rule->set_rule_name("MALWARE");
 
     auto* dlp_result = response.add_results();
@@ -939,8 +947,7 @@
         enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
     dlp_result->set_tag("dlp");
     auto* dlp_rule = dlp_result->add_triggered_rules();
-    dlp_rule->set_action(enterprise_connectors::ContentAnalysisResponse::
-                             Result::TriggeredRule::BLOCK);
+    dlp_rule->set_action(enterprise_connectors::TriggeredRule::BLOCK);
     dlp_rule->set_rule_id("0");
     dlp_rule->set_rule_name("some_dlp_rule");
 
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_delegate_unittest.cc b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_delegate_unittest.cc
index 97a24b6..694b650 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_delegate_unittest.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_delegate_unittest.cc
@@ -257,20 +257,17 @@
     rule->set_rule_id(base::NumberToString(dlp_rule.rule_id()));
     switch (dlp_rule.action()) {
       case DlpDeepScanningVerdict::TriggeredRule::ACTION_UNKNOWN:
-        rule->set_action(enterprise_connectors::ContentAnalysisResponse::
-                             Result::TriggeredRule::ACTION_UNSPECIFIED);
+        rule->set_action(
+            enterprise_connectors::TriggeredRule::ACTION_UNSPECIFIED);
         break;
       case DlpDeepScanningVerdict::TriggeredRule::REPORT_ONLY:
-        rule->set_action(enterprise_connectors::ContentAnalysisResponse::
-                             Result::TriggeredRule::REPORT_ONLY);
+        rule->set_action(enterprise_connectors::TriggeredRule::REPORT_ONLY);
         break;
       case DlpDeepScanningVerdict::TriggeredRule::WARN:
-        rule->set_action(enterprise_connectors::ContentAnalysisResponse::
-                             Result::TriggeredRule::WARN);
+        rule->set_action(enterprise_connectors::TriggeredRule::WARN);
         break;
       case DlpDeepScanningVerdict::TriggeredRule::BLOCK:
-        rule->set_action(enterprise_connectors::ContentAnalysisResponse::
-                             Result::TriggeredRule::BLOCK);
+        rule->set_action(enterprise_connectors::TriggeredRule::BLOCK);
         break;
     }
   }
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.cc b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.cc
index 0864bee5..522005f0 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.cc
@@ -139,7 +139,8 @@
     const std::string& expected_trigger,
     const std::string& expected_reason,
     const std::set<std::string>* expected_mimetypes,
-    int expected_content_size) {
+    int expected_content_size,
+    const std::string& expected_result) {
   event_key_ = SafeBrowsingPrivateEventRouter::kKeyUnscannedFileEvent;
   url_ = expected_url;
   filename_ = expected_filename;
@@ -148,6 +149,7 @@
   trigger_ = expected_trigger;
   unscanned_reason_ = expected_reason;
   content_size_ = expected_content_size;
+  result_ = expected_result;
   EXPECT_CALL(*client_, UploadRealtimeReport_(_, _))
       .WillOnce([this](base::Value& report,
                        base::OnceCallback<void(bool)>& callback) {
@@ -270,6 +272,8 @@
   ValidateField(event, SafeBrowsingPrivateEventRouter::kKeyTrigger, trigger_);
   ValidateField(event, SafeBrowsingPrivateEventRouter::kKeyContentSize,
                 content_size_);
+  ValidateField(event, SafeBrowsingPrivateEventRouter::kKeyEventResult,
+                result_);
   ValidateField(event, SafeBrowsingPrivateEventRouter::kKeyThreatType,
                 threat_type_);
   ValidateField(event, SafeBrowsingPrivateEventRouter::kKeyUnscannedReason,
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.h b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.h
index c86e7da..cce4169 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.h
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.h
@@ -67,7 +67,8 @@
                                 const std::string& expected_trigger,
                                 const std::string& expected_reason,
                                 const std::set<std::string>* expected_mimetypes,
-                                int expected_content_size);
+                                int expected_content_size,
+                                const std::string& expected_result);
 
   // Closure to run once all expected events are validated.
   void SetDoneClosure(base::RepeatingClosure closure);
@@ -101,6 +102,7 @@
   base::Optional<bool> clicked_through_ = base::nullopt;
   base::Optional<int> content_size_ = base::nullopt;
   const std::set<std::string>* mimetypes_ = nullptr;
+  base::Optional<std::string> result_ = base::nullopt;
 
   base::RepeatingClosure done_closure_;
 };
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.cc b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.cc
index bf921c1..89275d1 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.cc
@@ -7,11 +7,11 @@
 
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/string_number_conversions.h"
+#include "chrome/browser/enterprise/connectors/common.h"
 #include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h"
 #include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_factory.h"
 #include "chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
-#include "components/enterprise/common/proto/connectors.pb.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 
 namespace safe_browsing {
@@ -68,17 +68,17 @@
 ContentAnalysisScanResult& ContentAnalysisScanResult::operator=(
     const ContentAnalysisScanResult& other) = default;
 
-void MaybeReportDeepScanningVerdict(
-    Profile* profile,
-    const GURL& url,
-    const std::string& file_name,
-    const std::string& download_digest_sha256,
-    const std::string& mime_type,
-    const std::string& trigger,
-    DeepScanAccessPoint access_point,
-    const int64_t content_size,
-    BinaryUploadService::Result result,
-    const DeepScanningClientResponse& response) {
+void MaybeReportDeepScanningVerdict(Profile* profile,
+                                    const GURL& url,
+                                    const std::string& file_name,
+                                    const std::string& download_digest_sha256,
+                                    const std::string& mime_type,
+                                    const std::string& trigger,
+                                    DeepScanAccessPoint access_point,
+                                    const int64_t content_size,
+                                    BinaryUploadService::Result result,
+                                    const DeepScanningClientResponse& response,
+                                    EventResult event_result) {
   DCHECK(std::all_of(download_digest_sha256.begin(),
                      download_digest_sha256.end(), [](const char& c) {
                        return (c >= '0' && c <= '9') ||
@@ -90,7 +90,7 @@
     extensions::SafeBrowsingPrivateEventRouterFactory::GetForProfile(profile)
         ->OnUnscannedFileEvent(url, file_name, download_digest_sha256,
                                mime_type, trigger, access_point,
-                               unscanned_reason, content_size);
+                               unscanned_reason, content_size, event_result);
   }
 
   if (result != BinaryUploadService::Result::SUCCESS)
@@ -100,9 +100,9 @@
       response.malware_scan_verdict().verdict() ==
           MalwareDeepScanningVerdict::SCAN_FAILURE) {
     extensions::SafeBrowsingPrivateEventRouterFactory::GetForProfile(profile)
-        ->OnUnscannedFileEvent(url, file_name, download_digest_sha256,
-                               mime_type, trigger, access_point,
-                               "MALWARE_SCAN_FAILED", content_size);
+        ->OnUnscannedFileEvent(
+            url, file_name, download_digest_sha256, mime_type, trigger,
+            access_point, "MALWARE_SCAN_FAILED", content_size, event_result);
   }
 
   if (response.has_dlp_scan_verdict() &&
@@ -110,7 +110,7 @@
     extensions::SafeBrowsingPrivateEventRouterFactory::GetForProfile(profile)
         ->OnUnscannedFileEvent(url, file_name, download_digest_sha256,
                                mime_type, trigger, access_point,
-                               "DLP_SCAN_FAILED", content_size);
+                               "DLP_SCAN_FAILED", content_size, event_result);
   }
 
   if (response.malware_scan_verdict().verdict() ==
@@ -147,7 +147,8 @@
     DeepScanAccessPoint access_point,
     const int64_t content_size,
     BinaryUploadService::Result result,
-    const enterprise_connectors::ContentAnalysisResponse& response) {
+    const enterprise_connectors::ContentAnalysisResponse& response,
+    EventResult event_result) {
   DCHECK(std::all_of(download_digest_sha256.begin(),
                      download_digest_sha256.end(), [](const char& c) {
                        return (c >= '0' && c <= '9') ||
@@ -159,7 +160,7 @@
     extensions::SafeBrowsingPrivateEventRouterFactory::GetForProfile(profile)
         ->OnUnscannedFileEvent(url, file_name, download_digest_sha256,
                                mime_type, trigger, access_point,
-                               unscanned_reason, content_size);
+                               unscanned_reason, content_size, event_result);
   }
 
   if (result != BinaryUploadService::Result::SUCCESS)
@@ -171,7 +172,8 @@
       extensions::SafeBrowsingPrivateEventRouterFactory::GetForProfile(profile)
           ->OnUnscannedFileEvent(url, file_name, download_digest_sha256,
                                  mime_type, trigger, access_point,
-                                 "ANALYSIS_CONNECTOR_FAILED", content_size);
+                                 "ANALYSIS_CONNECTOR_FAILED", content_size,
+                                 event_result);
     } else if (result.triggered_rules_size() > 0) {
       extensions::SafeBrowsingPrivateEventRouterFactory::GetForProfile(profile)
           ->OnAnalysisConnectorResult(url, file_name, download_digest_sha256,
@@ -228,6 +230,23 @@
   }
 }
 
+std::string EventResultToString(EventResult result) {
+  switch (result) {
+    case EventResult::UNKNOWN:
+      return "EVENT_RESULT_UNKNOWN";
+    case EventResult::ALLOWED:
+      return "EVENT_RESULT_ALLOWED";
+    case EventResult::WARNED:
+      return "EVENT_RESULT_WARNED";
+    case EventResult::BLOCKED:
+      return "EVENT_RESULT_BLOCKED";
+    case EventResult::BYPASSED:
+      return "EVENT_RESULT_BYPASSED";
+  }
+  NOTREACHED();
+  return "";
+}
+
 std::string DeepScanAccessPointToString(DeepScanAccessPoint access_point) {
   switch (access_point) {
     case DeepScanAccessPoint::DOWNLOAD:
@@ -486,13 +505,11 @@
     ContentAnalysisTrigger trigger;
     switch (verdict.verdict()) {
       case MalwareDeepScanningVerdict::UWS:
-        trigger.action = enterprise_connectors::ContentAnalysisResponse::
-            Result::TriggeredRule::BLOCK;
+        trigger.action = enterprise_connectors::TriggeredRule::BLOCK;
         trigger.name = "UWS";
         break;
       case MalwareDeepScanningVerdict::MALWARE:
-        trigger.action = enterprise_connectors::ContentAnalysisResponse::
-            Result::TriggeredRule::BLOCK;
+        trigger.action = enterprise_connectors::TriggeredRule::BLOCK;
         trigger.name = "MALWARE";
         break;
       default:
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h
index 7a0eb3e..48a96ce 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h
@@ -80,9 +80,30 @@
 };
 std::string DeepScanAccessPointToString(DeepScanAccessPoint access_point);
 
+// The resulting action that chrome performed in response to a scan request.
+// This maps to the event result in the real-time reporting.
+enum class EventResult {
+  UNKNOWN,
+
+  // The user was allowed to use the data without restriction.
+  ALLOWED,
+
+  // The user was allowed to use the data but was warned that it may violate
+  // enterprise rules.
+  WARNED,
+
+  // The user was not allowed to use the data.
+  BLOCKED,
+
+  // The user has chosen to use the data even though it violated enterprise
+  // rules.
+  BYPASSED,
+};
+
 // Helper function to examine a DeepScanningClientResponse and report the
 // appropriate events to the enterprise admin. |download_digest_sha256| must be
-// encoded using base::HexEncode.
+// encoded using base::HexEncode.  |event_result| indicates whether the user was
+// ultimately allowed to access the text or file.
 void MaybeReportDeepScanningVerdict(Profile* profile,
                                     const GURL& url,
                                     const std::string& file_name,
@@ -92,11 +113,13 @@
                                     DeepScanAccessPoint access_point,
                                     const int64_t content_size,
                                     BinaryUploadService::Result result,
-                                    const DeepScanningClientResponse& response);
+                                    const DeepScanningClientResponse& response,
+                                    EventResult event_result);
 
 // Helper function to examine a ContentAnalysisResponse and report the
 // appropriate events to the enterprise admin. |download_digest_sha256| must be
-// encoded using base::HexEncode.
+// encoded using base::HexEncode.  |event_result| indicates whether the user was
+// ultimately allowed to access the text or file.
 void MaybeReportDeepScanningVerdict(
     Profile* profile,
     const GURL& url,
@@ -107,7 +130,8 @@
     DeepScanAccessPoint access_point,
     const int64_t content_size,
     BinaryUploadService::Result result,
-    const enterprise_connectors::ContentAnalysisResponse& response);
+    const enterprise_connectors::ContentAnalysisResponse& response,
+    EventResult event_result);
 
 // Helper function to report the user bypassed a warning to the enterprise
 // admin. This is split from MaybeReportDeepScanningVerdict since it happens
@@ -164,6 +188,10 @@
     base::Optional<bool> dlp_success,
     base::Optional<bool> malware_success);
 
+// Helper function to convert a EventResult to a string that.  The format of
+// string returned is processed by the sever.
+std::string EventResultToString(EventResult result);
+
 // Helper function to convert a BinaryUploadService::Result to a CamelCase
 // string.
 std::string BinaryUploadServiceResultToString(
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/fake_deep_scanning_dialog_delegate.cc b/chrome/browser/safe_browsing/cloud_content_scanning/fake_deep_scanning_dialog_delegate.cc
index 21929f3..ce5d3451 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/fake_deep_scanning_dialog_delegate.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/fake_deep_scanning_dialog_delegate.cc
@@ -150,8 +150,7 @@
 // static
 enterprise_connectors::ContentAnalysisResponse
 FakeDeepScanningDialogDelegate::MalwareResponse(
-    enterprise_connectors::ContentAnalysisResponse::Result::TriggeredRule::
-        Action action) {
+    enterprise_connectors::TriggeredRule::Action action) {
   enterprise_connectors::ContentAnalysisResponse response;
 
   auto* result = response.mutable_results()->Add();
@@ -188,8 +187,7 @@
 FakeDeepScanningDialogDelegate::DlpResponse(
     enterprise_connectors::ContentAnalysisResponse::Result::Status status,
     const std::string& rule_name,
-    enterprise_connectors::ContentAnalysisResponse::Result::TriggeredRule::
-        Action action) {
+    enterprise_connectors::TriggeredRule::Action action) {
   enterprise_connectors::ContentAnalysisResponse response;
 
   auto* result = response.mutable_results()->Add();
@@ -222,12 +220,10 @@
 
 // static
 enterprise_connectors::ContentAnalysisResponse MalwareAndDlpResponse(
-    enterprise_connectors::ContentAnalysisResponse::Result::TriggeredRule::
-        Action malware_action,
+    enterprise_connectors::TriggeredRule::Action malware_action,
     enterprise_connectors::ContentAnalysisResponse::Result::Status dlp_status,
     const std::string& dlp_rule_name,
-    enterprise_connectors::ContentAnalysisResponse::Result::TriggeredRule::
-        Action dlp_action) {
+    enterprise_connectors::TriggeredRule::Action dlp_action) {
   enterprise_connectors::ContentAnalysisResponse response;
 
   auto* malware_result = response.mutable_results()->Add();
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/fake_deep_scanning_dialog_delegate.h b/chrome/browser/safe_browsing/cloud_content_scanning/fake_deep_scanning_dialog_delegate.h
index 72d6099c..57825a1 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/fake_deep_scanning_dialog_delegate.h
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/fake_deep_scanning_dialog_delegate.h
@@ -97,8 +97,7 @@
 
   // Returns a content analysis response with a specific malware action.
   static enterprise_connectors::ContentAnalysisResponse MalwareResponse(
-      enterprise_connectors::ContentAnalysisResponse::Result::TriggeredRule::
-          Action action);
+      enterprise_connectors::TriggeredRule::Action action);
 
   // Returns a deep scanning response with a specific DLP verdict.
   static DeepScanningClientResponse DlpResponse(
@@ -110,8 +109,7 @@
   static enterprise_connectors::ContentAnalysisResponse DlpResponse(
       enterprise_connectors::ContentAnalysisResponse::Result::Status status,
       const std::string& rule_name,
-      enterprise_connectors::ContentAnalysisResponse::Result::TriggeredRule::
-          Action action);
+      enterprise_connectors::TriggeredRule::Action action);
 
   // Returns a deep scanning response with specific malware and DLP verdicts.
   static DeepScanningClientResponse MalwareAndDlpResponse(
@@ -122,12 +120,10 @@
 
   // Returns a content analysis response with specific malware and DLP actions.
   static enterprise_connectors::ContentAnalysisResponse MalwareAndDlpResponse(
-      enterprise_connectors::ContentAnalysisResponse::Result::TriggeredRule::
-          Action malware_action,
+      enterprise_connectors::TriggeredRule::Action malware_action,
       enterprise_connectors::ContentAnalysisResponse::Result::Status dlp_status,
       const std::string& dlp_rule_name,
-      enterprise_connectors::ContentAnalysisResponse::Result::TriggeredRule::
-          Action dlp_action);
+      enterprise_connectors::TriggeredRule::Action dlp_action);
 
   // Sets the BinaryUploadService::Result to use in the next response callback.
   static void SetResponseResult(BinaryUploadService::Result result);
diff --git a/chrome/browser/safe_browsing/download_protection/deep_scanning_browsertest.cc b/chrome/browser/safe_browsing/download_protection/deep_scanning_browsertest.cc
index 7042431e..4d4f940 100644
--- a/chrome/browser/safe_browsing/download_protection/deep_scanning_browsertest.cc
+++ b/chrome/browser/safe_browsing/download_protection/deep_scanning_browsertest.cc
@@ -510,8 +510,7 @@
     result->set_status(
         enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
     auto* malware_rule = result->add_triggered_rules();
-    malware_rule->set_action(enterprise_connectors::ContentAnalysisResponse::
-                                 Result::TriggeredRule::BLOCK);
+    malware_rule->set_action(enterprise_connectors::TriggeredRule::BLOCK);
     malware_rule->set_rule_name("MALWARE");
     SendFcmMessage(async_response);
   }
@@ -550,8 +549,7 @@
     result->set_status(
         enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
     auto* dlp_rule = result->add_triggered_rules();
-    dlp_rule->set_action(enterprise_connectors::ContentAnalysisResponse::
-                             Result::TriggeredRule::BLOCK);
+    dlp_rule->set_action(enterprise_connectors::TriggeredRule::BLOCK);
     ExpectContentAnalysisSynchronousResponse(/*is_advanced_protection=*/false,
                                              sync_response, {"dlp", "malware"});
   }
@@ -676,8 +674,7 @@
     result->set_status(
         enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
     auto* dlp_rule = result->add_triggered_rules();
-    dlp_rule->set_action(enterprise_connectors::ContentAnalysisResponse::
-                             Result::TriggeredRule::BLOCK);
+    dlp_rule->set_action(enterprise_connectors::TriggeredRule::BLOCK);
     ExpectContentAnalysisSynchronousResponse(/*is_advanced_protection=*/false,
                                              sync_response, {"dlp"});
   }
diff --git a/chrome/browser/safe_browsing/download_protection/deep_scanning_request.cc b/chrome/browser/safe_browsing/download_protection/deep_scanning_request.cc
index 7056b0a..a140bbf 100644
--- a/chrome/browser/safe_browsing/download_protection/deep_scanning_request.cc
+++ b/chrome/browser/safe_browsing/download_protection/deep_scanning_request.cc
@@ -41,10 +41,9 @@
     DownloadCheckResult* download_result) {
   bool malware_scan_failure = false;
   bool dlp_scan_failure = false;
-  auto malware_action = enterprise_connectors::ContentAnalysisResponse::Result::
-      TriggeredRule::ACTION_UNSPECIFIED;
-  auto dlp_action = enterprise_connectors::ContentAnalysisResponse::Result::
-      TriggeredRule::ACTION_UNSPECIFIED;
+  auto malware_action =
+      enterprise_connectors::TriggeredRule::ACTION_UNSPECIFIED;
+  auto dlp_action = enterprise_connectors::TriggeredRule::ACTION_UNSPECIFIED;
 
   for (const auto& result : response.results()) {
     if (result.tag() == "malware") {
@@ -74,34 +73,26 @@
   if (malware_action == enterprise_connectors::GetHighestPrecedenceAction(
                             malware_action, dlp_action)) {
     switch (malware_action) {
-      case enterprise_connectors::ContentAnalysisResponse::Result::
-          TriggeredRule::BLOCK:
+      case enterprise_connectors::TriggeredRule::BLOCK:
         *download_result = DownloadCheckResult::DANGEROUS;
         return;
-      case enterprise_connectors::ContentAnalysisResponse::Result::
-          TriggeredRule::WARN:
+      case enterprise_connectors::TriggeredRule::WARN:
         *download_result = DownloadCheckResult::POTENTIALLY_UNWANTED;
         return;
-      case enterprise_connectors::ContentAnalysisResponse::Result::
-          TriggeredRule::REPORT_ONLY:
-      case enterprise_connectors::ContentAnalysisResponse::Result::
-          TriggeredRule::ACTION_UNSPECIFIED:
+      case enterprise_connectors::TriggeredRule::REPORT_ONLY:
+      case enterprise_connectors::TriggeredRule::ACTION_UNSPECIFIED:
         break;
     }
   } else {
     switch (dlp_action) {
-      case enterprise_connectors::ContentAnalysisResponse::Result::
-          TriggeredRule::BLOCK:
+      case enterprise_connectors::TriggeredRule::BLOCK:
         *download_result = DownloadCheckResult::SENSITIVE_CONTENT_BLOCK;
         return;
-      case enterprise_connectors::ContentAnalysisResponse::Result::
-          TriggeredRule::WARN:
+      case enterprise_connectors::TriggeredRule::WARN:
         *download_result = DownloadCheckResult::SENSITIVE_CONTENT_WARNING;
         return;
-      case enterprise_connectors::ContentAnalysisResponse::Result::
-          TriggeredRule::REPORT_ONLY:
-      case enterprise_connectors::ContentAnalysisResponse::Result::
-          TriggeredRule::ACTION_UNSPECIFIED:
+      case enterprise_connectors::TriggeredRule::REPORT_ONLY:
+      case enterprise_connectors::TriggeredRule::ACTION_UNSPECIFIED:
         break;
     }
   }
@@ -384,19 +375,6 @@
       /*duration=*/base::TimeTicks::Now() - upload_start_time_,
       /*total_size=*/item_->GetTotalBytes(), /*result=*/result,
       /*response=*/response);
-  Profile* profile = Profile::FromBrowserContext(
-      content::DownloadItemUtils::GetBrowserContext(item_));
-  if (profile && trigger_ == DeepScanTrigger::TRIGGER_POLICY) {
-    std::string raw_digest_sha256 = item_->GetHash();
-    MaybeReportDeepScanningVerdict(
-        profile, item_->GetURL(), item_->GetTargetFilePath().AsUTF8Unsafe(),
-        base::HexEncode(raw_digest_sha256.data(), raw_digest_sha256.size()),
-        item_->GetMimeType(),
-        extensions::SafeBrowsingPrivateEventRouter::kTriggerFileDownload,
-        DeepScanAccessPoint::DOWNLOAD, item_->GetTotalBytes(), result,
-        response);
-  }
-
   DownloadCheckResult download_result = DownloadCheckResult::UNKNOWN;
   if (result == BinaryUploadService::Result::SUCCESS) {
     ResponseToDownloadCheckResult(response, &download_result);
@@ -422,6 +400,49 @@
       download_result = DownloadCheckResult::BLOCKED_UNSUPPORTED_FILE_TYPE;
   }
 
+  // Determine if the user is allowed to access the downloaded file.
+  EventResult event_result = EventResult::UNKNOWN;
+  switch (download_result) {
+    case DownloadCheckResult::UNKNOWN:
+    case DownloadCheckResult::SAFE:
+    case DownloadCheckResult::WHITELISTED_BY_POLICY:
+    case DownloadCheckResult::DEEP_SCANNED_SAFE:
+      event_result = EventResult::ALLOWED;
+      break;
+
+    case DownloadCheckResult::UNCOMMON:
+    case DownloadCheckResult::POTENTIALLY_UNWANTED:
+    case DownloadCheckResult::SENSITIVE_CONTENT_WARNING:
+    case DownloadCheckResult::DANGEROUS:
+    case DownloadCheckResult::DANGEROUS_HOST:
+      event_result = EventResult::WARNED;
+      break;
+
+    case DownloadCheckResult::BLOCKED_PASSWORD_PROTECTED:
+    case DownloadCheckResult::BLOCKED_TOO_LARGE:
+    case DownloadCheckResult::SENSITIVE_CONTENT_BLOCK:
+    case DownloadCheckResult::BLOCKED_UNSUPPORTED_FILE_TYPE:
+      event_result = EventResult::BLOCKED;
+      break;
+
+    default:
+      NOTREACHED() << "Should never be final result";
+      break;
+  }
+
+  Profile* profile = Profile::FromBrowserContext(
+      content::DownloadItemUtils::GetBrowserContext(item_));
+  if (profile && trigger_ == DeepScanTrigger::TRIGGER_POLICY) {
+    std::string raw_digest_sha256 = item_->GetHash();
+    MaybeReportDeepScanningVerdict(
+        profile, item_->GetURL(), item_->GetTargetFilePath().AsUTF8Unsafe(),
+        base::HexEncode(raw_digest_sha256.data(), raw_digest_sha256.size()),
+        item_->GetMimeType(),
+        extensions::SafeBrowsingPrivateEventRouter::kTriggerFileDownload,
+        DeepScanAccessPoint::DOWNLOAD, item_->GetTotalBytes(), result, response,
+        event_result);
+  }
+
   FinishRequest(download_result);
 }
 
diff --git a/chrome/browser/safe_browsing/download_protection/deep_scanning_request_unittest.cc b/chrome/browser/safe_browsing/download_protection/deep_scanning_request_unittest.cc
index fe52ab2..2e7409e 100644
--- a/chrome/browser/safe_browsing/download_protection/deep_scanning_request_unittest.cc
+++ b/chrome/browser/safe_browsing/download_protection/deep_scanning_request_unittest.cc
@@ -597,6 +597,11 @@
                 /*disabled*/ {});
   }
 
+  void TearDown() override {
+    extensions::SafeBrowsingPrivateEventRouterFactory::GetForProfile(profile_)
+        ->SetCloudPolicyClientForTesting(nullptr);
+  }
+
  protected:
   std::unique_ptr<policy::MockCloudPolicyClient> client_;
 };
@@ -638,8 +643,7 @@
       malware_result->set_status(
           enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
       auto* malware_rule = malware_result->add_triggered_rules();
-      malware_rule->set_action(enterprise_connectors::ContentAnalysisResponse::
-                                   Result::TriggeredRule::BLOCK);
+      malware_rule->set_action(enterprise_connectors::TriggeredRule::BLOCK);
       malware_rule->set_rule_name("MALWARE");
 
       auto* dlp_result = response.add_results();
@@ -647,8 +651,7 @@
       dlp_result->set_status(
           enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
       auto* dlp_rule = dlp_result->add_triggered_rules();
-      dlp_rule->set_action(enterprise_connectors::ContentAnalysisResponse::
-                               Result::TriggeredRule::BLOCK);
+      dlp_rule->set_action(enterprise_connectors::TriggeredRule::BLOCK);
       dlp_rule->set_rule_name("dlp_rule");
       dlp_rule->set_rule_id("0");
 
@@ -704,8 +707,7 @@
       malware_result->set_status(
           enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
       auto* malware_rule = malware_result->add_triggered_rules();
-      malware_rule->set_action(enterprise_connectors::ContentAnalysisResponse::
-                                   Result::TriggeredRule::WARN);
+      malware_rule->set_action(enterprise_connectors::TriggeredRule::WARN);
       malware_rule->set_rule_name("UWS");
 
       auto* dlp_result = response.add_results();
@@ -713,8 +715,7 @@
       dlp_result->set_status(
           enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
       auto* dlp_rule = dlp_result->add_triggered_rules();
-      dlp_rule->set_action(enterprise_connectors::ContentAnalysisResponse::
-                               Result::TriggeredRule::WARN);
+      dlp_rule->set_action(enterprise_connectors::TriggeredRule::WARN);
       dlp_rule->set_rule_name("dlp_rule");
       dlp_rule->set_rule_id("0");
 
@@ -768,8 +769,7 @@
       dlp_result->set_status(
           enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
       auto* dlp_rule = dlp_result->add_triggered_rules();
-      dlp_rule->set_action(enterprise_connectors::ContentAnalysisResponse::
-                               Result::TriggeredRule::BLOCK);
+      dlp_rule->set_action(enterprise_connectors::TriggeredRule::BLOCK);
       dlp_rule->set_rule_name("dlp_rule");
       dlp_rule->set_rule_id("0");
 
@@ -821,8 +821,7 @@
       dlp_result->set_status(
           enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
       auto* dlp_rule = dlp_result->add_triggered_rules();
-      dlp_rule->set_action(enterprise_connectors::ContentAnalysisResponse::
-                               Result::TriggeredRule::WARN);
+      dlp_rule->set_action(enterprise_connectors::TriggeredRule::WARN);
       dlp_rule->set_rule_name("dlp_rule");
       dlp_rule->set_rule_id("0");
 
@@ -876,13 +875,11 @@
       dlp_result->set_status(
           enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
       auto* dlp_rule1 = dlp_result->add_triggered_rules();
-      dlp_rule1->set_action(enterprise_connectors::ContentAnalysisResponse::
-                                Result::TriggeredRule::WARN);
+      dlp_rule1->set_action(enterprise_connectors::TriggeredRule::WARN);
       dlp_rule1->set_rule_name("dlp_rule1");
       dlp_rule1->set_rule_id("0");
       auto* dlp_rule2 = dlp_result->add_triggered_rules();
-      dlp_rule2->set_action(enterprise_connectors::ContentAnalysisResponse::
-                                Result::TriggeredRule::BLOCK);
+      dlp_rule2->set_action(enterprise_connectors::TriggeredRule::BLOCK);
       dlp_rule2->set_rule_name("dlp_rule2");
       dlp_rule2->set_rule_id("0");
 
@@ -947,7 +944,9 @@
         /*reason*/
         use_legacy_policies() ? "DLP_SCAN_FAILED" : "ANALYSIS_CONNECTOR_FAILED",
         /*mimetypes*/ ExeMimeTypes(),
-        /*size*/ std::string("download contents").size());
+        /*size*/ std::string("download contents").size(),
+        /*result*/
+        EventResultToString(EventResult::ALLOWED));
 
     request.Start();
 
@@ -992,7 +991,9 @@
         use_legacy_policies() ? "MALWARE_SCAN_FAILED"
                               : "ANALYSIS_CONNECTOR_FAILED",
         /*mimetypes*/ ExeMimeTypes(),
-        /*size*/ std::string("download contents").size());
+        /*size*/ std::string("download contents").size(),
+        /*result*/
+        EventResultToString(EventResult::ALLOWED));
 
     request.Start();
 
diff --git a/chrome/browser/safe_browsing/download_protection/download_protection_service_unittest.cc b/chrome/browser/safe_browsing/download_protection/download_protection_service_unittest.cc
index 8e5b66a..f19c90bd 100644
--- a/chrome/browser/safe_browsing/download_protection/download_protection_service_unittest.cc
+++ b/chrome/browser/safe_browsing/download_protection/download_protection_service_unittest.cc
@@ -1683,8 +1683,8 @@
   }
   {
     // Repeat the test with just the archive inside the zip file.
-    ASSERT_TRUE(base::DeleteFile(
-        zip_source_dir.GetPath().AppendASCII("file.exe"), false));
+    ASSERT_TRUE(
+        base::DeleteFile(zip_source_dir.GetPath().AppendASCII("file.exe")));
     ASSERT_TRUE(zip::Zip(zip_source_dir.GetPath(), tmp_path_, false));
     RunLoop run_loop;
     download_service_->CheckClientDownload(
diff --git a/chrome/browser/safety_check/android/BUILD.gn b/chrome/browser/safety_check/android/BUILD.gn
index aaeecb7..68171e0 100644
--- a/chrome/browser/safety_check/android/BUILD.gn
+++ b/chrome/browser/safety_check/android/BUILD.gn
@@ -25,19 +25,25 @@
 android_library("java") {
   sources = [
     "java/src/org/chromium/chrome/browser/safety_check/SafetyCheckBridge.java",
-    "java/src/org/chromium/chrome/browser/safety_check/SafetyCheckController.java",
-    "java/src/org/chromium/chrome/browser/safety_check/SafetyCheckElement.java",
-    "java/src/org/chromium/chrome/browser/safety_check/SafetyCheckModel.java",
+    "java/src/org/chromium/chrome/browser/safety_check/SafetyCheckCoordinator.java",
+    "java/src/org/chromium/chrome/browser/safety_check/SafetyCheckElementPreference.java",
+    "java/src/org/chromium/chrome/browser/safety_check/SafetyCheckMediator.java",
+    "java/src/org/chromium/chrome/browser/safety_check/SafetyCheckProperties.java",
     "java/src/org/chromium/chrome/browser/safety_check/SafetyCheckSettingsFragment.java",
     "java/src/org/chromium/chrome/browser/safety_check/SafetyCheckUpdatesDelegate.java",
+    "java/src/org/chromium/chrome/browser/safety_check/SafetyCheckViewBinder.java",
   ]
   deps = [
     ":java_resources",
     "//base:base_java",
     "//base:jni_java",
+    "//chrome/browser/preferences:java",
     "//components/browser_ui/settings/android:java",
     "//third_party/android_deps:android_support_v7_appcompat_java",
     "//third_party/android_deps:androidx_fragment_fragment_java",
+    "//third_party/android_deps:androidx_lifecycle_lifecycle_common_java",
+    "//third_party/android_deps:androidx_lifecycle_lifecycle_common_java8_java",
+    "//third_party/android_deps:androidx_lifecycle_lifecycle_livedata_core_java",
     "//third_party/android_deps:androidx_preference_preference_java",
     "//ui/android:ui_full_java",
   ]
@@ -48,14 +54,33 @@
   ]
 }
 
+android_library("javatests") {
+  testonly = true
+  sources = [ "javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckSettingsFragmentTest.java" ]
+  deps = [
+    ":java",
+    "//base:base_java",
+    "//base:base_java_test_support",
+    "//base/test:test_support_java",
+    "//chrome/browser/preferences:java",
+    "//chrome/browser/settings:test_support_java",
+    "//chrome/test/android:chrome_java_test_support",
+    "//content/public/test/android:content_java_test_support",
+    "//third_party/android_deps:androidx_preference_preference_java",
+    "//third_party/android_support_test_runner:runner_java",
+    "//third_party/junit:junit",
+    "//third_party/mockito:mockito_java",
+    "//ui/android:ui_full_java",
+  ]
+}
+
 android_library("junit") {
   # Skip platform checks since Robolectric depends on requires_android targets.
   bypass_platform_checks = true
   testonly = true
   sources = [
     "javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckBridgeTest.java",
-    "javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckControllerTest.java",
-    "javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckModelTest.java",
+    "javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckMediatorTest.java",
   ]
   deps = [
     ":java",
@@ -66,12 +91,14 @@
     "//chrome/browser/preferences:java",
     "//third_party/junit:junit",
     "//third_party/mockito:mockito_java",
+    "//ui/android:ui_full_java",
   ]
 }
 
 android_resources("java_resources") {
   sources = [
     "java/res/layout/safety_check_button.xml",
+    "java/res/layout/safety_check_status.xml",
     "java/res/values/dimens.xml",
     "java/res/xml/safety_check_preferences.xml",
   ]
diff --git a/chrome/browser/safety_check/android/java/res/layout/safety_check_status.xml b/chrome/browser/safety_check/android/java/res/layout/safety_check_status.xml
new file mode 100644
index 0000000..2bb7b84
--- /dev/null
+++ b/chrome/browser/safety_check/android/java/res/layout/safety_check_status.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2020 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. -->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent">
+     <!-- Spinning icon indicating a running check. -->
+     <ProgressBar
+          android:id="@+id/progress"
+          android:indeterminate="true"
+          android:layout_width="@dimen/safety_check_status_icon_size"
+          android:layout_height="@dimen/safety_check_status_icon_size"
+          android:layout_gravity="center_vertical"
+          android:visibility="gone" />
+     <!-- A status icon indicating the final state. -->
+     <ImageView
+          android:id="@+id/status_view"
+          android:importantForAccessibility="no"
+          android:layout_width="@dimen/safety_check_status_icon_size"
+          android:layout_height="@dimen/safety_check_status_icon_size"
+          android:layout_gravity="center_vertical"
+          android:visibility="gone" />
+</FrameLayout>
diff --git a/chrome/browser/safety_check/android/java/res/values/dimens.xml b/chrome/browser/safety_check/android/java/res/values/dimens.xml
index 7289b3b..e02d072 100644
--- a/chrome/browser/safety_check/android/java/res/values/dimens.xml
+++ b/chrome/browser/safety_check/android/java/res/values/dimens.xml
@@ -5,4 +5,5 @@
 
 <resources xmlns:tools="http://schemas.android.com/tools">
     <dimen name="safety_check_button_margin">16dp</dimen>
+    <dimen name="safety_check_status_icon_size">24dp</dimen>
 </resources>
diff --git a/chrome/browser/safety_check/android/java/res/xml/safety_check_preferences.xml b/chrome/browser/safety_check/android/java/res/xml/safety_check_preferences.xml
index eeb27ce..547a589 100644
--- a/chrome/browser/safety_check/android/java/res/xml/safety_check_preferences.xml
+++ b/chrome/browser/safety_check/android/java/res/xml/safety_check_preferences.xml
@@ -11,24 +11,21 @@
         android:title="@string/safety_check_description" />
 
     <!-- Safe Browsing -->
-    <org.chromium.components.browser_ui.settings.ChromeBasePreference
+    <org.chromium.chrome.browser.safety_check.SafetyCheckElementPreference
         android:key="safe_browsing"
         android:title="@string/safety_check_safe_browsing_title"
-        android:summary="@string/safety_check_unchecked"
         android:icon="@drawable/ic_security_grey"
         app:iconTint="@color/default_icon_color" />
     <!-- Passwords -->
-    <org.chromium.components.browser_ui.settings.ChromeBasePreference
+    <org.chromium.chrome.browser.safety_check.SafetyCheckElementPreference
         android:key="passwords"
         android:title="@string/safety_check_passwords_title"
-        android:summary="@string/safety_check_unchecked"
         android:icon="@drawable/ic_vpn_key_grey"
         app:iconTint="@color/default_icon_color" />
     <!-- Updates -->
-    <org.chromium.components.browser_ui.settings.ChromeBasePreference
+    <org.chromium.chrome.browser.safety_check.SafetyCheckElementPreference
         android:key="updates"
         android:title="@string/safety_check_updates_title"
-        android:summary="@string/safety_check_unchecked"
         android:icon="@drawable/ic_update_grey"
         app:iconTint="@color/default_icon_color" />
 
diff --git a/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckController.java b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckController.java
deleted file mode 100644
index 638c1bef..0000000
--- a/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckController.java
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright 2020 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.safety_check;
-
-import androidx.annotation.VisibleForTesting;
-
-import org.chromium.chrome.browser.password_check.BulkLeakCheckServiceState;
-import org.chromium.chrome.browser.safety_check.SafetyCheckBridge.SafetyCheckCommonObserver;
-
-/** Main class for all Safety check related logic. */
-public class SafetyCheckController implements SafetyCheckCommonObserver {
-    private SafetyCheckBridge mSafetyCheckBridge;
-    private SafetyCheckModel mModel;
-    private SafetyCheckUpdatesDelegate mUpdatesClient;
-
-    /**
-     * Creates a new instance of the Safety check controller given a model.
-     *
-     * @param model A model instance.
-     */
-    public SafetyCheckController(SafetyCheckModel model, SafetyCheckUpdatesDelegate client) {
-        mSafetyCheckBridge = new SafetyCheckBridge(SafetyCheckController.this);
-        mModel = model;
-        mUpdatesClient = client;
-    }
-
-    @VisibleForTesting
-    SafetyCheckController(
-            SafetyCheckModel model, SafetyCheckUpdatesDelegate client, SafetyCheckBridge bridge) {
-        mModel = model;
-        mUpdatesClient = client;
-        mSafetyCheckBridge = bridge;
-    }
-
-    /** Triggers all safety check child checks. */
-    public void performSafetyCheck() {
-        mModel.setCheckingState();
-        mSafetyCheckBridge.checkSafeBrowsing();
-        mSafetyCheckBridge.checkPasswords();
-        mUpdatesClient.checkForUpdates((status) -> { mModel.updateUpdatesStatus(status); });
-    }
-
-    /**
-     * Gets invoked once the Safe Browsing check is completed.
-     *
-     * @param status SafetyCheck::SafeBrowsingStatus enum value representing the Safe Browsing state
-     *     (see //components/safety_check/safety_check.h).
-     */
-    @Override
-    public void onSafeBrowsingCheckResult(@SafeBrowsingStatus int status) {
-        mModel.updateSafeBrowsingStatus(status);
-    }
-
-    /**
-     * Gets invoked by the C++ code every time another credential is checked.
-     *
-     * @param checked Number of passwords already checked.
-     * @param total Total number of passwords to check.
-     */
-    @Override
-    public void onPasswordCheckCredentialDone(int checked, int total) {}
-
-    /**
-     * Gets invoked by the C++ code when the status of the password check changes.
-     *
-     * @param state BulkLeakCheckService::State enum value representing the state (see
-     *     //components/password_manager/core/browser/bulk_leak_check_service_interface.h).
-     */
-    @Override
-    public void onPasswordCheckStateChange(@BulkLeakCheckServiceState int state) {
-        if (state == BulkLeakCheckServiceState.RUNNING) {
-            return;
-        }
-        mSafetyCheckBridge.stopObservingPasswordsCheck();
-        if (state == BulkLeakCheckServiceState.IDLE) {
-            boolean hasPasswords = mSafetyCheckBridge.savedPasswordsExist();
-            int numLeaked = mSafetyCheckBridge.getNumberOfPasswordLeaksFromLastCheck();
-            mModel.updatePasswordsStatusOnSucess(hasPasswords, numLeaked);
-        } else {
-            mModel.updatePasswordsStatusOnError(state);
-        }
-    }
-}
diff --git a/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckCoordinator.java b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckCoordinator.java
new file mode 100644
index 0000000..63611d3
--- /dev/null
+++ b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckCoordinator.java
@@ -0,0 +1,84 @@
+// Copyright 2020 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.safety_check;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.Observer;
+
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
+
+/**
+ * Coordinator for the Safety check settings page.
+ */
+public class SafetyCheckCoordinator implements DefaultLifecycleObserver {
+    private SafetyCheckSettingsFragment mSettingsFragment;
+    private SafetyCheckUpdatesDelegate mUpdatesClient;
+    private SafetyCheckMediator mMediator;
+
+    /**
+     * Creates a new Coordinator instance given a settings fragment and an updates client.
+     * There is no need to hold on to a reference since the settings fragment's lifecycle is
+     * observed and a reference is retained there.
+     * @param settingsFragment An instance of {SafetyCheckSettingsFragment} to observe.
+     * @param updatesClient An instance implementing the {@SafetyCheckUpdatesDelegate} interface.
+     */
+    public static void create(SafetyCheckSettingsFragment settingsFragment,
+            SafetyCheckUpdatesDelegate updatesClient) {
+        new SafetyCheckCoordinator(settingsFragment, updatesClient);
+    }
+
+    private SafetyCheckCoordinator(SafetyCheckSettingsFragment settingsFragment,
+            SafetyCheckUpdatesDelegate updatesClient) {
+        mSettingsFragment = settingsFragment;
+        mUpdatesClient = updatesClient;
+        // Create the model and the mediator once the view is created.
+        // The view's lifecycle is not available at this point, so observe the {@link LiveData} for
+        // it to get notified when {@link onCreateView} is called.
+        mSettingsFragment.getViewLifecycleOwnerLiveData().observe(
+                mSettingsFragment, new Observer<LifecycleOwner>() {
+                    @Override
+                    public void onChanged(LifecycleOwner lifecycleOwner) {
+                        // Only interested in the event when the View becomes non-null, which
+                        // happens when {@link onCreateView} is invoked.
+                        if (lifecycleOwner == null) {
+                            return;
+                        }
+                        // Only initialize it if it hasn't been already. This guards against
+                        // multiple invocations of this method.
+                        if (mMediator == null) {
+                            // Can start observing the View's lifecycle now.
+                            lifecycleOwner.getLifecycle().addObserver(SafetyCheckCoordinator.this);
+                            // The View is available, so now we can create the Model, MCP, and
+                            // Mediator.
+                            PropertyModel model = createModelAndMcp(mSettingsFragment);
+                            mMediator = new SafetyCheckMediator(model, mUpdatesClient);
+                        }
+                    }
+                });
+    }
+
+    @VisibleForTesting
+    static PropertyModel createModelAndMcp(SafetyCheckSettingsFragment settingsFragment) {
+        PropertyModel model = SafetyCheckProperties.createSafetyCheckModel();
+        PropertyModelChangeProcessor.create(model, settingsFragment, SafetyCheckViewBinder::bind);
+        return model;
+    }
+
+    /** Gets invoked when the Fragment detaches (the View is destroyed ). */
+    @Override
+    public void onDestroy(LifecycleOwner owner) {
+        // Stop observing the Lifecycle of the View as it is about to be destroyed.
+        owner.getLifecycle().removeObserver(this);
+        // Cancel any pending tasks.
+        mMediator.destroy();
+        // Clean up any objects we are holding on to.
+        mSettingsFragment = null;
+        mUpdatesClient = null;
+        mMediator = null;
+    }
+}
diff --git a/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckElement.java b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckElement.java
deleted file mode 100644
index a800c57..0000000
--- a/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckElement.java
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2020 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.safety_check;
-
-/**
- * Interface for storing information related to each Safety check element
- * (passwords, updates, etc) in the model.
- */
-public interface SafetyCheckElement {
-    /**
-     * @return The resource ID for the corresponding status string.
-     */
-    public int getStatusString();
-}
diff --git a/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckElementPreference.java b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckElementPreference.java
new file mode 100644
index 0000000..76639d4
--- /dev/null
+++ b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckElementPreference.java
@@ -0,0 +1,81 @@
+// Copyright 2020 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.safety_check;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+
+import androidx.annotation.DrawableRes;
+import androidx.preference.PreferenceViewHolder;
+
+import org.chromium.components.browser_ui.settings.ChromeBasePreference;
+
+/**
+ * A {@link Preference} for each Safety check element. In addition to the
+ * functionality, provided by the {@link IconPreference}, has a status indicator
+ * in the widget area that displays a progress bar or a status icon.
+ */
+public class SafetyCheckElementPreference extends ChromeBasePreference {
+    private View mProgressBar;
+    private ImageView mStatusView;
+
+    /**
+     * Creates a new object and sets the widget layout.
+     */
+    public SafetyCheckElementPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setWidgetLayoutResource(R.layout.safety_check_status);
+    }
+
+    /**
+     * Gets triggered when the view elements are created.
+     */
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+        mProgressBar = holder.findViewById(R.id.progress);
+        mStatusView = (ImageView) holder.findViewById(R.id.status_view);
+    }
+
+    /**
+     * Displays the progress bar.
+     */
+    void showProgressBar() {
+        // Ignore if this gets invoked before onBindViewHolder.
+        if (mStatusView == null || mProgressBar == null) {
+            return;
+        }
+        mStatusView.setVisibility(View.GONE);
+        mProgressBar.setVisibility(View.VISIBLE);
+    }
+
+    /**
+     * Displays the status icon.
+     * @param icon An icon to display.
+     */
+    void showStatusIcon(@DrawableRes int icon) {
+        // Ignore if this gets invoked before onBindViewHolder.
+        if (mStatusView == null || mProgressBar == null) {
+            return;
+        }
+        mStatusView.setImageResource(icon);
+        mProgressBar.setVisibility(View.GONE);
+        mStatusView.setVisibility(View.VISIBLE);
+    }
+
+    /**
+     * Hides anything in the status area.
+     */
+    void clearStatusIndicator() {
+        // Ignore if this gets invoked before onBindViewHolder.
+        if (mStatusView == null || mProgressBar == null) {
+            return;
+        }
+        mStatusView.setVisibility(View.GONE);
+        mProgressBar.setVisibility(View.GONE);
+    }
+}
diff --git a/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckMediator.java b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckMediator.java
new file mode 100644
index 0000000..48325eb
--- /dev/null
+++ b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckMediator.java
@@ -0,0 +1,137 @@
+// Copyright 2020 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.safety_check;
+
+import android.view.View;
+
+import androidx.annotation.VisibleForTesting;
+
+import org.chromium.base.Callback;
+import org.chromium.chrome.browser.password_check.BulkLeakCheckServiceState;
+import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
+import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
+import org.chromium.chrome.browser.safety_check.SafetyCheckBridge.SafetyCheckCommonObserver;
+import org.chromium.chrome.browser.safety_check.SafetyCheckProperties.PasswordsState;
+import org.chromium.chrome.browser.safety_check.SafetyCheckProperties.SafeBrowsingState;
+import org.chromium.chrome.browser.safety_check.SafetyCheckProperties.UpdatesState;
+import org.chromium.ui.modelutil.PropertyModel;
+
+import java.lang.ref.WeakReference;
+
+class SafetyCheckMediator implements SafetyCheckCommonObserver {
+    /** Bridge to the C++ side for the Safe Browsing and passwords checks. */
+    private SafetyCheckBridge mSafetyCheckBridge;
+    /** Model representing the current state of the checks. */
+    private PropertyModel mModel;
+    /** Client to interact with Omaha for the updates check. */
+    private SafetyCheckUpdatesDelegate mUpdatesClient;
+
+    private final SharedPreferencesManager mPreferenceManager;
+
+    /**
+     * Callback that gets invoked once the result of the updates check is available. Not inlined
+     * because a {@link WeakReference} needs to be passed (the check is asynchronous).
+     */
+    private final Callback<Integer> mUpdatesCheckCallback = (status) -> {
+        if (mModel != null) {
+            mModel.set(SafetyCheckProperties.UPDATES_STATE, status);
+        }
+    };
+
+    /**
+     * Creates a new instance of the Safety check mediator given a model and an updates client.
+     *
+     * @param model A model instance.
+     * @param client An updates client.
+     */
+    public SafetyCheckMediator(PropertyModel model, SafetyCheckUpdatesDelegate client) {
+        this(model, client, null);
+        // Have to initialize this after the constructor call, since a "this" instance is needed.
+        mSafetyCheckBridge = new SafetyCheckBridge(SafetyCheckMediator.this);
+    }
+
+    @VisibleForTesting
+    SafetyCheckMediator(
+            PropertyModel model, SafetyCheckUpdatesDelegate client, SafetyCheckBridge bridge) {
+        mModel = model;
+        mUpdatesClient = client;
+        mSafetyCheckBridge = bridge;
+        mPreferenceManager = SharedPreferencesManager.getInstance();
+        // Set the listener for clicking the Check button.
+        mModel.set(SafetyCheckProperties.SAFETY_CHECK_BUTTON_CLICK_LISTENER,
+                (View.OnClickListener) (v) -> performSafetyCheck());
+    }
+
+    /** Triggers all safety check child checks. */
+    public void performSafetyCheck() {
+        // Increment the stored number of Safety check starts.
+        mPreferenceManager.incrementInt(ChromePreferenceKeys.SETTINGS_SAFETY_CHECK_RUN_COUNTER);
+        // Set the checking state for all elements.
+        mModel.set(SafetyCheckProperties.PASSWORDS_STATE, PasswordsState.CHECKING);
+        mModel.set(SafetyCheckProperties.SAFE_BROWSING_STATE, SafeBrowsingState.CHECKING);
+        mModel.set(SafetyCheckProperties.UPDATES_STATE, UpdatesState.CHECKING);
+        // Start all the checks.
+        mSafetyCheckBridge.checkSafeBrowsing();
+        mSafetyCheckBridge.checkPasswords();
+        mUpdatesClient.checkForUpdates(new WeakReference(mUpdatesCheckCallback));
+    }
+
+    /**
+     * Gets invoked once the Safe Browsing check is completed.
+     *
+     * @param status SafetyCheck::SafeBrowsingStatus enum value representing the Safe Browsing state
+     *     (see //components/safety_check/safety_check.h).
+     */
+    @Override
+    public void onSafeBrowsingCheckResult(@SafeBrowsingStatus int status) {
+        mModel.set(SafetyCheckProperties.SAFE_BROWSING_STATE,
+                SafetyCheckProperties.safeBrowsingStateFromNative(status));
+    }
+
+    /**
+     * Gets invoked by the C++ code every time another credential is checked.
+     *
+     * @param checked Number of passwords already checked.
+     * @param total Total number of passwords to check.
+     */
+    @Override
+    public void onPasswordCheckCredentialDone(int checked, int total) {}
+
+    /**
+     * Gets invoked by the C++ code when the status of the password check changes.
+     *
+     * @param state BulkLeakCheckService::State enum value representing the state (see
+     *     //components/password_manager/core/browser/bulk_leak_check_service_interface.h).
+     */
+    @Override
+    public void onPasswordCheckStateChange(@BulkLeakCheckServiceState int state) {
+        if (state == BulkLeakCheckServiceState.RUNNING) {
+            return;
+        }
+        mSafetyCheckBridge.stopObservingPasswordsCheck();
+        // Handle the error states.
+        if (state != BulkLeakCheckServiceState.IDLE) {
+            mModel.set(SafetyCheckProperties.PASSWORDS_STATE,
+                    SafetyCheckProperties.passwordsStatefromErrorState(state));
+            return;
+        }
+        // Non-error state depends on whether there are any passwords saved and/or leaked.
+        if (!mSafetyCheckBridge.savedPasswordsExist()) {
+            mModel.set(SafetyCheckProperties.PASSWORDS_STATE, PasswordsState.NO_PASSWORDS);
+        } else if (mSafetyCheckBridge.getNumberOfPasswordLeaksFromLastCheck() == 0) {
+            mModel.set(SafetyCheckProperties.PASSWORDS_STATE, PasswordsState.SAFE);
+        } else {
+            mModel.set(SafetyCheckProperties.PASSWORDS_STATE, PasswordsState.COMPROMISED_EXIST);
+        }
+    }
+
+    /** Cancels any pending callbacks and registered observers.  */
+    public void destroy() {
+        mSafetyCheckBridge.destroy();
+        mSafetyCheckBridge = null;
+        mUpdatesClient = null;
+        mModel = null;
+    }
+}
diff --git a/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckModel.java b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckModel.java
deleted file mode 100644
index a63af05..0000000
--- a/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckModel.java
+++ /dev/null
@@ -1,221 +0,0 @@
-// Copyright 2020 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.safety_check;
-
-import org.chromium.chrome.browser.password_check.BulkLeakCheckServiceState;
-
-/**
- * Represents and stores the internal state of a Safety check.
- */
-public class SafetyCheckModel {
-    /**
-     * Stores the state related to the Safe Browsing element.
-     */
-    public enum SafeBrowsing implements SafetyCheckElement {
-        UNCHECKED(R.string.safety_check_unchecked),
-        CHECKING(R.string.safety_check_checking),
-        ENABLED_STANDARD(R.string.safety_check_safe_browsing_enabled_standard),
-        ENABLED_ENHANCED(R.string.safety_check_safe_browsing_enabled_enhanced),
-        DISABLED(R.string.safety_check_safe_browsing_disabled),
-        DISABLED_BY_ADMIN(R.string.safety_check_safe_browsing_disabled_by_admin),
-        ERROR(R.string.safety_check_error);
-
-        private final int mStatusString;
-
-        private SafeBrowsing(int statusString) {
-            mStatusString = statusString;
-        }
-
-        /**
-         * @return The resource ID for the corresponding status string.
-         */
-        @Override
-        public int getStatusString() {
-            return mStatusString;
-        }
-
-        /**
-         * android:key element in XML corresponding to Safe Browsing.
-         */
-        public static final String KEY = "safe_browsing";
-
-        /**
-         * Converts the C++ enum state to the internal representation.
-         * @param status Safe Browsing Status from C++.
-         * @return A SafeBrowsing enum element.
-         */
-        public static SafeBrowsing fromSafeBrowsingStatus(@SafeBrowsingStatus int status) {
-            switch (status) {
-                case SafeBrowsingStatus.CHECKING:
-                    return CHECKING;
-                case SafeBrowsingStatus.ENABLED:
-                case SafeBrowsingStatus.ENABLED_STANDARD:
-                    return ENABLED_STANDARD;
-                case SafeBrowsingStatus.ENABLED_ENHANCED:
-                    return ENABLED_ENHANCED;
-                case SafeBrowsingStatus.DISABLED:
-                    return DISABLED;
-                case SafeBrowsingStatus.DISABLED_BY_ADMIN:
-                    return DISABLED_BY_ADMIN;
-                default:
-                    return ERROR;
-            }
-        }
-    }
-
-    /**
-     * Stores the state related to the Passwords element.
-     */
-    public enum Passwords implements SafetyCheckElement {
-        UNCHECKED(R.string.safety_check_unchecked),
-        CHECKING(R.string.safety_check_checking),
-        SAFE,
-        COMPROMISED_EXIST,
-        OFFLINE,
-        NO_PASSWORDS(R.string.safety_check_passwords_no_passwords),
-        SIGNED_OUT,
-        QUOTA_LIMIT,
-        ERROR(R.string.safety_check_error);
-
-        private final int mStatusString;
-
-        private Passwords() {
-            mStatusString = 0;
-        }
-
-        private Passwords(int statusString) {
-            mStatusString = statusString;
-        }
-
-        /**
-         * @return The resource ID for the corresponding status string.
-         */
-        @Override
-        public int getStatusString() {
-            return mStatusString;
-        }
-
-        /**
-         * android:key element in XML corresponding to Passwords.
-         */
-        public static final String KEY = "passwords";
-
-        public static Passwords fromErrorState(@BulkLeakCheckServiceState int state) {
-            switch (state) {
-                case BulkLeakCheckServiceState.SIGNED_OUT:
-                    return SIGNED_OUT;
-                case BulkLeakCheckServiceState.QUOTA_LIMIT:
-                    return QUOTA_LIMIT;
-                default:
-                    return ERROR;
-            }
-        }
-    }
-
-    /**
-     * Stores the state related to the Updates element.
-     */
-    public enum Updates implements SafetyCheckElement {
-        UNCHECKED(R.string.safety_check_unchecked),
-        CHECKING(R.string.safety_check_checking),
-        UPDATED(R.string.safety_check_updates_updated),
-        OUTDATED(R.string.safety_check_updates_outdated),
-        OFFLINE(R.string.safety_check_updates_offline),
-        ERROR(R.string.safety_check_updates_error);
-
-        private final int mStatusString;
-
-        private Updates() {
-            mStatusString = 0;
-        }
-
-        private Updates(int statusString) {
-            mStatusString = statusString;
-        }
-
-        /**
-         * @return The resource ID for the corresponding status string.
-         */
-        @Override
-        public int getStatusString() {
-            return mStatusString;
-        }
-
-        /**
-         * android:key element in XML corresponding to Updates.
-         */
-        public static final String KEY = "updates";
-    }
-
-    private SafetyCheckSettingsFragment mView;
-    private SafeBrowsing mSafeBrowsing;
-    private Passwords mPasswords;
-    private Updates mUpdates;
-
-    /**
-     * Creates a new model for Safety check.
-     * @param view A Settings Fragment for Safety check.
-     */
-    public SafetyCheckModel(SafetyCheckSettingsFragment view) {
-        mView = view;
-    }
-
-    /**
-     * Updates the model and the view with the "Checking" state.
-     */
-    public void setCheckingState() {
-        mSafeBrowsing = SafeBrowsing.CHECKING;
-        mPasswords = Passwords.CHECKING;
-        mUpdates = Updates.CHECKING;
-        mView.updateElementStatus(SafeBrowsing.KEY, mSafeBrowsing.getStatusString());
-        mView.updateElementStatus(Passwords.KEY, mPasswords.getStatusString());
-        mView.updateElementStatus(Updates.KEY, mUpdates.getStatusString());
-    }
-
-    /**
-     * Updates the model and the view with the results of a Safe Browsing check.
-     * @param status The status of Safe Browsing.
-     */
-    public void updateSafeBrowsingStatus(@SafeBrowsingStatus int status) {
-        mSafeBrowsing = SafeBrowsing.fromSafeBrowsingStatus(status);
-        mView.updateElementStatus(SafeBrowsing.KEY, mSafeBrowsing.getStatusString());
-    }
-
-    /**
-     * Updates the model and the view with the results of a successful passwords
-     * check.
-     * @param hasPasswords Whether any passwords are saved.
-     * @param numLeaked Number of leaked passwords.
-     */
-    public void updatePasswordsStatusOnSucess(boolean hasPasswords, int numLeaked) {
-        if (!hasPasswords) {
-            mPasswords = Passwords.NO_PASSWORDS;
-        } else if (numLeaked == 0) {
-            mPasswords = Passwords.SAFE;
-        } else {
-            mPasswords = Passwords.COMPROMISED_EXIST;
-        }
-        mView.updateElementStatus(Passwords.KEY, mPasswords.getStatusString());
-    }
-
-    /**
-     * Updates the model and the view with the results of a failed passwords
-     * check.
-     * @param state State of the leak check service reflecting an error.
-     */
-    public void updatePasswordsStatusOnError(@BulkLeakCheckServiceState int state) {
-        mPasswords = Passwords.fromErrorState(state);
-        mView.updateElementStatus(Passwords.KEY, mPasswords.getStatusString());
-    }
-
-    /**
-     * Updates the model and the view with the results of an updates check.
-     * @param status Resulting state of the updates check.
-     */
-    public void updateUpdatesStatus(Updates status) {
-        mUpdates = status;
-        mView.updateElementStatus(Updates.KEY, mUpdates.getStatusString());
-    }
-}
diff --git a/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckProperties.java b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckProperties.java
new file mode 100644
index 0000000..7821bc49
--- /dev/null
+++ b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckProperties.java
@@ -0,0 +1,124 @@
+// Copyright 2020 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.safety_check;
+
+import androidx.annotation.IntDef;
+
+import org.chromium.chrome.browser.password_check.BulkLeakCheckServiceState;
+import org.chromium.ui.modelutil.PropertyKey;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.PropertyModel.WritableIntPropertyKey;
+import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+class SafetyCheckProperties {
+    /** State of the passwords check, one of the {@link PasswordsState} values. */
+    static final WritableIntPropertyKey PASSWORDS_STATE = new WritableIntPropertyKey();
+    /** State of the Safe Browsing check, one of the {@link SafeBrowsingState} values. */
+    static final WritableIntPropertyKey SAFE_BROWSING_STATE = new WritableIntPropertyKey();
+    /** State of the updates check, one of the {@link UpdatesState} values. */
+    static final WritableIntPropertyKey UPDATES_STATE = new WritableIntPropertyKey();
+    /** Listener for Safety check button click events. */
+    static final WritableObjectPropertyKey SAFETY_CHECK_BUTTON_CLICK_LISTENER =
+            new WritableObjectPropertyKey();
+
+    @IntDef({PasswordsState.UNCHECKED, PasswordsState.CHECKING, PasswordsState.SAFE,
+            PasswordsState.COMPROMISED_EXIST, PasswordsState.OFFLINE, PasswordsState.NO_PASSWORDS,
+            PasswordsState.SIGNED_OUT, PasswordsState.QUOTA_LIMIT, PasswordsState.ERROR})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PasswordsState {
+        int UNCHECKED = 0;
+        int CHECKING = 1;
+        int SAFE = 2;
+        int COMPROMISED_EXIST = 3;
+        int OFFLINE = 4;
+        int NO_PASSWORDS = 5;
+        int SIGNED_OUT = 6;
+        int QUOTA_LIMIT = 7;
+        int ERROR = 8;
+    }
+
+    static @PasswordsState int passwordsStatefromErrorState(@BulkLeakCheckServiceState int state) {
+        switch (state) {
+            case BulkLeakCheckServiceState.SIGNED_OUT:
+                return PasswordsState.SIGNED_OUT;
+            case BulkLeakCheckServiceState.QUOTA_LIMIT:
+                return PasswordsState.QUOTA_LIMIT;
+            case BulkLeakCheckServiceState.CANCELED:
+            case BulkLeakCheckServiceState.TOKEN_REQUEST_FAILURE:
+            case BulkLeakCheckServiceState.HASHING_FAILURE:
+            case BulkLeakCheckServiceState.NETWORK_ERROR:
+            case BulkLeakCheckServiceState.SERVICE_ERROR:
+                return PasswordsState.ERROR;
+            default:
+                assert false : "Unknown BulkLeakCheckServiceState value.";
+        }
+        // Never reached.
+        return 0;
+    }
+
+    @IntDef({SafeBrowsingState.UNCHECKED, SafeBrowsingState.CHECKING,
+            SafeBrowsingState.ENABLED_STANDARD, SafeBrowsingState.ENABLED_ENHANCED,
+            SafeBrowsingState.DISABLED, SafeBrowsingState.DISABLED_BY_ADMIN,
+            SafeBrowsingState.ERROR})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SafeBrowsingState {
+        int UNCHECKED = 0;
+        int CHECKING = 1;
+        int ENABLED_STANDARD = 2;
+        int ENABLED_ENHANCED = 3;
+        int DISABLED = 4;
+        int DISABLED_BY_ADMIN = 5;
+        int ERROR = 6;
+    }
+
+    static @SafeBrowsingState int safeBrowsingStateFromNative(@SafeBrowsingStatus int status) {
+        switch (status) {
+            case SafeBrowsingStatus.CHECKING:
+                return SafeBrowsingState.CHECKING;
+            case SafeBrowsingStatus.ENABLED:
+            case SafeBrowsingStatus.ENABLED_STANDARD:
+                return SafeBrowsingState.ENABLED_STANDARD;
+            case SafeBrowsingStatus.ENABLED_ENHANCED:
+                return SafeBrowsingState.ENABLED_ENHANCED;
+            case SafeBrowsingStatus.DISABLED:
+                return SafeBrowsingState.DISABLED;
+            case SafeBrowsingStatus.DISABLED_BY_ADMIN:
+                return SafeBrowsingState.DISABLED_BY_ADMIN;
+            case SafeBrowsingStatus.DISABLED_BY_EXTENSION:
+                assert false : "Safe Browsing cannot be disabled by extension on Android.";
+                return 0;
+            default:
+                assert false : "Unknown SafeBrowsingStatus value.";
+        }
+        // Never reached.
+        return 0;
+    }
+
+    @IntDef({UpdatesState.UNCHECKED, UpdatesState.CHECKING, UpdatesState.UPDATED,
+            UpdatesState.OUTDATED, UpdatesState.OFFLINE, UpdatesState.ERROR})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface UpdatesState {
+        int UNCHECKED = 0;
+        int CHECKING = 1;
+        int UPDATED = 2;
+        int OUTDATED = 3;
+        int OFFLINE = 4;
+        int ERROR = 5;
+    }
+
+    static final PropertyKey[] ALL_KEYS = new PropertyKey[] {PASSWORDS_STATE, SAFE_BROWSING_STATE,
+            UPDATES_STATE, SAFETY_CHECK_BUTTON_CLICK_LISTENER};
+
+    static PropertyModel createSafetyCheckModel() {
+        return new PropertyModel.Builder(ALL_KEYS)
+                .with(PASSWORDS_STATE, PasswordsState.UNCHECKED)
+                .with(SAFE_BROWSING_STATE, SafeBrowsingState.UNCHECKED)
+                .with(UPDATES_STATE, UpdatesState.UNCHECKED)
+                .build();
+    }
+}
diff --git a/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckSettingsFragment.java b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckSettingsFragment.java
index 1425609..6e31f82 100644
--- a/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckSettingsFragment.java
+++ b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckSettingsFragment.java
@@ -4,7 +4,11 @@
 
 package org.chromium.chrome.browser.safety_check;
 
+import android.content.Context;
 import android.os.Bundle;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.RelativeSizeSpan;
+import android.text.style.SuperscriptSpan;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -13,34 +17,58 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceFragmentCompat;
 
+import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
+import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
 import org.chromium.components.browser_ui.settings.SettingsUtils;
+import org.chromium.ui.text.SpanApplier;
+import org.chromium.ui.text.SpanApplier.SpanInfo;
 import org.chromium.ui.widget.ButtonCompat;
 
 /**
- * Fragment containing Safety check.
- *
- * Note: it is required for {@link #setUpdatesClient(SafetyCheckUpdatesDelegate)} to have been
- * invoked before {@link #onCreatePreferences(Bundle, String)}.
+ * Settings fragment containing Safety check. This class represents a View in the MVC paradigm.
  */
 public class SafetyCheckSettingsFragment extends PreferenceFragmentCompat {
-    private SafetyCheckController mController;
-    private SafetyCheckModel mModel;
-    private SafetyCheckUpdatesDelegate mUpdatesClient;
+    // Number of Safety check runs, after which the "NEW" label is no longer shown.
+    public static final int SAFETY_CHECK_RUNS_SHOW_NEW_LABEL = 3;
+
+    /** The "Check" button at the bottom that needs to be added after the View is inflated. */
+    private ButtonCompat mCheckButton;
+
+    public static CharSequence getSafetyCheckSettingsElementTitle(Context context) {
+        SharedPreferencesManager preferenceManager = SharedPreferencesManager.getInstance();
+        if (preferenceManager.readInt(ChromePreferenceKeys.SETTINGS_SAFETY_CHECK_RUN_COUNTER)
+                < SAFETY_CHECK_RUNS_SHOW_NEW_LABEL) {
+            // Show the styled "NEW" text if the user ran the Safety check less than 3 times.
+            // TODO(crbug.com/1102827): remove the "NEW" label in M88 once the feature is no longer
+            // "new".
+            return SpanApplier.applySpans(context.getString(R.string.prefs_safety_check),
+                    new SpanInfo("<new>", "</new>", new SuperscriptSpan(),
+                            new RelativeSizeSpan(0.75f),
+                            new ForegroundColorSpan(ApiCompatibilityUtils.getColor(
+                                    context.getResources(), R.color.default_text_color_blue))));
+        } else {
+            // Remove the "NEW" text and the trailing whitespace.
+            return (CharSequence) (SpanApplier
+                                           .removeSpanText(
+                                                   context.getString(R.string.prefs_safety_check),
+                                                   new SpanInfo("<new>", "</new>"))
+                                           .toString()
+                                           .trim());
+        }
+    }
 
     /**
      * Initializes all the objects related to the preferences page.
-     * Note: {@link #setUpdatesClient(SafetyCheckUpdatesDelegate)} should be invoked before this.
      */
     @Override
     public void onCreatePreferences(Bundle bundle, String s) {
-        // The updates client should be set before this method is invoked.
-        assert mUpdatesClient != null;
-        // Create the model and the controller.
-        mModel = new SafetyCheckModel(this);
-        mController = new SafetyCheckController(mModel, mUpdatesClient);
         // Add all preferences and set the title.
         SettingsUtils.addPreferencesFromResource(this, R.xml.safety_check_preferences);
-        getActivity().setTitle(R.string.prefs_safety_check);
+        CharSequence safetyCheckTitle = SpanApplier.removeSpanText(
+                getString(R.string.prefs_safety_check), new SpanInfo("<new>", "</new>"));
+        // Remove the trailing whitespace left after deleting the "NEW" label.
+        getActivity().setTitle(safetyCheckTitle.toString().trim());
     }
 
     @Override
@@ -49,20 +77,16 @@
         LinearLayout view =
                 (LinearLayout) super.onCreateView(inflater, container, savedInstanceState);
         // Add a button to the bottom of the preferences view.
-        ButtonCompat checkButton =
-                (ButtonCompat) inflater.inflate(R.layout.safety_check_button, view, false);
-        checkButton.setOnClickListener((View v) -> onSafetyCheckButtonClicked());
-        view.addView(checkButton);
+        mCheckButton = (ButtonCompat) inflater.inflate(R.layout.safety_check_button, view, false);
+        view.addView(mCheckButton);
         return view;
     }
 
     /**
-     * Sets the client for interacting with Omaha for the updates check.
-     * @param client An instance of a class implementing
-     *               {@link SafetyCheckUpdatesDelegate}.
+     * @return A {@link ButtonCompat} object for the Check button.
      */
-    public void setUpdatesClient(SafetyCheckUpdatesDelegate client) {
-        mUpdatesClient = client;
+    public ButtonCompat getCheckButton() {
+        return mCheckButton;
     }
 
     /**
@@ -72,13 +96,14 @@
      */
     public void updateElementStatus(String key, int statusString) {
         Preference p = findPreference(key);
-        p.setSummary(statusString);
-    }
-
-    /**
-     * Gets triggered when the button for starting Safety check is pressed.
-     */
-    private void onSafetyCheckButtonClicked() {
-        mController.performSafetyCheck();
+        // If this is invoked before the preferences are created, do nothing.
+        if (p == null) {
+            return;
+        }
+        if (statusString != 0) {
+            p.setSummary(statusString);
+        } else {
+            p.setSummary("");
+        }
     }
 }
diff --git a/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckUpdatesDelegate.java b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckUpdatesDelegate.java
index fcaad94..e0388e09 100644
--- a/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckUpdatesDelegate.java
+++ b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckUpdatesDelegate.java
@@ -5,7 +5,8 @@
 package org.chromium.chrome.browser.safety_check;
 
 import org.chromium.base.Callback;
-import org.chromium.chrome.browser.safety_check.SafetyCheckModel.Updates;
+
+import java.lang.ref.WeakReference;
 
 /**
  * Interface to interact with the version updater.
@@ -19,7 +20,8 @@
     /**
      * Asynchronously checks for updates and invokes the provided callback with
      * the result.
-     * @param statusCallback A callback to invoke with the result.
+     * @param statusCallback A callback to invoke with the result. Takes an element of
+     *                       {@link SafetyCheckProperties.UpdatesState} as an argument.
      */
-    void checkForUpdates(Callback<Updates> statusCallback);
+    void checkForUpdates(WeakReference<Callback<Integer>> statusCallback);
 }
diff --git a/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckViewBinder.java b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckViewBinder.java
new file mode 100644
index 0000000..7187a13
--- /dev/null
+++ b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckViewBinder.java
@@ -0,0 +1,190 @@
+// Copyright 2020 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.safety_check;
+
+import android.view.View;
+
+import org.chromium.chrome.browser.safety_check.SafetyCheckProperties.PasswordsState;
+import org.chromium.chrome.browser.safety_check.SafetyCheckProperties.SafeBrowsingState;
+import org.chromium.chrome.browser.safety_check.SafetyCheckProperties.UpdatesState;
+import org.chromium.ui.modelutil.PropertyKey;
+import org.chromium.ui.modelutil.PropertyModel;
+
+class SafetyCheckViewBinder {
+    private static final String PASSWORDS_KEY = "passwords";
+    private static final String SAFE_BROWSING_KEY = "safe_browsing";
+    private static final String UPDATES_KEY = "updates";
+
+    private static int getStringForPasswords(@PasswordsState int state) {
+        switch (state) {
+            case PasswordsState.UNCHECKED:
+            case PasswordsState.CHECKING:
+                return 0;
+            case PasswordsState.NO_PASSWORDS:
+                return R.string.safety_check_passwords_no_passwords;
+            case PasswordsState.SIGNED_OUT:
+            case PasswordsState.QUOTA_LIMIT:
+            case PasswordsState.OFFLINE:
+            case PasswordsState.ERROR:
+                return R.string.safety_check_error;
+            case PasswordsState.SAFE:
+            case PasswordsState.COMPROMISED_EXIST:
+                // TODO(crbug.com/1070620): update the strings for all states once available.
+                return 0;
+            default:
+                assert false : "Unknown PasswordsState value.";
+        }
+        // Not reached.
+        return 0;
+    }
+
+    private static int getStatusIconForPasswords(@PasswordsState int state) {
+        switch (state) {
+            case PasswordsState.UNCHECKED:
+            case PasswordsState.CHECKING:
+                return 0;
+            case PasswordsState.SAFE:
+                return R.drawable.ic_done_blue;
+            case PasswordsState.COMPROMISED_EXIST:
+                return R.drawable.ic_warning_red_24dp;
+            case PasswordsState.NO_PASSWORDS:
+            case PasswordsState.SIGNED_OUT:
+            case PasswordsState.QUOTA_LIMIT:
+            case PasswordsState.OFFLINE:
+            case PasswordsState.ERROR:
+                return R.drawable.ic_info_outline_grey_24dp;
+            default:
+                assert false : "Unknown PasswordsState value.";
+        }
+        // Not reached.
+        return 0;
+    }
+
+    private static int getStringForSafeBrowsing(@SafeBrowsingState int state) {
+        switch (state) {
+            case SafeBrowsingState.UNCHECKED:
+            case SafeBrowsingState.CHECKING:
+                return 0;
+            case SafeBrowsingState.ENABLED_STANDARD:
+                return R.string.safety_check_safe_browsing_enabled_standard;
+            case SafeBrowsingState.ENABLED_ENHANCED:
+                return R.string.safety_check_safe_browsing_enabled_enhanced;
+            case SafeBrowsingState.DISABLED:
+                return R.string.safety_check_safe_browsing_disabled;
+            case SafeBrowsingState.DISABLED_BY_ADMIN:
+                return R.string.safety_check_safe_browsing_disabled_by_admin;
+            case SafeBrowsingState.ERROR:
+                return R.string.safety_check_error;
+            default:
+                assert false : "Unknown SafeBrowsingState value.";
+        }
+        // Not reached.
+        return 0;
+    }
+
+    private static int getStatusIconForSafeBrowsing(@SafeBrowsingState int state) {
+        switch (state) {
+            case SafeBrowsingState.UNCHECKED:
+            case SafeBrowsingState.CHECKING:
+                return 0;
+            case SafeBrowsingState.ENABLED_STANDARD:
+            case SafeBrowsingState.ENABLED_ENHANCED:
+                return R.drawable.ic_done_blue;
+            case SafeBrowsingState.DISABLED:
+            case SafeBrowsingState.DISABLED_BY_ADMIN:
+            case SafeBrowsingState.ERROR:
+                return R.drawable.ic_info_outline_grey_24dp;
+            default:
+                assert false : "Unknown SafeBrowsingState value.";
+        }
+        // Not reached.
+        return 0;
+    }
+
+    private static int getStringForUpdates(@UpdatesState int state) {
+        switch (state) {
+            case UpdatesState.UNCHECKED:
+            case UpdatesState.CHECKING:
+                return 0;
+            case UpdatesState.UPDATED:
+                return R.string.safety_check_updates_updated;
+            case UpdatesState.OUTDATED:
+                return R.string.safety_check_updates_outdated;
+            case UpdatesState.OFFLINE:
+                return R.string.safety_check_updates_offline;
+            case UpdatesState.ERROR:
+                return R.string.safety_check_updates_error;
+            default:
+                assert false : "Unknown UpdatesState value.";
+        }
+        // Not reached.
+        return 0;
+    }
+
+    private static int getStatusIconForUpdates(@UpdatesState int state) {
+        switch (state) {
+            case UpdatesState.UNCHECKED:
+            case UpdatesState.CHECKING:
+                return 0;
+            case UpdatesState.UPDATED:
+                return R.drawable.ic_done_blue;
+            case UpdatesState.OUTDATED:
+                return R.drawable.ic_warning_red_24dp;
+            case UpdatesState.OFFLINE:
+            case UpdatesState.ERROR:
+                return R.drawable.ic_info_outline_grey_24dp;
+            default:
+                assert false : "Unknown UpdatesState value.";
+        }
+        // Not reached.
+        return 0;
+    }
+
+    static void bind(
+            PropertyModel model, SafetyCheckSettingsFragment fragment, PropertyKey propertyKey) {
+        if (SafetyCheckProperties.PASSWORDS_STATE == propertyKey) {
+            @PasswordsState
+            int state = model.get(SafetyCheckProperties.PASSWORDS_STATE);
+            fragment.updateElementStatus(PASSWORDS_KEY, getStringForPasswords(state));
+            SafetyCheckElementPreference preference = fragment.findPreference(PASSWORDS_KEY);
+            if (state == PasswordsState.UNCHECKED) {
+                preference.clearStatusIndicator();
+            } else if (state == PasswordsState.CHECKING) {
+                preference.showProgressBar();
+            } else {
+                preference.showStatusIcon(getStatusIconForPasswords(state));
+            }
+        } else if (SafetyCheckProperties.SAFE_BROWSING_STATE == propertyKey) {
+            @SafeBrowsingState
+            int state = model.get(SafetyCheckProperties.SAFE_BROWSING_STATE);
+            fragment.updateElementStatus(SAFE_BROWSING_KEY, getStringForSafeBrowsing(state));
+            SafetyCheckElementPreference preference = fragment.findPreference(SAFE_BROWSING_KEY);
+            if (state == SafeBrowsingState.UNCHECKED) {
+                preference.clearStatusIndicator();
+            } else if (state == SafeBrowsingState.CHECKING) {
+                preference.showProgressBar();
+            } else {
+                preference.showStatusIcon(getStatusIconForPasswords(state));
+            }
+        } else if (SafetyCheckProperties.UPDATES_STATE == propertyKey) {
+            @UpdatesState
+            int state = model.get(SafetyCheckProperties.UPDATES_STATE);
+            fragment.updateElementStatus(UPDATES_KEY, getStringForUpdates(state));
+            SafetyCheckElementPreference preference = fragment.findPreference(UPDATES_KEY);
+            if (state == UpdatesState.UNCHECKED) {
+                preference.clearStatusIndicator();
+            } else if (state == UpdatesState.CHECKING) {
+                preference.showProgressBar();
+            } else {
+                preference.showStatusIcon(getStatusIconForPasswords(state));
+            }
+        } else if (SafetyCheckProperties.SAFETY_CHECK_BUTTON_CLICK_LISTENER == propertyKey) {
+            fragment.getCheckButton().setOnClickListener((View.OnClickListener) model.get(
+                    SafetyCheckProperties.SAFETY_CHECK_BUTTON_CLICK_LISTENER));
+        } else {
+            assert false : "Unhandled property detected in SafetyCheckViewBinder!";
+        }
+    }
+}
diff --git a/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckControllerTest.java b/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckControllerTest.java
deleted file mode 100644
index 663b5ff..0000000
--- a/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckControllerTest.java
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2020 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.safety_check;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.verify;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import org.chromium.base.Callback;
-import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.chrome.browser.safety_check.SafetyCheckModel.Updates;
-
-/** Unit tests for {@link SafetyCheckModel}. */
-@RunWith(BaseRobolectricTestRunner.class)
-public class SafetyCheckControllerTest {
-    @Mock
-    private SafetyCheckModel mModel;
-    @Mock
-    private SafetyCheckUpdatesDelegate mUpdatesDelegate;
-    @Mock
-    private SafetyCheckBridge mBridge;
-
-    private SafetyCheckController mController;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mController = new SafetyCheckController(mModel, mUpdatesDelegate, mBridge);
-    }
-
-    /** Tests that the updates callback is routed correctly. */
-    @Test
-    public void testUpdatesCheck() {
-        doAnswer(invocation -> {
-            Callback<Updates> callback = (Callback<Updates>) invocation.getArguments()[0];
-            callback.onResult(Updates.UPDATED);
-            return null;
-        })
-                .when(mUpdatesDelegate)
-                .checkForUpdates(any(Callback.class));
-
-        mController.performSafetyCheck();
-        // The model should get updated with the result of the callback.
-        verify(mModel).updateUpdatesStatus(Updates.UPDATED);
-    }
-}
diff --git a/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckMediatorTest.java b/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckMediatorTest.java
new file mode 100644
index 0000000..681388e
--- /dev/null
+++ b/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckMediatorTest.java
@@ -0,0 +1,162 @@
+// Copyright 2020 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.safety_check;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import org.chromium.base.Callback;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.password_check.BulkLeakCheckServiceState;
+import org.chromium.chrome.browser.safety_check.SafetyCheckProperties.PasswordsState;
+import org.chromium.chrome.browser.safety_check.SafetyCheckProperties.SafeBrowsingState;
+import org.chromium.chrome.browser.safety_check.SafetyCheckProperties.UpdatesState;
+import org.chromium.ui.modelutil.PropertyModel;
+
+import java.lang.ref.WeakReference;
+
+/** Unit tests for {@link SafetyCheckMediator}. */
+@RunWith(BaseRobolectricTestRunner.class)
+public class SafetyCheckMediatorTest {
+    private PropertyModel mModel;
+    @Mock
+    private SafetyCheckUpdatesDelegate mUpdatesDelegate;
+    @Mock
+    private SafetyCheckBridge mBridge;
+
+    private SafetyCheckMediator mMediator;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mModel = SafetyCheckProperties.createSafetyCheckModel();
+        mMediator = new SafetyCheckMediator(mModel, mUpdatesDelegate, mBridge);
+    }
+
+    @Test
+    public void testUpdatesCheckUpdated() {
+        doAnswer(invocation -> {
+            Callback<Integer> callback =
+                    ((WeakReference<Callback<Integer>>) invocation.getArguments()[0]).get();
+            callback.onResult(UpdatesState.UPDATED);
+            return null;
+        })
+                .when(mUpdatesDelegate)
+                .checkForUpdates(any(WeakReference.class));
+
+        mMediator.performSafetyCheck();
+        assertEquals(UpdatesState.UPDATED, mModel.get(SafetyCheckProperties.UPDATES_STATE));
+    }
+
+    @Test
+    public void testUpdatesCheckOutdated() {
+        doAnswer(invocation -> {
+            Callback<Integer> callback =
+                    ((WeakReference<Callback<Integer>>) invocation.getArguments()[0]).get();
+            callback.onResult(UpdatesState.OUTDATED);
+            return null;
+        })
+                .when(mUpdatesDelegate)
+                .checkForUpdates(any(WeakReference.class));
+
+        mMediator.performSafetyCheck();
+        assertEquals(UpdatesState.OUTDATED, mModel.get(SafetyCheckProperties.UPDATES_STATE));
+    }
+
+    @Test
+    public void testSafeBrowsingCheckEnabledStandard() {
+        doAnswer(invocation -> {
+            mMediator.onSafeBrowsingCheckResult(SafeBrowsingStatus.ENABLED_STANDARD);
+            return null;
+        })
+                .when(mBridge)
+                .checkSafeBrowsing();
+
+        mMediator.performSafetyCheck();
+        assertEquals(SafeBrowsingState.ENABLED_STANDARD,
+                mModel.get(SafetyCheckProperties.SAFE_BROWSING_STATE));
+    }
+
+    @Test
+    public void testSafeBrowsingCheckDisabled() {
+        doAnswer(invocation -> {
+            mMediator.onSafeBrowsingCheckResult(SafeBrowsingStatus.DISABLED);
+            return null;
+        })
+                .when(mBridge)
+                .checkSafeBrowsing();
+
+        mMediator.performSafetyCheck();
+        assertEquals(
+                SafeBrowsingState.DISABLED, mModel.get(SafetyCheckProperties.SAFE_BROWSING_STATE));
+    }
+
+    @Test
+    public void testPasswordsCheckError() {
+        doAnswer(invocation -> {
+            mMediator.onPasswordCheckStateChange(BulkLeakCheckServiceState.SERVICE_ERROR);
+            return null;
+        })
+                .when(mBridge)
+                .checkPasswords();
+
+        mMediator.performSafetyCheck();
+        assertEquals(PasswordsState.ERROR, mModel.get(SafetyCheckProperties.PASSWORDS_STATE));
+    }
+
+    @Test
+    public void testPasswordsCheckNoPasswords() {
+        doAnswer(invocation -> {
+            mMediator.onPasswordCheckStateChange(BulkLeakCheckServiceState.IDLE);
+            return null;
+        })
+                .when(mBridge)
+                .checkPasswords();
+        when(mBridge.savedPasswordsExist()).thenReturn(false);
+
+        mMediator.performSafetyCheck();
+        assertEquals(
+                PasswordsState.NO_PASSWORDS, mModel.get(SafetyCheckProperties.PASSWORDS_STATE));
+    }
+
+    @Test
+    public void testPasswordsCheckNoLeaks() {
+        doAnswer(invocation -> {
+            mMediator.onPasswordCheckStateChange(BulkLeakCheckServiceState.IDLE);
+            return null;
+        })
+                .when(mBridge)
+                .checkPasswords();
+        when(mBridge.savedPasswordsExist()).thenReturn(true);
+        when(mBridge.getNumberOfPasswordLeaksFromLastCheck()).thenReturn(0);
+
+        mMediator.performSafetyCheck();
+        assertEquals(PasswordsState.SAFE, mModel.get(SafetyCheckProperties.PASSWORDS_STATE));
+    }
+
+    @Test
+    public void testPasswordsCheckHasLeaks() {
+        doAnswer(invocation -> {
+            mMediator.onPasswordCheckStateChange(BulkLeakCheckServiceState.IDLE);
+            return null;
+        })
+                .when(mBridge)
+                .checkPasswords();
+        when(mBridge.savedPasswordsExist()).thenReturn(true);
+        when(mBridge.getNumberOfPasswordLeaksFromLastCheck()).thenReturn(123);
+
+        mMediator.performSafetyCheck();
+        assertEquals(PasswordsState.COMPROMISED_EXIST,
+                mModel.get(SafetyCheckProperties.PASSWORDS_STATE));
+    }
+}
diff --git a/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckModelTest.java b/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckModelTest.java
deleted file mode 100644
index 26d769c..0000000
--- a/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckModelTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2020 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.safety_check;
-
-import static org.mockito.Mockito.verify;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.chrome.browser.password_check.BulkLeakCheckServiceState;
-import org.chromium.chrome.browser.safety_check.SafetyCheckModel.Updates;
-
-/** Unit tests for {@link SafetyCheckModel}. */
-@RunWith(BaseRobolectricTestRunner.class)
-public class SafetyCheckModelTest {
-    @Mock
-    private SafetyCheckSettingsFragment mView;
-
-    private SafetyCheckModel mSafetyCheckModel;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mSafetyCheckModel = new SafetyCheckModel(mView);
-    }
-
-    /** Tests a standard workflow for Safe Browsing. */
-    @Test
-    public void testSafeBrowsing() {
-        mSafetyCheckModel.setCheckingState();
-        verify(mView).updateElementStatus("safe_browsing", R.string.safety_check_checking);
-        mSafetyCheckModel.updateSafeBrowsingStatus(SafeBrowsingStatus.ENABLED_STANDARD);
-        verify(mView).updateElementStatus(
-                "safe_browsing", R.string.safety_check_safe_browsing_enabled_standard);
-    }
-
-    /** Tests a successful workflow for the passwords check. */
-    @Test
-    public void testPasswordsSuccess() {
-        mSafetyCheckModel.setCheckingState();
-        verify(mView).updateElementStatus("passwords", R.string.safety_check_checking);
-        mSafetyCheckModel.updatePasswordsStatusOnSucess(false, 0);
-        verify(mView).updateElementStatus(
-                "passwords", R.string.safety_check_passwords_no_passwords);
-    }
-
-    /** Tests a failure workflow for the passwords check. */
-    @Test
-    public void testPasswordsError() {
-        mSafetyCheckModel.setCheckingState();
-        verify(mView).updateElementStatus("passwords", R.string.safety_check_checking);
-        mSafetyCheckModel.updatePasswordsStatusOnError(
-                BulkLeakCheckServiceState.TOKEN_REQUEST_FAILURE);
-        verify(mView).updateElementStatus("passwords", R.string.safety_check_error);
-    }
-
-    /** Tests a workflow for the updates check. */
-    @Test
-    public void testUpdates() {
-        mSafetyCheckModel.setCheckingState();
-        verify(mView).updateElementStatus("updates", R.string.safety_check_checking);
-        mSafetyCheckModel.updateUpdatesStatus(Updates.UPDATED);
-        verify(mView).updateElementStatus("updates", R.string.safety_check_updates_updated);
-    }
-}
diff --git a/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckSettingsFragmentTest.java b/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckSettingsFragmentTest.java
new file mode 100644
index 0000000..1a518d8
--- /dev/null
+++ b/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckSettingsFragmentTest.java
@@ -0,0 +1,121 @@
+// Copyright 2020 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.safety_check;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.InstrumentationRegistry;
+
+import androidx.preference.Preference;
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
+import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
+import org.chromium.chrome.browser.safety_check.SafetyCheckProperties.SafeBrowsingState;
+import org.chromium.chrome.browser.safety_check.SafetyCheckProperties.UpdatesState;
+import org.chromium.chrome.browser.settings.SettingsActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/** Tests {@link SafetyCheckSettingsFragment} together with {@link SafetyCheckViewBinder}. */
+@RunWith(ChromeJUnit4ClassRunner.class)
+public class SafetyCheckSettingsFragmentTest {
+    private static final String PASSWORDS = "passwords";
+    private static final String SAFE_BROWSING = "safe_browsing";
+    private static final String UPDATES = "updates";
+    @Rule
+    public SettingsActivityTestRule<SafetyCheckSettingsFragment> mSettingsActivityTestRule =
+            new SettingsActivityTestRule<>(SafetyCheckSettingsFragment.class);
+
+    @Mock
+    private SafetyCheckBridge mSafetyCheckBridge;
+
+    private PropertyModel mModel;
+    private SafetyCheckSettingsFragment mFragment;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    @SmallTest
+    public void testGetSafetyCheckSettingsElementTitleNew() {
+        SharedPreferencesManager preferenceManager = SharedPreferencesManager.getInstance();
+        // The "NEW" label should still be shown after one run.
+        preferenceManager.writeInt(ChromePreferenceKeys.SETTINGS_SAFETY_CHECK_RUN_COUNTER, 1);
+        String title = SafetyCheckSettingsFragment
+                               .getSafetyCheckSettingsElementTitle(
+                                       InstrumentationRegistry.getTargetContext())
+                               .toString();
+        assertTrue(title.contains("New"));
+    }
+
+    @Test
+    @SmallTest
+    public void testGetSafetyCheckSettingsElementTitleNoNew() {
+        SharedPreferencesManager preferenceManager = SharedPreferencesManager.getInstance();
+        // The "NEW" label disappears after 3 runs.
+        preferenceManager.writeInt(ChromePreferenceKeys.SETTINGS_SAFETY_CHECK_RUN_COUNTER, 3);
+        String title = SafetyCheckSettingsFragment
+                               .getSafetyCheckSettingsElementTitle(
+                                       InstrumentationRegistry.getTargetContext())
+                               .toString();
+        assertFalse(title.contains("New"));
+    }
+
+    private void createFragmentAndModel() {
+        mSettingsActivityTestRule.startSettingsActivity();
+        mFragment = (SafetyCheckSettingsFragment) mSettingsActivityTestRule.getFragment();
+        mModel = SafetyCheckCoordinator.createModelAndMcp(mFragment);
+    }
+
+    @Test
+    @MediumTest
+    public void testNullStateDisplayedCorrectly() {
+        createFragmentAndModel();
+        Preference passwords = mFragment.findPreference(PASSWORDS);
+        Preference safeBrowsing = mFragment.findPreference(SAFE_BROWSING);
+        Preference updates = mFragment.findPreference(UPDATES);
+
+        assertEquals("", passwords.getSummary());
+        assertEquals("", safeBrowsing.getSummary());
+        assertEquals("", updates.getSummary());
+    }
+
+    @Test
+    @MediumTest
+    public void testStateChangeDisplayedCorrectly() {
+        createFragmentAndModel();
+        Preference passwords = mFragment.findPreference(PASSWORDS);
+        Preference safeBrowsing = mFragment.findPreference(SAFE_BROWSING);
+        Preference updates = mFragment.findPreference(UPDATES);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            // Passwords state remains unchanged.
+            // Safe browsing is in "checking".
+            mModel.set(SafetyCheckProperties.SAFE_BROWSING_STATE, SafeBrowsingState.CHECKING);
+            // Updates goes through "checking" and ends up in "outdated".
+            mModel.set(SafetyCheckProperties.UPDATES_STATE, UpdatesState.CHECKING);
+            mModel.set(SafetyCheckProperties.UPDATES_STATE, UpdatesState.OUTDATED);
+        });
+
+        assertEquals("", passwords.getSummary());
+        assertEquals("", safeBrowsing.getSummary());
+        assertEquals(InstrumentationRegistry.getTargetContext().getString(
+                             R.string.safety_check_updates_outdated),
+                updates.getSummary());
+    }
+}
diff --git a/chrome/browser/safety_check/android/safety_check_bridge.cc b/chrome/browser/safety_check/android/safety_check_bridge.cc
index 60ba67b..565ec2a 100644
--- a/chrome/browser/safety_check/android/safety_check_bridge.cc
+++ b/chrome/browser/safety_check/android/safety_check_bridge.cc
@@ -32,6 +32,9 @@
 void SafetyCheckBridge::Destroy(
     JNIEnv* env,
     const base::android::JavaParamRef<jobject>& obj) {
+  if (password_check_observed_) {
+    password_check_controller_->RemoveObserver(this);
+  }
   password_check_controller_.reset();
   safety_check_.reset();
   delete this;
@@ -46,6 +49,7 @@
 void SafetyCheckBridge::CheckPasswords(
     JNIEnv* env,
     const base::android::JavaParamRef<jobject>& obj) {
+  password_check_observed_ = true;
   password_check_controller_->AddObserver(this);
   password_check_controller_->StartPasswordCheck();
 }
@@ -66,6 +70,7 @@
     JNIEnv* env,
     const base::android::JavaParamRef<jobject>& obj) {
   password_check_controller_->RemoveObserver(this);
+  password_check_observed_ = false;
 }
 
 void SafetyCheckBridge::OnSafeBrowsingCheckResult(
diff --git a/chrome/browser/safety_check/android/safety_check_bridge.h b/chrome/browser/safety_check/android/safety_check_bridge.h
index 9ec9959..14017e29 100644
--- a/chrome/browser/safety_check/android/safety_check_bridge.h
+++ b/chrome/browser/safety_check/android/safety_check_bridge.h
@@ -69,6 +69,7 @@
   PrefService* pref_service_ = nullptr;
   std::unique_ptr<safety_check::SafetyCheck> safety_check_;
   std::unique_ptr<BulkLeakCheckControllerAndroid> password_check_controller_;
+  bool password_check_observed_ = false;
   base::android::ScopedJavaGlobalRef<jobject> j_safety_check_observer_;
 };
 
diff --git a/chrome/browser/search_engines/template_url_service_sync_unittest.cc b/chrome/browser/search_engines/template_url_service_sync_unittest.cc
index 098de2a0..8267bde 100644
--- a/chrome/browser/search_engines/template_url_service_sync_unittest.cc
+++ b/chrome/browser/search_engines/template_url_service_sync_unittest.cc
@@ -1145,7 +1145,8 @@
   GURL google_url(model()->search_terms_data().GoogleBaseURLValue());
   TemplateURL* key2 = model()->GetTemplateURLForHost(google_url.host());
   ASSERT_FALSE(key2 == NULL);
-  base::string16 google_keyword(url_formatter::StripWWWFromHost(google_url));
+  base::string16 google_keyword(
+      base::ASCIIToUTF16(url_formatter::StripWWW(google_url.host())));
   EXPECT_EQ(google_keyword, key2->keyword());
 
   // We should also have gotten some corresponding UPDATEs pushed upstream.
@@ -1163,8 +1164,8 @@
 TEST_F(TemplateURLServiceSyncTest, AutogeneratedKeywordConflicts) {
   // Sync brings in some autogenerated keywords, but the generated keywords we
   // try to create conflict with ones in the model.
-  base::string16 google_keyword(url_formatter::StripWWWFromHost(GURL(
-      model()->search_terms_data().GoogleBaseURLValue())));
+  base::string16 google_keyword(base::ASCIIToUTF16(url_formatter::StripWWW(
+      GURL(model()->search_terms_data().GoogleBaseURLValue()).host())));
   const std::string local_google_url =
       "{google:baseURL}1/search?q={searchTerms}";
   TemplateURL* google =
@@ -1235,8 +1236,8 @@
   MergeAndExpectNotify(initial_data, 1);
 
   // We should still have coalesced the updates to one each.
-  base::string16 google_keyword(url_formatter::StripWWWFromHost(GURL(
-      model()->search_terms_data().GoogleBaseURLValue())));
+  base::string16 google_keyword(base::ASCIIToUTF16(url_formatter::StripWWW(
+      GURL(model()->search_terms_data().GoogleBaseURLValue()).host())));
   TemplateURL* keyword1 =
       model()->GetTemplateURLForKeyword(google_keyword + ASCIIToUTF16("_"));
   ASSERT_FALSE(keyword1 == NULL);
diff --git a/chrome/browser/sessions/tab_restore_browsertest.cc b/chrome/browser/sessions/tab_restore_browsertest.cc
index 5ef787f..909f587b 100644
--- a/chrome/browser/sessions/tab_restore_browsertest.cc
+++ b/chrome/browser/sessions/tab_restore_browsertest.cc
@@ -958,6 +958,80 @@
   EXPECT_EQ(data->color(), visual_data.color());
 }
 
+// Closing the last tab in a collapsed group then restoring will place the group
+// back expanded with its metadata.
+IN_PROC_BROWSER_TEST_F(TabRestoreTestWithTabGroupsEnabled,
+                       RestoreCollapsedGroupTab_ExpandsGroup) {
+  const int tab_count = AddSomeTabs(browser(), 1);
+  ASSERT_LE(2, tab_count);
+
+  const int grouped_tab_index = tab_count - 1;
+  tab_groups::TabGroupId group_id =
+      browser()->tab_strip_model()->AddToNewGroup({grouped_tab_index});
+  const tab_groups::TabGroupVisualData visual_data(
+      base::ASCIIToUTF16("Foo"), tab_groups::TabGroupColorId::kCyan, true);
+
+  TabGroup* group =
+      browser()->tab_strip_model()->group_model()->GetTabGroup(group_id);
+  ASSERT_TRUE(group);
+  group->SetVisualData(visual_data);
+
+  CloseTab(grouped_tab_index);
+
+  ASSERT_NO_FATAL_FAILURE(RestoreTab(0, grouped_tab_index));
+  ASSERT_EQ(tab_count, browser()->tab_strip_model()->count());
+
+  EXPECT_EQ(group_id, browser()
+                          ->tab_strip_model()
+                          ->GetTabGroupForTab(grouped_tab_index)
+                          .value());
+  const tab_groups::TabGroupVisualData* data = browser()
+                                                   ->tab_strip_model()
+                                                   ->group_model()
+                                                   ->GetTabGroup(group_id)
+                                                   ->visual_data();
+  ASSERT_TRUE(data);
+  EXPECT_EQ(data->title(), visual_data.title());
+  EXPECT_EQ(data->color(), visual_data.color());
+  EXPECT_FALSE(data->is_collapsed());
+}
+
+// Closing a tab in a collapsed group then restoring the tab will expand the
+// group upon restore.
+IN_PROC_BROWSER_TEST_F(TabRestoreTestWithTabGroupsEnabled,
+                       RestoreTabIntoCollapsedGroup_ExpandsGroup) {
+  const int tab_count = AddSomeTabs(browser(), 2);
+  ASSERT_LE(3, tab_count);
+
+  const int closed_tab_index = 1;
+
+  tab_groups::TabGroupId group_id =
+      browser()->tab_strip_model()->AddToNewGroup({0, 1});
+  const tab_groups::TabGroupVisualData visual_data(
+      base::ASCIIToUTF16("Foo"), tab_groups::TabGroupColorId::kCyan, true);
+  TabGroup* group =
+      browser()->tab_strip_model()->group_model()->GetTabGroup(group_id);
+  group->SetVisualData(visual_data);
+
+  CloseTab(closed_tab_index);
+
+  ASSERT_NO_FATAL_FAILURE(RestoreTab(0, closed_tab_index));
+  ASSERT_EQ(tab_count, browser()->tab_strip_model()->count());
+
+  EXPECT_EQ(group_id, browser()
+                          ->tab_strip_model()
+                          ->GetTabGroupForTab(closed_tab_index)
+                          .value());
+  const tab_groups::TabGroupVisualData* data = browser()
+                                                   ->tab_strip_model()
+                                                   ->group_model()
+                                                   ->GetTabGroup(group_id)
+                                                   ->visual_data();
+  EXPECT_EQ(data->title(), visual_data.title());
+  EXPECT_EQ(data->color(), visual_data.color());
+  EXPECT_FALSE(data->is_collapsed());
+}
+
 // Closing a tab in a group then updating the metadata before restoring will
 // place the group back without updating the metadata.
 IN_PROC_BROWSER_TEST_F(TabRestoreTestWithTabGroupsEnabled,
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/screenshot/ScreenshotCoordinator.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/screenshot/ScreenshotCoordinator.java
index 80032e8d..fb6ee1f 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/screenshot/ScreenshotCoordinator.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/screenshot/ScreenshotCoordinator.java
@@ -14,6 +14,7 @@
 import org.chromium.chrome.browser.share.share_sheet.ChromeOptionShareCallback;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.modules.image_editor.ImageEditorModuleProvider;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 
 /**
  * Handles the screenshot action in the Sharing Hub and launches the screenshot editor.
@@ -26,22 +27,25 @@
     private final Activity mActivity;
     private final Tab mTab;
     private final ChromeOptionShareCallback mChromeOptionShareCallback;
+    private final BottomSheetController mBottomSheetController;
 
     private EditorScreenshotTask mScreenshotTask;
     private Bitmap mScreenshot;
 
-    public ScreenshotCoordinator(
-            Activity activity, Tab tab, ChromeOptionShareCallback chromeOptionShareCallback) {
+    public ScreenshotCoordinator(Activity activity, Tab tab,
+            ChromeOptionShareCallback chromeOptionShareCallback,
+            BottomSheetController sheetController) {
         mActivity = activity;
         mTab = tab;
         mChromeOptionShareCallback = chromeOptionShareCallback;
+        mBottomSheetController = sheetController;
     }
 
     /**
      * Takes a screenshot of the current tab and attempts to launch the screenshot image editor.
      */
     public void captureScreenshot() {
-        mScreenshotTask = new EditorScreenshotTask(mActivity);
+        mScreenshotTask = new EditorScreenshotTask(mActivity, mBottomSheetController);
         mScreenshotTask.capture(() -> {
             mScreenshot = mScreenshotTask.getScreenshot();
             if (mScreenshot == null) {
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java
index d3078af..90c022cf 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java
@@ -173,14 +173,14 @@
         PropertyModel propertyModel = ShareSheetPropertyModelBuilder.createPropertyModel(
                 AppCompatResources.getDrawable(mActivity, R.drawable.screenshot),
                 mActivity.getResources().getString(R.string.sharing_screenshot),
-                (shareParams)
+                (view)
                         -> {
                     RecordUserAction.record("SharingHubAndroid.ScreenshotSelected");
                     RecordHistogram.recordMediumTimesHistogram(
                             "Sharing.SharingHubAndroid.TimeToShare",
                             System.currentTimeMillis() - mShareStartTime);
-                    mScreenshotCoordinator = new ScreenshotCoordinator(
-                            mActivity, mTabProvider.get(), mChromeOptionShareCallback);
+                    mScreenshotCoordinator = new ScreenshotCoordinator(mActivity,
+                            mTabProvider.get(), mChromeOptionShareCallback, mBottomSheetController);
                     // Capture a screenshot once the bottom sheet is fully hidden. The
                     // observer will then remove itself.
                     mBottomSheetController.addObserver(mSheetObserver);
@@ -195,7 +195,7 @@
         PropertyModel propertyModel = ShareSheetPropertyModelBuilder.createPropertyModel(
                 AppCompatResources.getDrawable(mActivity, R.drawable.ic_content_copy_black),
                 mActivity.getResources().getString(R.string.sharing_copy_url),
-                (shareParams)
+                (view)
                         -> {
                     RecordUserAction.record("SharingHubAndroid.CopyURLSelected");
                     RecordHistogram.recordMediumTimesHistogram(
@@ -217,7 +217,7 @@
         PropertyModel propertyModel = ShareSheetPropertyModelBuilder.createPropertyModel(
                 AppCompatResources.getDrawable(mActivity, R.drawable.ic_content_copy_black),
                 mActivity.getResources().getString(R.string.sharing_copy_text),
-                (shareParams)
+                (view)
                         -> {
                     RecordUserAction.record("SharingHubAndroid.CopyTextSelected");
                     RecordHistogram.recordMediumTimesHistogram(
@@ -238,7 +238,7 @@
         PropertyModel propertyModel = ShareSheetPropertyModelBuilder.createPropertyModel(
                 AppCompatResources.getDrawable(mActivity, R.drawable.send_tab),
                 mActivity.getResources().getString(R.string.send_tab_to_self_share_activity_title),
-                (shareParams)
+                (view)
                         -> {
                     RecordUserAction.record("SharingHubAndroid.SendTabToSelfSelected");
                     RecordHistogram.recordMediumTimesHistogram(
@@ -264,7 +264,7 @@
         PropertyModel propertyModel = ShareSheetPropertyModelBuilder.createPropertyModel(
                 AppCompatResources.getDrawable(mActivity, R.drawable.qr_code),
                 mActivity.getResources().getString(R.string.qr_code_share_icon_label),
-                (currentActivity)
+                (view)
                         -> {
                     RecordUserAction.record("SharingHubAndroid.QRCodeSelected");
                     RecordHistogram.recordMediumTimesHistogram(
@@ -284,7 +284,7 @@
         PropertyModel propertyModel = ShareSheetPropertyModelBuilder.createPropertyModel(
                 AppCompatResources.getDrawable(mActivity, R.drawable.sharing_print),
                 mActivity.getResources().getString(R.string.print_share_activity_title),
-                (currentActivity)
+                (view)
                         -> {
                     RecordUserAction.record("SharingHubAndroid.PrintSelected");
                     RecordHistogram.recordMediumTimesHistogram(
diff --git a/chrome/browser/sharesheet/share_action.h b/chrome/browser/sharesheet/share_action.h
new file mode 100644
index 0000000..ded2ca70
--- /dev/null
+++ b/chrome/browser/sharesheet/share_action.h
@@ -0,0 +1,42 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SHARESHEET_SHARE_ACTION_H_
+#define CHROME_BROWSER_SHARESHEET_SHARE_ACTION_H_
+
+#include "base/strings/string16.h"
+#include "chrome/browser/sharesheet/sharesheet_controller.h"
+#include "ui/views/view.h"
+
+namespace sharesheet {
+
+// An interface implemented by each ShareAction.
+class ShareAction {
+ public:
+  explicit ShareAction(SharesheetController* controller);
+  virtual ~ShareAction() = default;
+
+  virtual const base::string16 GetActionName() = 0;
+
+  // LaunchAction should synchronously create all UI needed and fill
+  // the |root_view|. Methods on |controller| can be used to inform
+  // the sharesheet about the lifecycle of the ShareAction.
+  //
+  // |root_view| is a container within the larger share_sheet which should act
+  // as the parent view for ShareAction views. It is guaranteed that
+  // |root_view| will stay alive and visible until either
+  // ShareAction::OnClosing is called, or the ShareAction calls
+  // |controller|->ShareActionCompleted().
+  virtual void LaunchAction(views::View* root_view) = 0;
+
+  // OnClosing informs the ShareAction when the sharesheet is closed. This
+  // occurs when the user presses the back button out of the share action view
+  // or closes the sharesheet. All processes in ShareAction should shutdown when
+  // OnClosing is called, and not use |root_view| once the method completes.
+  virtual void OnClosing() = 0;
+};
+
+}  // namespace sharesheet
+
+#endif  // CHROME_BROWSER_SHARESHEET_SHARE_ACTION_H_
diff --git a/chrome/browser/sharesheet/sharesheet_controller.h b/chrome/browser/sharesheet/sharesheet_controller.h
new file mode 100644
index 0000000..37f0677
--- /dev/null
+++ b/chrome/browser/sharesheet/sharesheet_controller.h
@@ -0,0 +1,23 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SHARESHEET_SHARESHEET_CONTROLLER_H_
+#define CHROME_BROWSER_SHARESHEET_SHARESHEET_CONTROLLER_H_
+
+namespace sharesheet {
+
+// The SharesheetController allows ShareActions to request changes to the state
+// of the sharesheet.
+class SharesheetController {
+ public:
+  virtual ~SharesheetController() = default;
+
+  // Called by ShareAction to notify SharesheetBubbleView that ShareAction
+  // has completed.
+  virtual void ShareActionCompleted() = 0;
+};
+
+}  // namespace sharesheet
+
+#endif  // CHROME_BROWSER_SHARESHEET_SHARESHEET_CONTROLLER_H_
diff --git a/chrome/browser/sharesheet/sharesheet_service.cc b/chrome/browser/sharesheet/sharesheet_service.cc
index cf0a6c0..f8cfea9e 100644
--- a/chrome/browser/sharesheet/sharesheet_service.cc
+++ b/chrome/browser/sharesheet/sharesheet_service.cc
@@ -4,6 +4,11 @@
 
 #include "chrome/browser/sharesheet/sharesheet_service.h"
 
+#include <utility>
+
+#include "chrome/browser/sharesheet/sharesheet_service_delegate.h"
+#include "ui/views/view.h"
+
 namespace sharesheet {
 
 SharesheetService::SharesheetService(Profile* profile)
@@ -11,4 +16,26 @@
 
 SharesheetService::~SharesheetService() = default;
 
+void SharesheetService::ShowBubble(views::View* bubble_anchor_view) {
+  auto sharesheet_service_delegate =
+      std::make_unique<SharesheetServiceDelegate>(
+          delegate_counter_++, std::move(bubble_anchor_view), this);
+
+  sharesheet_service_delegate->ShowBubble();
+
+  active_delegates_.push_back(std::move(sharesheet_service_delegate));
+}
+
+// Cleanup delegate when bubble closes.
+void SharesheetService::OnBubbleClosed(uint32_t id) {
+  auto iter = active_delegates_.begin();
+  while (iter != active_delegates_.end()) {
+    if ((*iter)->GetId() == id) {
+      active_delegates_.erase(iter);
+      break;
+    }
+    ++iter;
+  }
+}
+
 }  // namespace sharesheet
diff --git a/chrome/browser/sharesheet/sharesheet_service.h b/chrome/browser/sharesheet/sharesheet_service.h
index 29f3e42..0c5606b 100644
--- a/chrome/browser/sharesheet/sharesheet_service.h
+++ b/chrome/browser/sharesheet/sharesheet_service.h
@@ -6,14 +6,21 @@
 #define CHROME_BROWSER_SHARESHEET_SHARESHEET_SERVICE_H_
 
 #include <memory>
+#include <vector>
 
 #include "chrome/browser/sharesheet/sharesheet_action_cache.h"
 #include "components/keyed_service/core/keyed_service.h"
 
 class Profile;
 
+namespace views {
+class View;
+}
+
 namespace sharesheet {
 
+class SharesheetServiceDelegate;
+
 // The SharesheetService is the root service that provides a sharesheet for
 // Chrome desktop.
 class SharesheetService : public KeyedService {
@@ -24,8 +31,16 @@
   SharesheetService(const SharesheetService&) = delete;
   SharesheetService& operator=(const SharesheetService&) = delete;
 
+  void ShowBubble(views::View* bubble_anchor_view);
+  void OnBubbleClosed(uint32_t id);
+
  private:
+  uint32_t delegate_counter_ = 0;
   std::unique_ptr<SharesheetActionCache> sharesheet_action_cache_;
+
+  // Record of all active SharesheetServiceDelegates. These can be retrieved
+  // by ShareActions and used as SharesheetControllers to make bubble changes.
+  std::vector<std::unique_ptr<SharesheetServiceDelegate>> active_delegates_;
 };
 
 }  // namespace sharesheet
diff --git a/chrome/browser/sharesheet/sharesheet_service_delegate.cc b/chrome/browser/sharesheet/sharesheet_service_delegate.cc
index e124271..4eb0b98 100644
--- a/chrome/browser/sharesheet/sharesheet_service_delegate.cc
+++ b/chrome/browser/sharesheet/sharesheet_service_delegate.cc
@@ -4,28 +4,41 @@
 
 #include "chrome/browser/sharesheet/sharesheet_service_delegate.h"
 
+#include "base/bind.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/sharesheet/sharesheet_service.h"
+#include "chrome/browser/sharesheet/sharesheet_service_factory.h"
 #include "chrome/browser/ui/views/sharesheet_bubble_view.h"
-#include "content/public/browser/web_contents.h"
 #include "ui/views/view.h"
 
 namespace sharesheet {
 
 SharesheetServiceDelegate::SharesheetServiceDelegate(
-    content::WebContents* web_contents,
-    views::View* bubble_anchor_view)
-    : sharesheet_bubble_view_(
-          std::make_unique<SharesheetBubbleView>(bubble_anchor_view)) {
-  Profile* const profile =
-      Profile::FromBrowserContext(web_contents->GetBrowserContext());
-  sharesheet_service_ = std::make_unique<SharesheetService>(profile);
-}
+    uint32_t id,
+    views::View* bubble_anchor_view,
+    SharesheetService* sharesheet_service)
+    : id_(id),
+      sharesheet_bubble_view_(
+          std::make_unique<SharesheetBubbleView>(bubble_anchor_view, this)),
+      sharesheet_service_(sharesheet_service) {}
 
 SharesheetServiceDelegate::~SharesheetServiceDelegate() = default;
 
+uint32_t SharesheetServiceDelegate::GetId() {
+  return id_;
+}
+
 void SharesheetServiceDelegate::ShowBubble() {
   sharesheet_bubble_view_->ShowBubble();
 }
 
+void SharesheetServiceDelegate::ShareActionCompleted() {
+  sharesheet_bubble_view_->CloseBubble();
+}
+
+void SharesheetServiceDelegate::OnBubbleClosed() {
+  sharesheet_bubble_view_.release();
+  sharesheet_service_->OnBubbleClosed(id_);
+}
+
 }  // namespace sharesheet
diff --git a/chrome/browser/sharesheet/sharesheet_service_delegate.h b/chrome/browser/sharesheet/sharesheet_service_delegate.h
index 5fdf67a..9a737c6 100644
--- a/chrome/browser/sharesheet/sharesheet_service_delegate.h
+++ b/chrome/browser/sharesheet/sharesheet_service_delegate.h
@@ -7,11 +7,9 @@
 
 #include <memory>
 
-class SharesheetBubbleView;
+#include "chrome/browser/sharesheet/sharesheet_controller.h"
 
-namespace content {
-class WebContents;
-}  // namespace content
+class SharesheetBubbleView;
 
 namespace views {
 class View;
@@ -21,20 +19,30 @@
 
 class SharesheetService;
 
-class SharesheetServiceDelegate {
+// The SharesheetServiceDelegate is the middle point between the UI and the
+// business logic in the sharesheet.
+class SharesheetServiceDelegate : public SharesheetController {
  public:
-  explicit SharesheetServiceDelegate(content::WebContents* web_contents,
-                                     views::View* bubble_anchor_view);
-  ~SharesheetServiceDelegate();
+  explicit SharesheetServiceDelegate(uint32_t id,
+                                     views::View* bubble_anchor_view,
+                                     SharesheetService* sharesheet_service);
+  ~SharesheetServiceDelegate() override;
   SharesheetServiceDelegate(const SharesheetServiceDelegate&) = delete;
   SharesheetServiceDelegate& operator=(const SharesheetServiceDelegate&) =
       delete;
 
+  uint32_t GetId();
+
   void ShowBubble();
+  void OnBubbleClosed();
+
+  // SharesheetController overrides
+  void ShareActionCompleted() override;
 
  private:
+  uint32_t id_;
   std::unique_ptr<SharesheetBubbleView> sharesheet_bubble_view_;
-  std::unique_ptr<SharesheetService> sharesheet_service_;
+  SharesheetService* sharesheet_service_;
 };
 
 }  // namespace sharesheet
diff --git a/chrome/browser/sharing/webrtc/sharing_service_host_unittest.cc b/chrome/browser/sharing/webrtc/sharing_service_host_unittest.cc
index 3d149c4..01c5a73 100644
--- a/chrome/browser/sharing/webrtc/sharing_service_host_unittest.cc
+++ b/chrome/browser/sharing/webrtc/sharing_service_host_unittest.cc
@@ -13,6 +13,7 @@
 #include "chrome/browser/sharing/mock_sharing_device_source.h"
 #include "chrome/browser/sharing/sharing_sync_preference.h"
 #include "chrome/browser/sharing/webrtc/sharing_webrtc_connection_host.h"
+#include "chrome/services/sharing/public/mojom/nearby_connections.mojom.h"
 #include "chrome/services/sharing/public/mojom/webrtc.mojom.h"
 #include "components/sync_device_info/fake_device_info_sync_service.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
@@ -29,8 +30,8 @@
 class FakeSharingMojoService : public sharing::mojom::Sharing,
                                public sharing::mojom::SignallingReceiver {
  public:
-  using NearbyConnectionsHostMojom =
-      location::nearby::connections::mojom::NearbyConnectionsHost;
+  using NearbyConnectionsDependenciesPtr =
+      location::nearby::connections::mojom::NearbyConnectionsDependenciesPtr;
 
   FakeSharingMojoService() = default;
   ~FakeSharingMojoService() override = default;
@@ -49,7 +50,7 @@
     signaling_set.Add(this, std::move(signalling_receiver));
   }
   void CreateNearbyConnections(
-      mojo::PendingRemote<NearbyConnectionsHostMojom> host,
+      NearbyConnectionsDependenciesPtr dependencies,
       CreateNearbyConnectionsCallback callback) override {
     NOTIMPLEMENTED();
   }
diff --git a/chrome/browser/site_isolation/spellcheck_per_process_browsertest.cc b/chrome/browser/site_isolation/spellcheck_per_process_browsertest.cc
index 58d4f7d..7b75c23 100644
--- a/chrome/browser/site_isolation/spellcheck_per_process_browsertest.cc
+++ b/chrome/browser/site_isolation/spellcheck_per_process_browsertest.cc
@@ -132,6 +132,10 @@
   }
 #endif  // defined(OS_WIN)
 
+  void InitializeDictionaries(
+      InitializeDictionariesCallback callback) override {
+    NOTREACHED();
+  }
 #endif  // BUILDFLAG(USE_BROWSER_SPELLCHECKER)
 
 #if defined(OS_ANDROID)
diff --git a/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc b/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc
index aa5b7ab..d881dff 100644
--- a/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc
+++ b/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc
@@ -210,6 +210,22 @@
 }
 #endif  // defined(OS_WIN)
 
+void SpellCheckHostChromeImpl::InitializeDictionaries(
+    InitializeDictionariesCallback callback) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  // Initialize the spellcheck service if needed. Initialization must
+  // happen on UI thread.
+  SpellcheckService* spellcheck = GetSpellcheckService();
+
+  if (!spellcheck) {  // Teardown.
+    std::move(callback).Run();
+    return;
+  }
+
+  spellcheck->InitializeDictionaries(std::move(callback));
+}
+
 void SpellCheckHostChromeImpl::OnRequestFinished(SpellingRequest* request) {
   auto iterator = requests_.find(request);
   requests_.erase(iterator);
diff --git a/chrome/browser/spellchecker/spell_check_host_chrome_impl.h b/chrome/browser/spellchecker/spell_check_host_chrome_impl.h
index 17bb3d8..35682a0 100644
--- a/chrome/browser/spellchecker/spell_check_host_chrome_impl.h
+++ b/chrome/browser/spellchecker/spell_check_host_chrome_impl.h
@@ -85,6 +85,8 @@
       GetPerLanguageSuggestionsCallback callback) override;
 #endif  // defined(OS_WIN)
 
+  void InitializeDictionaries(InitializeDictionariesCallback callback) override;
+
   // Clears a finished request from |requests_|. Exposed to SpellingRequest.
   void OnRequestFinished(SpellingRequest* request);
 
diff --git a/chrome/browser/spellchecker/spell_check_host_chrome_impl_win_browsertest.cc b/chrome/browser/spellchecker/spell_check_host_chrome_impl_win_browsertest.cc
index 6c8810f..e951f9c 100644
--- a/chrome/browser/spellchecker/spell_check_host_chrome_impl_win_browsertest.cc
+++ b/chrome/browser/spellchecker/spell_check_host_chrome_impl_win_browsertest.cc
@@ -27,8 +27,14 @@
 
 class SpellCheckHostChromeImplWinBrowserTest : public InProcessBrowserTest {
  public:
-  SpellCheckHostChromeImplWinBrowserTest() {
-    feature_list_.InitAndEnableFeature(spellcheck::kWinUseBrowserSpellChecker);
+  SpellCheckHostChromeImplWinBrowserTest() = default;
+
+  void SetUp() override {
+    // Don't delay initialization of the SpellcheckService on browser launch.
+    feature_list_.InitWithFeatures(
+        /*enabled_features=*/{spellcheck::kWinUseBrowserSpellChecker},
+        /*disabled_features=*/{spellcheck::kWinDelaySpellcheckServiceInit});
+    InProcessBrowserTest::SetUp();
   }
 
   void SetUpOnMainThread() override {
@@ -38,12 +44,16 @@
     SpellCheckHostChromeImpl::Create(
         renderer_->GetID(), spell_check_host_.BindNewPipeAndPassReceiver());
 
+    InitializeSpellcheckService();
+
     platform_spell_checker_ = SpellcheckServiceFactory::GetForContext(context)
                                   ->platform_spell_checker();
   }
 
   void TearDownOnMainThread() override { renderer_.reset(); }
 
+  virtual void InitializeSpellcheckService() {}
+
   void OnSpellcheckResult(const std::vector<SpellCheckResult>& result) {
     received_result_ = true;
     result_ = result;
@@ -76,6 +86,9 @@
     received_result_ = false;
   }
 
+  void RunSpellCheckReturnMessageTest();
+  void RunGetPerLanguageSuggestionsTest();
+
  protected:
   PlatformSpellChecker* platform_spell_checker_;
   base::test::ScopedFeatureList feature_list_;
@@ -91,6 +104,10 @@
 // Uses browsertest to setup chrome threads.
 IN_PROC_BROWSER_TEST_F(SpellCheckHostChromeImplWinBrowserTest,
                        SpellCheckReturnMessage) {
+  RunSpellCheckReturnMessageTest();
+}
+
+void SpellCheckHostChromeImplWinBrowserTest::RunSpellCheckReturnMessageTest() {
   if (!spellcheck::WindowsVersionSupportsSpellchecker()) {
     return;
   }
@@ -118,6 +135,11 @@
 
 IN_PROC_BROWSER_TEST_F(SpellCheckHostChromeImplWinBrowserTest,
                        GetPerLanguageSuggestions) {
+  RunGetPerLanguageSuggestionsTest();
+}
+
+void SpellCheckHostChromeImplWinBrowserTest::
+    RunGetPerLanguageSuggestionsTest() {
   if (!spellcheck::WindowsVersionSupportsSpellchecker()) {
     return;
   }
@@ -140,3 +162,45 @@
   ASSERT_EQ(1U, suggestion_result_.size());
   EXPECT_GT(suggestion_result_[0].size(), 0U);
 }
+
+class SpellCheckHostChromeImplWinBrowserTestDelayInit
+    : public SpellCheckHostChromeImplWinBrowserTest {
+ public:
+  SpellCheckHostChromeImplWinBrowserTestDelayInit() = default;
+
+  void SetUp() override {
+    // Don't initialize the SpellcheckService on browser launch.
+    feature_list_.InitWithFeatures(
+        /*enabled_features=*/{spellcheck::kWinUseBrowserSpellChecker,
+                              spellcheck::kWinDelaySpellcheckServiceInit},
+        /*disabled_features=*/{});
+    InProcessBrowserTest::SetUp();
+  }
+
+  void InitializeSpellcheckService() override {
+    // With the kWinDelaySpellcheckServiceInit feature flag set, the spellcheck
+    // service is not initialized when instantiated. Call InitializeDictionaries
+    // to load the dictionaries.
+    spell_check_host_->InitializeDictionaries(
+        base::BindOnce(&SpellCheckHostChromeImplWinBrowserTestDelayInit::
+                           InitializeDictionariesCallback,
+                       base::Unretained(this)));
+    RunUntilResultReceived();
+  }
+
+  void InitializeDictionariesCallback() {
+    received_result_ = true;
+    if (quit_)
+      std::move(quit_).Run();
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(SpellCheckHostChromeImplWinBrowserTestDelayInit,
+                       SpellCheckReturnMessage) {
+  RunSpellCheckReturnMessageTest();
+}
+
+IN_PROC_BROWSER_TEST_F(SpellCheckHostChromeImplWinBrowserTestDelayInit,
+                       GetPerLanguageSuggestions) {
+  RunGetPerLanguageSuggestionsTest();
+}
diff --git a/chrome/browser/spellchecker/spellcheck_service.cc b/chrome/browser/spellchecker/spellcheck_service.cc
index fbac180..d3dfa5e 100644
--- a/chrome/browser/spellchecker/spellcheck_service.cc
+++ b/chrome/browser/spellchecker/spellcheck_service.cc
@@ -392,6 +392,19 @@
   const PrefService* prefs = user_prefs::UserPrefs::Get(context);
   std::vector<spellcheck::mojom::SpellCheckBDictLanguagePtr> dictionaries;
 
+  bool enable_if_uninitialized = false;
+#if defined(OS_WIN)
+  if (spellcheck::UseBrowserSpellChecker() &&
+      base::FeatureList::IsEnabled(
+          spellcheck::kWinDelaySpellcheckServiceInit)) {
+    // If initialization of the spellcheck service is on-demand, the
+    // renderer-side SpellCheck object needs to start out as enabled in order
+    // for a click on editable content to initialize the spellcheck service.
+    if (!dictionaries_loaded())
+      enable_if_uninitialized = true;
+  }
+#endif  // defined(OS_WIN)
+
   for (const auto& hunspell_dictionary : hunspell_dictionaries_) {
     dictionaries.push_back(spellcheck::mojom::SpellCheckBDictLanguage::New(
         hunspell_dictionary->GetDictionaryFile().Duplicate(),
@@ -399,7 +412,7 @@
   }
 
   bool enable = prefs->GetBoolean(spellcheck::prefs::kSpellCheckEnable) &&
-                !dictionaries.empty();
+                (!dictionaries.empty() || enable_if_uninitialized);
 
   std::vector<std::string> custom_words;
   if (enable) {
diff --git a/chrome/browser/ssl/ocsp_browsertest.cc b/chrome/browser/ssl/ocsp_browsertest.cc
new file mode 100644
index 0000000..6ec682f
--- /dev/null
+++ b/chrome/browser/ssl/ocsp_browsertest.cc
@@ -0,0 +1,603 @@
+// Copyright 2020 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 <memory>
+
+#include "base/message_loop/message_loop_current.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/net/system_network_context_manager.h"
+#include "chrome/browser/policy/chrome_browser_policy_connector.h"
+#include "chrome/browser/ssl/ssl_browsertest_util.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_service.h"
+#include "components/security_interstitials/content/ssl_error_handler.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/network_service_instance.h"
+#include "content/public/browser/ssl_status.h"
+#include "content/public/common/network_service_util.h"
+#include "content/public/test/browser_test.h"
+#include "net/cert/ev_root_ca_metadata.h"
+#include "net/test/cert_test_util.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "services/network/public/cpp/features.h"
+#include "services/network/public/mojom/ssl_config.mojom.h"
+#include "third_party/blink/public/common/features.h"
+
+namespace AuthState = ssl_test_util::AuthState;
+
+namespace {
+
+// SHA256 hash of the testserver root_ca_cert DER.
+// openssl x509 -in root_ca_cert.pem -outform der | \
+//   openssl dgst -sha256 -binary | xxd -i
+static const net::SHA256HashValue kTestRootCertHash = {
+    {0xb2, 0xab, 0xa3, 0xa5, 0xd4, 0x11, 0x56, 0xcb, 0xb9, 0x23, 0x35,
+     0x07, 0x6d, 0x0b, 0x51, 0xbe, 0xd3, 0xee, 0x2e, 0xab, 0xe7, 0xab,
+     0x6b, 0xad, 0xcc, 0x2a, 0xfa, 0x35, 0xfb, 0x8e, 0x31, 0x5e}};
+
+// The test EV policy OID used for generated certs.
+static const char kOCSPTestCertPolicy[] = "1.3.6.1.4.1.11129.2.4.1";
+
+}  // namespace
+
+class OCSPBrowserTest : public PlatformBrowserTest,
+                        public ::testing::WithParamInterface<bool>,
+                        public network::mojom::SSLConfigClient {
+ public:
+  OCSPBrowserTest() = default;
+
+  void SetUp() override {
+    SystemNetworkContextManager::SetEnableCertificateTransparencyForTesting(
+        false);
+
+    EXPECT_CALL(policy_provider_, IsInitializationComplete(testing::_))
+        .WillRepeatedly(testing::Return(true));
+    policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
+        &policy_provider_);
+
+    InProcessBrowserTest::SetUp();
+    SSLErrorHandler::ResetConfigForTesting();
+  }
+
+  void TearDown() override {
+    InProcessBrowserTest::TearDown();
+
+    SystemNetworkContextManager::SetEnableCertificateTransparencyForTesting(
+        base::nullopt);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    std::vector<base::Feature> enabled_features;
+    std::vector<base::Feature> disabled_features;
+    disabled_features.push_back(blink::features::kMixedContentAutoupgrade);
+    if (GetParam()) {
+      enabled_features.push_back(network::features::kCertVerifierService);
+    } else {
+      disabled_features.push_back(network::features::kCertVerifierService);
+    }
+
+    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
+  }
+
+  void SetUpOnMainThread() override {
+    network::mojom::NetworkContextParamsPtr context_params =
+        g_browser_process->system_network_context_manager()
+            ->CreateDefaultNetworkContextParams();
+    last_ssl_config_ = *context_params->initial_ssl_config;
+    receiver_.Bind(std::move(context_params->ssl_config_client_receiver));
+
+    if (GetParam() || content::IsInProcessNetworkService()) {
+      // TODO(https://crbug.com/1085233): when the CertVerifierService is moved
+      // out of process, the ScopedTestEVPolicy needs to be instantiated in
+      // that process.
+      ev_test_policy_ = std::make_unique<net::ScopedTestEVPolicy>(
+          net::EVRootCAMetadata::GetInstance(), kTestRootCertHash,
+          kOCSPTestCertPolicy);
+    } else {
+      content::GetNetworkService()->BindTestInterface(
+          network_service_test_.BindNewPipeAndPassReceiver());
+      mojo::ScopedAllowSyncCallForTesting allow_sync_call;
+      EXPECT_TRUE(network_service_test_->SetEVPolicy(
+          std::vector<uint8_t>(
+              kTestRootCertHash.data,
+              kTestRootCertHash.data + sizeof(kTestRootCertHash.data)),
+          kOCSPTestCertPolicy));
+    }
+  }
+
+  // Sets the policy identified by |policy_name| to be true, ensuring
+  // that the corresponding boolean pref |pref_name| is updated to match.
+  void EnablePolicy(PrefService* pref_service,
+                    const char* policy_name,
+                    const char* pref_name) {
+    policy::PolicyMap policy_map;
+    policy_map.Set(policy_name, policy::POLICY_LEVEL_MANDATORY,
+                   policy::POLICY_SCOPE_MACHINE, policy::POLICY_SOURCE_CLOUD,
+                   std::make_unique<base::Value>(true), nullptr);
+
+    EXPECT_NO_FATAL_FAILURE(UpdateChromePolicy(policy_map));
+
+    EXPECT_TRUE(pref_service->GetBoolean(pref_name));
+    EXPECT_TRUE(pref_service->IsManagedPreference(pref_name));
+
+    // Wait for the updated SSL configuration to be sent to the network service,
+    // to avoid a race.
+    g_browser_process->system_network_context_manager()
+        ->FlushSSLConfigManagerForTesting();
+  }
+
+  void EnableRevocationChecking() {
+    // OCSP checking is disabled by default.
+    EXPECT_FALSE(last_ssl_config_.rev_checking_enabled);
+    EXPECT_FALSE(g_browser_process->system_network_context_manager()
+                     ->CreateDefaultNetworkContextParams()
+                     ->initial_ssl_config->rev_checking_enabled);
+
+    // Enable, and make sure the default network context params reflect the
+    // change.
+    base::RunLoop run_loop;
+    set_ssl_config_updated_callback(run_loop.QuitClosure());
+    ASSERT_NO_FATAL_FAILURE(
+        EnablePolicy(g_browser_process->local_state(),
+                     policy::key::kEnableOnlineRevocationChecks,
+                     prefs::kCertRevocationCheckingEnabled));
+    run_loop.Run();
+    EXPECT_TRUE(last_ssl_config_.rev_checking_enabled);
+    EXPECT_TRUE(g_browser_process->system_network_context_manager()
+                    ->CreateDefaultNetworkContextParams()
+                    ->initial_ssl_config->rev_checking_enabled);
+  }
+
+  void DoConnection(
+      const net::EmbeddedTestServer::ServerCertificateConfig& config) {
+    net::EmbeddedTestServer server(net::EmbeddedTestServer::TYPE_HTTPS);
+
+    server.SetSSLConfig(config);
+    server.AddDefaultHandlers(GetChromeTestDataDir());
+    ASSERT_TRUE(server.Start());
+
+    ui_test_utils::NavigateToURL(browser(), server.GetURL("/ssl/google.html"));
+  }
+
+  net::CertStatus GetCurrentCertStatus() {
+    content::NavigationEntry* entry =
+        chrome_test_utils::GetActiveWebContents(this)
+            ->GetController()
+            .GetVisibleEntry();
+    return entry->GetSSL().cert_status;
+  }
+
+  void set_ssl_config_updated_callback(
+      const base::RepeatingClosure& ssl_config_updated_callback) {
+    ssl_config_updated_callback_ = std::move(ssl_config_updated_callback);
+  }
+
+  // network::mojom::SSLConfigClient implementation.
+  void OnSSLConfigUpdated(network::mojom::SSLConfigPtr ssl_config) override {
+    last_ssl_config_ = *ssl_config;
+    if (ssl_config_updated_callback_)
+      ssl_config_updated_callback_.Run();
+  }
+
+  const network::mojom::SSLConfig& last_ssl_config() const {
+    return last_ssl_config_;
+  }
+
+ private:
+  void UpdateChromePolicy(const policy::PolicyMap& policies) {
+    policy_provider_.UpdateChromePolicy(policies);
+    ASSERT_TRUE(base::MessageLoopCurrent::Get());
+
+    base::RunLoop().RunUntilIdle();
+
+    content::FlushNetworkServiceInstanceForTesting();
+  }
+
+  policy::MockConfigurationPolicyProvider policy_provider_;
+
+  std::unique_ptr<net::ScopedTestEVPolicy> ev_test_policy_;
+  base::test::ScopedFeatureList scoped_feature_list_;
+  mojo::Remote<network::mojom::NetworkServiceTest> network_service_test_;
+
+  base::RepeatingClosure ssl_config_updated_callback_;
+  network::mojom::SSLConfig last_ssl_config_;
+  mojo::Receiver<network::mojom::SSLConfigClient> receiver_{this};
+};
+
+// Visits a page with revocation checking set to the default value (disabled)
+// and a revoked OCSP response.
+IN_PROC_BROWSER_TEST_P(OCSPBrowserTest, TestHTTPSOCSPRevokedButNotChecked) {
+  // OCSP checking is disabled by default.
+  EXPECT_FALSE(last_ssl_config().rev_checking_enabled);
+  EXPECT_FALSE(g_browser_process->system_network_context_manager()
+                   ->CreateDefaultNetworkContextParams()
+                   ->initial_ssl_config->rev_checking_enabled);
+
+  net::EmbeddedTestServer::ServerCertificateConfig revoked_cert_config;
+  revoked_cert_config.ocsp_config = net::EmbeddedTestServer::OCSPConfig(
+      {{net::OCSPRevocationStatus::REVOKED,
+        net::EmbeddedTestServer::OCSPConfig::SingleResponse::Date::kValid}});
+  DoConnection(revoked_cert_config);
+
+  ssl_test_util::CheckAuthenticatedState(
+      chrome_test_utils::GetActiveWebContents(this), AuthState::NONE);
+
+  net::CertStatus cert_status = GetCurrentCertStatus();
+  EXPECT_FALSE(cert_status & net::CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+// Visits a page with revocation checking enabled and a valid OCSP response.
+IN_PROC_BROWSER_TEST_P(OCSPBrowserTest, TestHTTPSOCSPOk) {
+  EnableRevocationChecking();
+
+  net::EmbeddedTestServer::ServerCertificateConfig ok_cert_config;
+  ok_cert_config.policy_oids = {kOCSPTestCertPolicy};
+  ok_cert_config.ocsp_config = net::EmbeddedTestServer::OCSPConfig(
+      {{net::OCSPRevocationStatus::GOOD,
+        net::EmbeddedTestServer::OCSPConfig::SingleResponse::Date::kValid}});
+
+  DoConnection(ok_cert_config);
+
+  ssl_test_util::CheckAuthenticatedState(
+      chrome_test_utils::GetActiveWebContents(this), AuthState::NONE);
+
+  net::CertStatus cert_status = GetCurrentCertStatus();
+  EXPECT_EQ(ssl_test_util::SystemUsesChromiumEVMetadata(),
+            static_cast<bool>(cert_status & net::CERT_STATUS_IS_EV));
+  EXPECT_TRUE(cert_status & net::CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+// Visits a page with revocation checking enabled and a revoked OCSP response.
+IN_PROC_BROWSER_TEST_P(OCSPBrowserTest, TestHTTPSOCSPRevoked) {
+  EnableRevocationChecking();
+
+  net::EmbeddedTestServer::ServerCertificateConfig revoked_cert_config;
+  revoked_cert_config.policy_oids = {kOCSPTestCertPolicy};
+  revoked_cert_config.ocsp_config = net::EmbeddedTestServer::OCSPConfig(
+      {{net::OCSPRevocationStatus::REVOKED,
+        net::EmbeddedTestServer::OCSPConfig::SingleResponse::Date::kValid}});
+
+  DoConnection(revoked_cert_config);
+
+  ssl_test_util::CheckAuthenticationBrokenState(
+      chrome_test_utils::GetActiveWebContents(this), net::CERT_STATUS_REVOKED,
+      AuthState::SHOWING_INTERSTITIAL);
+
+  net::CertStatus cert_status = GetCurrentCertStatus();
+  EXPECT_FALSE(cert_status & net::CERT_STATUS_IS_EV);
+  EXPECT_TRUE(cert_status & net::CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+IN_PROC_BROWSER_TEST_P(OCSPBrowserTest, TestHTTPSOCSPInvalid) {
+  EnableRevocationChecking();
+
+  net::EmbeddedTestServer::ServerCertificateConfig invalid_cert_config;
+  invalid_cert_config.policy_oids = {kOCSPTestCertPolicy};
+  invalid_cert_config.ocsp_config = net::EmbeddedTestServer::OCSPConfig(
+      net::EmbeddedTestServer::OCSPConfig::ResponseType::kInvalidResponse);
+
+  DoConnection(invalid_cert_config);
+
+  ssl_test_util::CheckAuthenticatedState(
+      chrome_test_utils::GetActiveWebContents(this), AuthState::NONE);
+
+  net::CertStatus cert_status = GetCurrentCertStatus();
+  EXPECT_FALSE(cert_status & net::CERT_STATUS_IS_EV);
+  EXPECT_TRUE(cert_status & net::CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+IN_PROC_BROWSER_TEST_P(OCSPBrowserTest, TestHTTPSOCSPIntermediateValid) {
+  EnableRevocationChecking();
+
+  net::EmbeddedTestServer::ServerCertificateConfig
+      intermediate_invalid_cert_config;
+  intermediate_invalid_cert_config.policy_oids = {kOCSPTestCertPolicy};
+  intermediate_invalid_cert_config.intermediate =
+      net::EmbeddedTestServer::IntermediateType::kInHandshake;
+  intermediate_invalid_cert_config
+      .ocsp_config = net::EmbeddedTestServer::OCSPConfig(
+      {{net::OCSPRevocationStatus::GOOD,
+        net::EmbeddedTestServer::OCSPConfig::SingleResponse::Date::kValid}});
+  intermediate_invalid_cert_config
+      .intermediate_ocsp_config = net::EmbeddedTestServer::OCSPConfig(
+      {{net::OCSPRevocationStatus::GOOD,
+        net::EmbeddedTestServer::OCSPConfig::SingleResponse::Date::kValid}});
+
+  DoConnection(intermediate_invalid_cert_config);
+
+  ssl_test_util::CheckAuthenticatedState(
+      chrome_test_utils::GetActiveWebContents(this), AuthState::NONE);
+
+  net::CertStatus cert_status = GetCurrentCertStatus();
+  EXPECT_EQ(ssl_test_util::SystemUsesChromiumEVMetadata(),
+            static_cast<bool>(cert_status & net::CERT_STATUS_IS_EV));
+  EXPECT_TRUE(cert_status & net::CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+IN_PROC_BROWSER_TEST_P(OCSPBrowserTest,
+                       TestHTTPSOCSPIntermediateResponseOldButStillValid) {
+  EnableRevocationChecking();
+
+  net::EmbeddedTestServer::ServerCertificateConfig cert_config;
+  cert_config.policy_oids = {kOCSPTestCertPolicy};
+  cert_config.intermediate =
+      net::EmbeddedTestServer::IntermediateType::kInHandshake;
+  cert_config.ocsp_config = net::EmbeddedTestServer::OCSPConfig(
+      {{net::OCSPRevocationStatus::GOOD,
+        net::EmbeddedTestServer::OCSPConfig::SingleResponse::Date::kValid}});
+  // Use an OCSP response for the intermediate that would be too old for a leaf
+  // cert, but is still valid for an intermediate.
+  cert_config.intermediate_ocsp_config = net::EmbeddedTestServer::OCSPConfig(
+      {{net::OCSPRevocationStatus::GOOD,
+        net::EmbeddedTestServer::OCSPConfig::SingleResponse::Date::kLong}});
+
+  DoConnection(cert_config);
+
+  ssl_test_util::CheckAuthenticatedState(
+      chrome_test_utils::GetActiveWebContents(this), AuthState::NONE);
+
+  net::CertStatus cert_status = GetCurrentCertStatus();
+  EXPECT_EQ(ssl_test_util::SystemUsesChromiumEVMetadata(),
+            static_cast<bool>(cert_status & net::CERT_STATUS_IS_EV));
+  EXPECT_TRUE(cert_status & net::CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+IN_PROC_BROWSER_TEST_P(OCSPBrowserTest,
+                       TestHTTPSOCSPIntermediateResponseTooOld) {
+  EnableRevocationChecking();
+
+  net::EmbeddedTestServer::ServerCertificateConfig cert_config;
+  cert_config.policy_oids = {kOCSPTestCertPolicy};
+  cert_config.intermediate =
+      net::EmbeddedTestServer::IntermediateType::kInHandshake;
+  cert_config.ocsp_config = net::EmbeddedTestServer::OCSPConfig(
+      {{net::OCSPRevocationStatus::GOOD,
+        net::EmbeddedTestServer::OCSPConfig::SingleResponse::Date::kValid}});
+  cert_config.intermediate_ocsp_config = net::EmbeddedTestServer::OCSPConfig(
+      {{net::OCSPRevocationStatus::GOOD,
+        net::EmbeddedTestServer::OCSPConfig::SingleResponse::Date::kLonger}});
+
+  DoConnection(cert_config);
+
+  ssl_test_util::CheckAuthenticatedState(
+      chrome_test_utils::GetActiveWebContents(this), AuthState::NONE);
+
+  net::CertStatus cert_status = GetCurrentCertStatus();
+  if (ssl_test_util::UsingBuiltinCertVerifier()) {
+    // The builtin verifier enforces the baseline requirements for max age of an
+    // intermediate's OCSP response, so the connection is considered non-EV.
+    EXPECT_EQ(0u, cert_status & net::CERT_STATUS_ALL_ERRORS);
+    EXPECT_EQ(0u, cert_status & net::CERT_STATUS_IS_EV);
+  } else {
+    // The platform verifiers are more lenient.
+    EXPECT_EQ(0u, cert_status & net::CERT_STATUS_ALL_ERRORS);
+    EXPECT_EQ(ssl_test_util::SystemUsesChromiumEVMetadata(),
+              static_cast<bool>(cert_status & net::CERT_STATUS_IS_EV));
+  }
+  EXPECT_TRUE(cert_status & net::CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+IN_PROC_BROWSER_TEST_P(OCSPBrowserTest, TestHTTPSOCSPIntermediateRevoked) {
+  EnableRevocationChecking();
+
+  net::EmbeddedTestServer::ServerCertificateConfig cert_config;
+  cert_config.policy_oids = {kOCSPTestCertPolicy};
+  cert_config.intermediate =
+      net::EmbeddedTestServer::IntermediateType::kInHandshake;
+  cert_config.ocsp_config = net::EmbeddedTestServer::OCSPConfig(
+      {{net::OCSPRevocationStatus::GOOD,
+        net::EmbeddedTestServer::OCSPConfig::SingleResponse::Date::kValid}});
+  cert_config.intermediate_ocsp_config = net::EmbeddedTestServer::OCSPConfig(
+      {{net::OCSPRevocationStatus::REVOKED,
+        net::EmbeddedTestServer::OCSPConfig::SingleResponse::Date::kValid}});
+
+  DoConnection(cert_config);
+
+  net::CertStatus cert_status = GetCurrentCertStatus();
+
+#if defined(OS_WIN)
+  // TODO(mattm): Seems to be flaky on Windows. Either returns
+  // CERT_STATUS_UNABLE_TO_CHECK_REVOCATION (which gets masked off due to
+  // soft-fail), or CERT_STATUS_REVOKED.
+  EXPECT_THAT(cert_status & net::CERT_STATUS_ALL_ERRORS,
+              ::testing::AnyOf(0u, net::CERT_STATUS_REVOKED));
+#else
+  ssl_test_util::CheckAuthenticationBrokenState(
+      chrome_test_utils::GetActiveWebContents(this), net::CERT_STATUS_REVOKED,
+      AuthState::SHOWING_INTERSTITIAL);
+#endif
+
+  EXPECT_EQ(0u, cert_status & net::CERT_STATUS_IS_EV);
+  EXPECT_TRUE(cert_status & net::CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+IN_PROC_BROWSER_TEST_P(OCSPBrowserTest, TestHTTPSOCSPValidStapled) {
+  if (!ssl_test_util::SystemSupportsOCSPStapling()) {
+    LOG(WARNING)
+        << "Skipping test because system doesn't support OCSP stapling";
+    return;
+  }
+
+  EnableRevocationChecking();
+
+  net::EmbeddedTestServer::ServerCertificateConfig cert_config;
+  cert_config.policy_oids = {kOCSPTestCertPolicy};
+
+  // AIA OCSP url is included, but does not return a successful ocsp response.
+  cert_config.ocsp_config = net::EmbeddedTestServer::OCSPConfig(
+      net::EmbeddedTestServer::OCSPConfig::ResponseType::kTryLater);
+
+  cert_config.stapled_ocsp_config = net::EmbeddedTestServer::OCSPConfig(
+      {{net::OCSPRevocationStatus::GOOD,
+        net::EmbeddedTestServer::OCSPConfig::SingleResponse::Date::kValid}});
+
+  DoConnection(cert_config);
+
+  ssl_test_util::CheckAuthenticatedState(
+      chrome_test_utils::GetActiveWebContents(this), AuthState::NONE);
+
+  net::CertStatus cert_status = GetCurrentCertStatus();
+  EXPECT_EQ(ssl_test_util::SystemUsesChromiumEVMetadata(),
+            static_cast<bool>(cert_status & net::CERT_STATUS_IS_EV));
+  EXPECT_TRUE(cert_status & net::CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+IN_PROC_BROWSER_TEST_P(OCSPBrowserTest, TestHTTPSOCSPRevokedStapled) {
+  if (!ssl_test_util::SystemSupportsOCSPStapling()) {
+    LOG(WARNING)
+        << "Skipping test because system doesn't support OCSP stapling";
+    return;
+  }
+
+  EnableRevocationChecking();
+
+  net::EmbeddedTestServer::ServerCertificateConfig cert_config;
+  cert_config.policy_oids = {kOCSPTestCertPolicy};
+
+  // AIA OCSP url is included, but does not return a successful ocsp response.
+  cert_config.ocsp_config = net::EmbeddedTestServer::OCSPConfig(
+      net::EmbeddedTestServer::OCSPConfig::ResponseType::kTryLater);
+
+  cert_config.stapled_ocsp_config = net::EmbeddedTestServer::OCSPConfig(
+      {{net::OCSPRevocationStatus::REVOKED,
+        net::EmbeddedTestServer::OCSPConfig::SingleResponse::Date::kValid}});
+
+  DoConnection(cert_config);
+
+  ssl_test_util::CheckAuthenticationBrokenState(
+      chrome_test_utils::GetActiveWebContents(this), net::CERT_STATUS_REVOKED,
+      AuthState::SHOWING_INTERSTITIAL);
+
+  net::CertStatus cert_status = GetCurrentCertStatus();
+  EXPECT_FALSE(cert_status & net::CERT_STATUS_IS_EV);
+  EXPECT_TRUE(cert_status & net::CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+IN_PROC_BROWSER_TEST_P(OCSPBrowserTest, TestHTTPSOCSPOldStapledAndInvalidAIA) {
+  if (!ssl_test_util::SystemSupportsOCSPStapling()) {
+    LOG(WARNING)
+        << "Skipping test because system doesn't support OCSP stapling";
+    return;
+  }
+
+  EnableRevocationChecking();
+
+  net::EmbeddedTestServer::ServerCertificateConfig cert_config;
+  cert_config.policy_oids = {kOCSPTestCertPolicy};
+  // Stapled response indicates good, but is too old.
+  cert_config.stapled_ocsp_config = net::EmbeddedTestServer::OCSPConfig(
+      {{net::OCSPRevocationStatus::GOOD,
+        net::EmbeddedTestServer::OCSPConfig::SingleResponse::Date::kOld}});
+
+  // AIA OCSP url is included, but does not return a successful ocsp response.
+  cert_config.ocsp_config = net::EmbeddedTestServer::OCSPConfig(
+      net::EmbeddedTestServer::OCSPConfig::ResponseType::kTryLater);
+
+  DoConnection(cert_config);
+  ssl_test_util::CheckAuthenticatedState(
+      chrome_test_utils::GetActiveWebContents(this), AuthState::NONE);
+
+  net::CertStatus cert_status = GetCurrentCertStatus();
+  EXPECT_FALSE(cert_status & net::CERT_STATUS_IS_EV);
+  EXPECT_TRUE(cert_status & net::CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+IN_PROC_BROWSER_TEST_P(OCSPBrowserTest, TestHTTPSOCSPOldStapledButValidAIA) {
+  if (!ssl_test_util::SystemSupportsOCSPStapling()) {
+    LOG(WARNING)
+        << "Skipping test because system doesn't support OCSP stapling";
+    return;
+  }
+
+  EnableRevocationChecking();
+
+  net::EmbeddedTestServer::ServerCertificateConfig cert_config;
+  cert_config.policy_oids = {kOCSPTestCertPolicy};
+
+  // Stapled response indicates good, but response is too old.
+  cert_config.stapled_ocsp_config = net::EmbeddedTestServer::OCSPConfig(
+      {{net::OCSPRevocationStatus::GOOD,
+        net::EmbeddedTestServer::OCSPConfig::SingleResponse::Date::kOld}});
+
+  // AIA OCSP url is included, and returns a successful ocsp response.
+  cert_config.ocsp_config = net::EmbeddedTestServer::OCSPConfig(
+      {{net::OCSPRevocationStatus::GOOD,
+        net::EmbeddedTestServer::OCSPConfig::SingleResponse::Date::kValid}});
+
+  DoConnection(cert_config);
+  ssl_test_util::CheckAuthenticatedState(
+      chrome_test_utils::GetActiveWebContents(this), AuthState::NONE);
+
+  net::CertStatus cert_status = GetCurrentCertStatus();
+  EXPECT_EQ(ssl_test_util::SystemUsesChromiumEVMetadata(),
+            static_cast<bool>(cert_status & net::CERT_STATUS_IS_EV));
+  EXPECT_TRUE(cert_status & net::CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+IN_PROC_BROWSER_TEST_P(OCSPBrowserTest, HardFailOnOCSPInvalid) {
+  if (!ssl_test_util::SystemSupportsHardFailRevocationChecking()) {
+    LOG(WARNING) << "Skipping test because system doesn't support hard fail "
+                 << "revocation checking";
+    return;
+  }
+
+  // OCSP checking is disabled by default.
+  EXPECT_FALSE(last_ssl_config().rev_checking_required_local_anchors);
+  EXPECT_FALSE(g_browser_process->system_network_context_manager()
+                   ->CreateDefaultNetworkContextParams()
+                   ->initial_ssl_config->rev_checking_required_local_anchors);
+
+  // Enable, and make sure the default network context params reflect the
+  // change.
+  base::RunLoop run_loop;
+  set_ssl_config_updated_callback(run_loop.QuitClosure());
+  ASSERT_NO_FATAL_FAILURE(
+      EnablePolicy(g_browser_process->local_state(),
+                   policy::key::kRequireOnlineRevocationChecksForLocalAnchors,
+                   prefs::kCertRevocationCheckingRequiredLocalAnchors));
+  run_loop.Run();
+  EXPECT_TRUE(last_ssl_config().rev_checking_required_local_anchors);
+  EXPECT_TRUE(g_browser_process->system_network_context_manager()
+                  ->CreateDefaultNetworkContextParams()
+                  ->initial_ssl_config->rev_checking_required_local_anchors);
+
+  net::EmbeddedTestServer::ServerCertificateConfig cert_config;
+  cert_config.policy_oids = {kOCSPTestCertPolicy};
+  cert_config.ocsp_config = net::EmbeddedTestServer::OCSPConfig(
+      net::EmbeddedTestServer::OCSPConfig::ResponseType::kInvalidResponse);
+
+  DoConnection(cert_config);
+
+  ssl_test_util::CheckAuthenticationBrokenState(
+      chrome_test_utils::GetActiveWebContents(this),
+      net::CERT_STATUS_UNABLE_TO_CHECK_REVOCATION,
+      AuthState::SHOWING_INTERSTITIAL);
+
+  net::CertStatus cert_status = GetCurrentCertStatus();
+  // Without a positive OCSP response, we shouldn't show the EV status.
+  EXPECT_FALSE(cert_status & net::CERT_STATUS_IS_EV);
+  EXPECT_TRUE(cert_status & net::CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+INSTANTIATE_TEST_SUITE_P(/* no prefix */, OCSPBrowserTest, ::testing::Bool());
+
+using AIABrowserTest = OCSPBrowserTest;
+
+IN_PROC_BROWSER_TEST_P(AIABrowserTest, TestHTTPSAIA) {
+  net::EmbeddedTestServer::ServerCertificateConfig cert_config;
+  cert_config.intermediate = net::EmbeddedTestServer::IntermediateType::kByAIA;
+
+  DoConnection(cert_config);
+  ssl_test_util::CheckAuthenticatedState(
+      chrome_test_utils::GetActiveWebContents(this), AuthState::NONE);
+}
+
+INSTANTIATE_TEST_SUITE_P(/* no prefix */, AIABrowserTest, ::testing::Bool());
diff --git a/chrome/browser/ssl/ssl_browsertest.cc b/chrome/browser/ssl/ssl_browsertest.cc
index a5578a3..4d87caac 100644
--- a/chrome/browser/ssl/ssl_browsertest.cc
+++ b/chrome/browser/ssl/ssl_browsertest.cc
@@ -398,8 +398,6 @@
         https_server_mismatched_(net::EmbeddedTestServer::TYPE_HTTPS),
         https_server_sha1_(net::EmbeddedTestServer::TYPE_HTTPS),
         https_server_common_name_only_(net::EmbeddedTestServer::TYPE_HTTPS),
-        https_server_ocsp_ok_(net::EmbeddedTestServer::TYPE_HTTPS),
-        https_server_ocsp_revoked_(net::EmbeddedTestServer::TYPE_HTTPS),
         wss_server_expired_(net::SpawnedTestServer::TYPE_WSS,
                             SSLOptions(SSLOptions::CERT_EXPIRED),
                             net::GetWebSocketTestDataDirectory()),
@@ -421,20 +419,6 @@
     https_server_common_name_only_.SetSSLConfig(
         net::EmbeddedTestServer::CERT_COMMON_NAME_ONLY);
     https_server_common_name_only_.AddDefaultHandlers(GetChromeTestDataDir());
-
-    net::EmbeddedTestServer::ServerCertificateConfig ok_cert_config;
-    ok_cert_config.ocsp_config = net::EmbeddedTestServer::OCSPConfig(
-        {{net::OCSPRevocationStatus::GOOD,
-          net::EmbeddedTestServer::OCSPConfig::SingleResponse::Date::kValid}});
-    https_server_ocsp_ok_.SetSSLConfig(ok_cert_config);
-    https_server_ocsp_ok_.AddDefaultHandlers(GetChromeTestDataDir());
-
-    net::EmbeddedTestServer::ServerCertificateConfig revoked_cert_config;
-    revoked_cert_config.ocsp_config = net::EmbeddedTestServer::OCSPConfig(
-        {{net::OCSPRevocationStatus::REVOKED,
-          net::EmbeddedTestServer::OCSPConfig::SingleResponse::Date::kValid}});
-    https_server_ocsp_revoked_.SetSSLConfig(revoked_cert_config);
-    https_server_ocsp_revoked_.AddDefaultHandlers(GetChromeTestDataDir());
   }
 
   void SetUp() override {
@@ -799,16 +783,9 @@
     EXPECT_EQ(app_url, new_tab->GetVisibleURL());
   }
 
-  void set_ssl_config_updated_callback(
-      const base::RepeatingClosure& ssl_config_updated_callback) {
-    ssl_config_updated_callback_ = std::move(ssl_config_updated_callback);
-  }
-
   // network::mojom::SSLConfigClient implementation.
   void OnSSLConfigUpdated(network::mojom::SSLConfigPtr ssl_config) override {
     last_ssl_config_ = *ssl_config;
-    if (ssl_config_updated_callback_)
-      ssl_config_updated_callback_.Run();
   }
 
  protected:
@@ -880,15 +857,12 @@
   net::EmbeddedTestServer https_server_mismatched_;
   net::EmbeddedTestServer https_server_sha1_;
   net::EmbeddedTestServer https_server_common_name_only_;
-  net::EmbeddedTestServer https_server_ocsp_ok_;
-  net::EmbeddedTestServer https_server_ocsp_revoked_;
 
   net::SpawnedTestServer wss_server_expired_;
   net::SpawnedTestServer wss_server_mismatched_;
 
   policy::MockConfigurationPolicyProvider policy_provider_;
 
-  base::RepeatingClosure ssl_config_updated_callback_;
   network::mojom::SSLConfig last_ssl_config_;
   mojo::Receiver<network::mojom::SSLConfigClient> receiver_{this};
 
@@ -1616,100 +1590,6 @@
   ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);
 }
 
-// Visits a page with revocation checking enabled and a valid OCSP response.
-IN_PROC_BROWSER_TEST_F(SSLUITest, TestHTTPSOCSPOk) {
-  // OCSP checking is disabled by default.
-  EXPECT_FALSE(last_ssl_config_.rev_checking_enabled);
-  EXPECT_FALSE(CreateDefaultNetworkContextParams()
-                   ->initial_ssl_config->rev_checking_enabled);
-
-  // Enable, and make sure the default network context params reflect the
-  // change.
-  base::RunLoop run_loop;
-  set_ssl_config_updated_callback(run_loop.QuitClosure());
-  ASSERT_NO_FATAL_FAILURE(
-      EnablePolicy(g_browser_process->local_state(),
-                   policy::key::kEnableOnlineRevocationChecks,
-                   prefs::kCertRevocationCheckingEnabled));
-  run_loop.Run();
-  EXPECT_TRUE(last_ssl_config_.rev_checking_enabled);
-  EXPECT_TRUE(CreateDefaultNetworkContextParams()
-                  ->initial_ssl_config->rev_checking_enabled);
-
-  ASSERT_TRUE(https_server_ocsp_ok_.Start());
-
-  ui_test_utils::NavigateToURL(
-      browser(), https_server_ocsp_ok_.GetURL("/ssl/google.html"));
-
-  ssl_test_util::CheckAuthenticatedState(
-      browser()->tab_strip_model()->GetActiveWebContents(), AuthState::NONE);
-
-  content::NavigationEntry* entry = browser()
-                                        ->tab_strip_model()
-                                        ->GetActiveWebContents()
-                                        ->GetController()
-                                        .GetVisibleEntry();
-  ASSERT_TRUE(entry);
-  EXPECT_TRUE(entry->GetSSL().cert_status &
-              net::CERT_STATUS_REV_CHECKING_ENABLED);
-}
-
-// Visits a page with revocation checking enabled and a revoked OCSP response.
-IN_PROC_BROWSER_TEST_F(SSLUITest, TestHTTPSOCSPRevoked) {
-  // OCSP checking is disabled by default.
-  EXPECT_FALSE(last_ssl_config_.rev_checking_enabled);
-  EXPECT_FALSE(CreateDefaultNetworkContextParams()
-                   ->initial_ssl_config->rev_checking_enabled);
-
-  // Enable, and make sure the default network context params reflect the
-  // change.
-  base::RunLoop run_loop;
-  set_ssl_config_updated_callback(run_loop.QuitClosure());
-  ASSERT_NO_FATAL_FAILURE(
-      EnablePolicy(g_browser_process->local_state(),
-                   policy::key::kEnableOnlineRevocationChecks,
-                   prefs::kCertRevocationCheckingEnabled));
-  run_loop.Run();
-  EXPECT_TRUE(last_ssl_config_.rev_checking_enabled);
-  EXPECT_TRUE(CreateDefaultNetworkContextParams()
-                  ->initial_ssl_config->rev_checking_enabled);
-
-  ASSERT_TRUE(https_server_ocsp_revoked_.Start());
-
-  ui_test_utils::NavigateToURL(
-      browser(), https_server_ocsp_revoked_.GetURL("/ssl/google.html"));
-
-  ssl_test_util::CheckAuthenticationBrokenState(
-      browser()->tab_strip_model()->GetActiveWebContents(),
-      net::CERT_STATUS_REVOKED, AuthState::SHOWING_INTERSTITIAL);
-}
-
-// Visits a page with revocation checking set to the default value (disabled)
-// and a revoked OCSP response.
-IN_PROC_BROWSER_TEST_F(SSLUITest, TestHTTPSOCSPRevokedButNotChecked) {
-  // OCSP checking is disabled by default.
-  EXPECT_FALSE(last_ssl_config_.rev_checking_enabled);
-  EXPECT_FALSE(CreateDefaultNetworkContextParams()
-                   ->initial_ssl_config->rev_checking_enabled);
-
-  ASSERT_TRUE(https_server_ocsp_revoked_.Start());
-
-  ui_test_utils::NavigateToURL(
-      browser(), https_server_ocsp_revoked_.GetURL("/ssl/google.html"));
-
-  ssl_test_util::CheckAuthenticatedState(
-      browser()->tab_strip_model()->GetActiveWebContents(), AuthState::NONE);
-
-  content::NavigationEntry* entry = browser()
-                                        ->tab_strip_model()
-                                        ->GetActiveWebContents()
-                                        ->GetController()
-                                        .GetVisibleEntry();
-  ASSERT_TRUE(entry);
-  EXPECT_FALSE(entry->GetSSL().cert_status &
-               net::CERT_STATUS_REV_CHECKING_ENABLED);
-}
-
 // Visits a page that uses a SHA-1 leaf certificate, which should be rejected
 // by default.
 IN_PROC_BROWSER_TEST_F(SSLUITest, SHA1IsDefaultDisabled) {
diff --git a/chrome/browser/ssl/ssl_browsertest_util.cc b/chrome/browser/ssl/ssl_browsertest_util.cc
index dabb084..dcf5ae1 100644
--- a/chrome/browser/ssl/ssl_browsertest_util.cc
+++ b/chrome/browser/ssl/ssl_browsertest_util.cc
@@ -19,10 +19,15 @@
 #include "mojo/public/cpp/bindings/sync_call_restrictions.h"
 #include "net/base/features.h"
 #include "net/cert/cert_status_flags.h"
+#include "net/cert/ev_root_ca_metadata.h"
 #include "net/net_buildflags.h"
 #include "services/network/public/mojom/network_context.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if defined(OS_MACOSX)
+#include "base/mac/mac_util.h"
+#endif
+
 namespace ssl_test_util {
 
 namespace AuthState {
@@ -134,7 +139,7 @@
   run_loop_.Quit();
 }
 
-static bool UsingBuiltinCertVerifier() {
+bool UsingBuiltinCertVerifier() {
 #if defined(OS_FUCHSIA) || defined(OS_LINUX) || defined(OS_CHROMEOS)
   return true;
 #elif BUILDFLAG(BUILTIN_CERT_VERIFIER_FEATURE_SUPPORTED)
@@ -144,6 +149,42 @@
   return false;
 }
 
+bool SystemSupportsHardFailRevocationChecking() {
+  if (UsingBuiltinCertVerifier())
+    return true;
+#if defined(OS_WIN)
+  return true;
+#else
+  return false;
+#endif
+}
+
+bool SystemUsesChromiumEVMetadata() {
+  if (UsingBuiltinCertVerifier())
+    return true;
+#if defined(PLATFORM_USES_CHROMIUM_EV_METADATA)
+  return true;
+#else
+  return false;
+#endif
+}
+
+bool SystemSupportsOCSPStapling() {
+  if (UsingBuiltinCertVerifier())
+    return true;
+#if defined(OS_ANDROID)
+  return false;
+#elif defined(OS_MACOSX)
+  // The SecTrustSetOCSPResponse function exists since macOS 10.9+, but does
+  // not actually do anything until 10.12.
+  if (base::mac::IsAtLeastOS10_12())
+    return true;
+  return false;
+#else
+  return true;
+#endif
+}
+
 bool CertVerifierSupportsCRLSetBlocking() {
   if (UsingBuiltinCertVerifier())
     return true;
diff --git a/chrome/browser/ssl/ssl_browsertest_util.h b/chrome/browser/ssl/ssl_browsertest_util.h
index 93dfa04..29411e7 100644
--- a/chrome/browser/ssl/ssl_browsertest_util.h
+++ b/chrome/browser/ssl/ssl_browsertest_util.h
@@ -88,6 +88,28 @@
   base::RunLoop run_loop_;
 };
 
+// Returns true if Chrome will use its builtin cert verifier rather than the
+// operating system's default.
+bool UsingBuiltinCertVerifier();
+
+// SystemSupportsHardFailRevocationChecking returns true iff the current
+// operating system supports revocation checking and can distinguish between
+// situations where a given certificate lacks any revocation information (eg:
+// no CRLDistributionPoints and no OCSP Responder AuthorityInfoAccess) and when
+// revocation information cannot be obtained (eg: the CRL was unreachable).
+// If it does not, then tests which rely on 'hard fail' behaviour should be
+// skipped.
+bool SystemSupportsHardFailRevocationChecking();
+
+// SystemUsesChromiumEVMetadata returns true iff the current operating system
+// uses Chromium's EV metadata (i.e. EVRootCAMetadata). If it does not, then
+// several tests are effected because our testing EV certificate won't be
+// recognised as EV.
+bool SystemUsesChromiumEVMetadata();
+
+// Returns true iff OCSP stapling is supported on this operating system.
+bool SystemSupportsOCSPStapling();
+
 // Returns |true| if the default CertVerifier used by the NetworkService is
 // expected to support blocking certificates that appear within a CRLSet.
 bool CertVerifierSupportsCRLSetBlocking();
diff --git a/chrome/browser/supervised_user/supervised_user_url_filter.cc b/chrome/browser/supervised_user/supervised_user_url_filter.cc
index 031a24d..210605d 100644
--- a/chrome/browser/supervised_user/supervised_user_url_filter.cc
+++ b/chrome/browser/supervised_user/supervised_user_url_filter.cc
@@ -256,8 +256,7 @@
 
   std::string trimmed_host = canonical_host;
   if (is_host_www && !patern_accepts) {
-    trimmed_host = base::UTF16ToASCII(
-        url_formatter::StripWWW(base::ASCIIToUTF16(canonical_host)));
+    trimmed_host = url_formatter::StripWWW(canonical_host);
   }
 
   std::string trimmed_pattern = pattern;
diff --git a/chrome/browser/sync/test/integration/printers_helper.cc b/chrome/browser/sync/test/integration/printers_helper.cc
index 37135ae..a1aa62a 100644
--- a/chrome/browser/sync/test/integration/printers_helper.cc
+++ b/chrome/browser/sync/test/integration/printers_helper.cc
@@ -105,7 +105,7 @@
 chromeos::Printer CreateTestPrinter(int index) {
   chromeos::Printer printer(PrinterId(index));
   printer.set_description("Description");
-  printer.set_uri(base::StringPrintf("ipp://192.168.1.%d", index));
+  printer.SetUri(base::StringPrintf("ipp://192.168.1.%d", index));
 
   return printer;
 }
diff --git a/chrome/browser/task_manager/providers/task.cc b/chrome/browser/task_manager/providers/task.cc
index 37b9b0f..c125387 100644
--- a/chrome/browser/task_manager/providers/task.cc
+++ b/chrome/browser/task_manager/providers/task.cc
@@ -6,6 +6,7 @@
 
 #include <stddef.h>
 
+#include "base/numerics/safe_conversions.h"
 #include "base/process/process.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile.h"
@@ -92,15 +93,13 @@
 
   int64_t current_cycle_read_byte_count =
       cumulative_bytes_read_ - last_refresh_cumulative_bytes_read_;
-  network_read_rate_ =
-      (current_cycle_read_byte_count * base::TimeDelta::FromSeconds(1)) /
-      update_interval;
+  network_read_rate_ = base::Round<int64_t>(current_cycle_read_byte_count /
+                                            update_interval.InSecondsF());
 
   int64_t current_cycle_sent_byte_count =
       cumulative_bytes_sent_ - last_refresh_cumulative_bytes_sent_;
-  network_sent_rate_ =
-      (current_cycle_sent_byte_count * base::TimeDelta::FromSeconds(1)) /
-      update_interval;
+  network_sent_rate_ = base::Round<int64_t>(current_cycle_sent_byte_count /
+                                            update_interval.InSecondsF());
 
   last_refresh_cumulative_bytes_read_ = cumulative_bytes_read_;
   last_refresh_cumulative_bytes_sent_ = cumulative_bytes_sent_;
diff --git a/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillBridge.java b/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillBridge.java
index 672b7bc5..d72a200 100644
--- a/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillBridge.java
+++ b/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillBridge.java
@@ -6,8 +6,8 @@
 
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.NativeMethods;
-import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.touch_to_fill.data.Credential;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetControllerProvider;
 import org.chromium.ui.base.WindowAndroid;
 
 import java.util.Arrays;
@@ -22,9 +22,9 @@
 
     private TouchToFillBridge(long nativeView, WindowAndroid windowAndroid) {
         mNativeView = nativeView;
-        ChromeActivity activity = (ChromeActivity) windowAndroid.getActivity().get();
         mTouchToFillComponent = new TouchToFillCoordinator();
-        mTouchToFillComponent.initialize(activity, activity.getBottomSheetController(), this);
+        mTouchToFillComponent.initialize(windowAndroid.getContext().get(),
+                BottomSheetControllerProvider.from(windowAndroid), this);
     }
 
     @CalledByNative
diff --git a/chrome/browser/touch_to_fill/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillIntegrationTest.java b/chrome/browser/touch_to_fill/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillIntegrationTest.java
index d4b476b..58af50c 100644
--- a/chrome/browser/touch_to_fill/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillIntegrationTest.java
+++ b/chrome/browser/touch_to_fill/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillIntegrationTest.java
@@ -42,6 +42,7 @@
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.SheetState;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetControllerProvider;
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.content_public.browser.test.util.TouchCommon;
 
@@ -70,6 +71,8 @@
     @Rule
     public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
 
+    private BottomSheetController mBottomSheetController;
+
     public TouchToFillIntegrationTest() {
         MockitoAnnotations.initMocks(this);
     }
@@ -78,8 +81,10 @@
     public void setUp() throws InterruptedException {
         mActivityTestRule.startMainActivityOnBlankPage();
         runOnUiThreadBlocking(() -> {
-            mTouchToFill.initialize(mActivityTestRule.getActivity(),
-                    mActivityTestRule.getActivity().getBottomSheetController(), mMockBridge);
+            mBottomSheetController = BottomSheetControllerProvider.from(
+                    mActivityTestRule.getActivity().getWindowAndroid());
+            mTouchToFill.initialize(
+                    mActivityTestRule.getActivity(), mBottomSheetController, mMockBridge);
         });
     }
 
@@ -117,8 +122,6 @@
     @MediumTest
     @SuppressLint("SetTextI18n")
     public void testDismissedIfUnableToShow() throws Exception {
-        BottomSheetController bottomSheetController =
-                mActivityTestRule.getActivity().getBottomSheetController();
         BottomSheetContent otherBottomSheetContent = runOnUiThreadBlocking(() -> {
             TextView highPriorityBottomSheetContentView =
                     new TextView(mActivityTestRule.getActivity());
@@ -173,7 +176,7 @@
                     return 0;
                 }
             };
-            bottomSheetController.requestShowContent(content, /* animate = */ false);
+            mBottomSheetController.requestShowContent(content, /* animate = */ false);
             return content;
         });
         pollUiThread(() -> getBottomSheetState() == SheetState.PEEK);
@@ -187,7 +190,7 @@
         Espresso.onView(withText("Another bottom sheet content")).check(matches(isDisplayed()));
 
         runOnUiThreadBlocking(() -> {
-            bottomSheetController.hideContent(otherBottomSheetContent, /* animate = */ false);
+            mBottomSheetController.hideContent(otherBottomSheetContent, /* animate = */ false);
         });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HIDDEN);
     }
@@ -202,6 +205,6 @@
     }
 
     private @SheetState int getBottomSheetState() {
-        return mActivityTestRule.getActivity().getBottomSheetController().getSheetState();
+        return mBottomSheetController.getSheetState();
     }
 }
diff --git a/chrome/browser/touch_to_fill/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillViewTest.java b/chrome/browser/touch_to_fill/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillViewTest.java
index d7d0086..1cd68320 100644
--- a/chrome/browser/touch_to_fill/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillViewTest.java
+++ b/chrome/browser/touch_to_fill/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillViewTest.java
@@ -81,6 +81,7 @@
 
     private PropertyModel mModel;
     private TouchToFillView mTouchToFillView;
+    private BottomSheetController mBottomSheetController;
 
     @Rule
     public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
@@ -90,9 +91,11 @@
         MockitoAnnotations.initMocks(this);
         mActivityTestRule.startMainActivityOnBlankPage();
         mModel = TouchToFillProperties.createDefaultModel(mDismissHandler);
+        mBottomSheetController = mActivityTestRule.getActivity()
+                                         .getRootUiCoordinatorForTesting()
+                                         .getBottomSheetController();
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            mTouchToFillView =
-                    new TouchToFillView(getActivity(), getActivity().getBottomSheetController());
+            mTouchToFillView = new TouchToFillView(getActivity(), mBottomSheetController);
             TouchToFillCoordinator.setUpModelChangeProcessors(mModel, mTouchToFillView);
         });
     }
@@ -340,7 +343,7 @@
     }
 
     private @SheetState int getBottomSheetState() {
-        return getActivity().getBottomSheetController().getSheetState();
+        return mBottomSheetController.getSheetState();
     }
 
     private RecyclerView getCredentials() {
diff --git a/chrome/browser/translate/android/translate_bridge.cc b/chrome/browser/translate/android/translate_bridge.cc
index 7d0f1dcc6..b596c69 100644
--- a/chrome/browser/translate/android/translate_bridge.cc
+++ b/chrome/browser/translate/android/translate_bridge.cc
@@ -158,9 +158,6 @@
 }
 
 // static
-// This logic should be kept in sync with prependToAcceptLanguagesIfNecessary in
-// chrome/android/java/src/org/chromium/chrome/browser/
-//     physicalweb/PwsClientImpl.java
 // Input |locales| is a comma separated locale representation that consists of
 // language tags (BCP47 compliant format). Each language tag contains a language
 // code and a country code or a language code only.
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 55664ea..b51e246 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -12,6 +12,7 @@
 import("//chrome/browser/buildflags.gni")
 import("//chrome/common/features.gni")
 import("//chromeos/assistant/assistant.gni")
+import("//chromeos/dbus/use_real_dbus_clients.gni")
 import("//components/feature_engagement/features.gni")
 import("//components/feed/features.gni")
 import("//components/nacl/features.gni")
@@ -1462,6 +1463,9 @@
       "//chrome/app/vector_icons",
       "//chrome/browser:theme_properties",
       "//chrome/browser/media/router",
+      "//chrome/browser/nearby_sharing/logging",
+      "//chrome/browser/nearby_sharing/logging:util",
+      "//chrome/browser/nearby_sharing/proto",
       "//chrome/browser/profile_resetter:profile_reset_report_proto",
       "//chrome/browser/resource_coordinator:tab_metrics_event_proto",
       "//chrome/browser/resource_coordinator/tab_ranker",
@@ -1847,10 +1851,6 @@
       "ash/launcher/arc_app_window_delegate.h",
       "ash/launcher/arc_app_window_info.cc",
       "ash/launcher/arc_app_window_info.h",
-      "ash/launcher/arc_app_window_launcher_controller.cc",
-      "ash/launcher/arc_app_window_launcher_controller.h",
-      "ash/launcher/arc_app_window_launcher_item_controller.cc",
-      "ash/launcher/arc_app_window_launcher_item_controller.h",
       "ash/launcher/arc_playstore_shortcut_launcher_item_controller.cc",
       "ash/launcher/arc_playstore_shortcut_launcher_item_controller.h",
       "ash/launcher/arc_shelf_spinner_item_controller.cc",
@@ -1865,30 +1865,18 @@
       "ash/launcher/chrome_launcher_controller_util.h",
       "ash/launcher/crostini_app_display.cc",
       "ash/launcher/crostini_app_display.h",
-      "ash/launcher/crostini_app_window_shelf_controller.cc",
-      "ash/launcher/crostini_app_window_shelf_controller.h",
       "ash/launcher/discover_window_observer.cc",
       "ash/launcher/discover_window_observer.h",
-      "ash/launcher/extension_app_window_launcher_controller.cc",
-      "ash/launcher/extension_app_window_launcher_controller.h",
-      "ash/launcher/extension_app_window_launcher_item_controller.cc",
-      "ash/launcher/extension_app_window_launcher_item_controller.h",
       "ash/launcher/extension_shelf_context_menu.cc",
       "ash/launcher/extension_shelf_context_menu.h",
       "ash/launcher/extension_uninstaller.cc",
       "ash/launcher/extension_uninstaller.h",
-      "ash/launcher/internal_app_shelf_context_menu.cc",
-      "ash/launcher/internal_app_shelf_context_menu.h",
-      "ash/launcher/internal_app_window_shelf_controller.cc",
-      "ash/launcher/internal_app_window_shelf_controller.h",
       "ash/launcher/launcher_app_updater.cc",
       "ash/launcher/launcher_app_updater.h",
       "ash/launcher/launcher_controller_helper.cc",
       "ash/launcher/launcher_controller_helper.h",
       "ash/launcher/launcher_extension_app_updater.cc",
       "ash/launcher/launcher_extension_app_updater.h",
-      "ash/launcher/multi_profile_app_window_launcher_controller.cc",
-      "ash/launcher/multi_profile_app_window_launcher_controller.h",
       "ash/launcher/multi_profile_browser_status_monitor.cc",
       "ash/launcher/multi_profile_browser_status_monitor.h",
       "ash/launcher/settings_window_observer.cc",
@@ -2537,12 +2525,14 @@
     allow_circular_includes_from += [ "//chrome/browser/chromeos" ]
 
     if (!is_official_build) {
-      sources += [
-        "webui/chromeos/emulator/device_emulator_message_handler.cc",
-        "webui/chromeos/emulator/device_emulator_message_handler.h",
-        "webui/chromeos/emulator/device_emulator_ui.cc",
-        "webui/chromeos/emulator/device_emulator_ui.h",
-      ]
+      if (!use_real_dbus_clients) {
+        sources += [
+          "webui/chromeos/emulator/device_emulator_message_handler.cc",
+          "webui/chromeos/emulator/device_emulator_message_handler.h",
+          "webui/chromeos/emulator/device_emulator_ui.cc",
+          "webui/chromeos/emulator/device_emulator_ui.h",
+        ]
+      }
       deps += [
         "//chromeos/components/sample_system_web_app_ui",
         "//chromeos/components/telemetry_extension_ui",
diff --git a/chrome/browser/ui/android/default_browser_promo/java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoManager.java b/chrome/browser/ui/android/default_browser_promo/java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoManager.java
index 92a5f37a..a16f265 100644
--- a/chrome/browser/ui/android/default_browser_promo/java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoManager.java
+++ b/chrome/browser/ui/android/default_browser_promo/java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoManager.java
@@ -9,6 +9,7 @@
 import android.app.role.RoleManager;
 import android.content.Context;
 import android.content.Intent;
+import android.net.Uri;
 import android.os.Build;
 import android.provider.Settings;
 
@@ -32,6 +33,7 @@
 public class DefaultBrowserPromoManager implements PauseResumeWithNativeObserver, Destroyable {
     private static final String SKIP_PRIMER_PARAM = "skip_primer";
     private static final String DISABLE_DISAMBIGUATION_SHEET = "disable_disambiguation_sheet";
+    private static final String DISAMBIGUATION_PROMO_URL = "disambiguation_promo_url";
 
     private final Activity mActivity;
     private DefaultBrowserPromoDialog mDialog;
@@ -145,8 +147,17 @@
             DefaultBrowserPromoMetrics.recordUiDismissalReason(
                     mCurrentState, UIDismissalReason.CHANGE_DEFAULT);
 
-            Intent intent = new Intent(Intent.ACTION_MAIN);
-            intent.addCategory(Intent.CATEGORY_APP_BROWSER);
+            Intent intent = new Intent();
+            String url = ChromeFeatureList.getFieldTrialParamByFeature(
+                    ChromeFeatureList.ANDROID_DEFAULT_BROWSER_PROMO, DISAMBIGUATION_PROMO_URL);
+            if (url != null && !url.isEmpty()) {
+                intent.setAction(Intent.ACTION_VIEW);
+                intent.addCategory(Intent.CATEGORY_BROWSABLE);
+                intent.setData(Uri.parse(url));
+            } else {
+                intent.setAction(Intent.ACTION_MAIN);
+                intent.addCategory(Intent.CATEGORY_APP_BROWSER);
+            }
             intent.putExtra(DefaultBrowserPromoUtils.DISAMBIGUATION_SHEET_PROMOED_KEY, true);
             mActivity.startActivity(intent);
         });
diff --git a/chrome/browser/ui/android/default_browser_promo/java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoUtils.java b/chrome/browser/ui/android/default_browser_promo/java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoUtils.java
index 505cfef..4a14e5d 100644
--- a/chrome/browser/ui/android/default_browser_promo/java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoUtils.java
+++ b/chrome/browser/ui/android/default_browser_promo/java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoUtils.java
@@ -7,6 +7,7 @@
 import android.app.Activity;
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
+import android.net.Uri;
 import android.text.TextUtils;
 
 import androidx.annotation.IntDef;
@@ -192,6 +193,17 @@
         }
     }
 
+    /**
+     * Remove intent data if this intent is triggered by default browser promo; Otherwise,
+     * chrome will open a new tab.
+     */
+    public static void maybeRemoveIntentData(Intent intent) {
+        if (intent.getBooleanExtra(DISAMBIGUATION_SHEET_PROMOED_KEY, false)) {
+            // Intent with Uri.EMPTY as data will be ignored by the IntentHandler.
+            intent.setData(Uri.EMPTY);
+        }
+    }
+
     @VisibleForTesting
     static boolean isChromePreStableInstalled() {
         for (ResolveInfo info : PackageManagerUtils.queryAllWebBrowsersInfo()) {
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 9da0228..d77fd07 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -874,11 +874,11 @@
       </message>
 
       <!-- Safety check -->
-      <message name="IDS_PREFS_SAFETY_CHECK" desc="Title of the Safety check element in settings, allowing the user to check multiple areas of browser safety. [CHAR-LIMIT=32]">
-        Safety check
+      <message name="IDS_PREFS_SAFETY_CHECK" desc="Title of the Safety check element in settings, allowing the user to check multiple areas of browser safety. The NEW label is displayed when the number of times the user ran the check is less than 3. [CHAR-LIMIT=32]">
+        Safety check <ph name="BEGIN_NEW">&lt;new&gt;</ph>New<ph name="END_NEW">&lt;/new&gt;</ph>
       </message>
       <message name="IDS_SAFETY_CHECK_DESCRIPTION" desc="Introduces the safety check categories to the user. Followed by several items, such as 'Safe Browsing', 'Updates', 'Passwords'.">
-        Chrome can check if your settings are protecting you from:
+        Chrome can help keep you safe from data breaches, unsafe websites, and more
       </message>
       <message name="IDS_SAFETY_CHECK_SAFE_BROWSING_TITLE" desc="Title for the Safe Browsing section.">
         Safe Browsing
@@ -892,12 +892,6 @@
       <message name="IDS_SAFETY_CHECK_BUTTON" desc="Text for the button to start Safety check.">
         Check now
       </message>
-      <message name="IDS_SAFETY_CHECK_UNCHECKED" desc="A given element has not been checked.">
-        Unchecked
-      </message>
-      <message name="IDS_SAFETY_CHECK_CHECKING" desc="A given element is being checked.">
-        Checking
-      </message>
       <message name="IDS_SAFETY_CHECK_ERROR" desc="A generic error state.">
         An error occurred.
       </message>
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PREFS_SAFETY_CHECK.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PREFS_SAFETY_CHECK.png.sha1
index 23101175..b58b1524 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PREFS_SAFETY_CHECK.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PREFS_SAFETY_CHECK.png.sha1
@@ -1 +1 @@
-09a8d1f29f8009206faafa00767edeb0f9947a37
\ No newline at end of file
+08dabb1b81f39507632b720854be695317cfcb73
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SAFETY_CHECK_DESCRIPTION.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SAFETY_CHECK_DESCRIPTION.png.sha1
index 23101175..9d93be6 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SAFETY_CHECK_DESCRIPTION.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SAFETY_CHECK_DESCRIPTION.png.sha1
@@ -1 +1 @@
-09a8d1f29f8009206faafa00767edeb0f9947a37
\ No newline at end of file
+889584fe00a7795436189b5ac998d9219addeeba
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SAFETY_CHECK_UNCHECKED.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SAFETY_CHECK_UNCHECKED.png.sha1
deleted file mode 100644
index 23101175..0000000
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SAFETY_CHECK_UNCHECKED.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-09a8d1f29f8009206faafa00767edeb0f9947a37
\ No newline at end of file
diff --git a/chrome/browser/ui/app_list/arc/arc_app_unittest.cc b/chrome/browser/ui/app_list/arc/arc_app_unittest.cc
index 354eb7b6..ebc72841 100644
--- a/chrome/browser/ui/app_list/arc/arc_app_unittest.cc
+++ b/chrome/browser/ui/app_list/arc/arc_app_unittest.cc
@@ -60,7 +60,7 @@
 #include "chrome/browser/ui/app_list/chrome_app_list_item.h"
 #include "chrome/browser/ui/app_list/test/fake_app_list_model_updater.h"
 #include "chrome/browser/ui/app_list/test/test_app_list_controller_delegate.h"
-#include "chrome/browser/ui/ash/launcher/arc_app_window_launcher_controller.h"
+#include "chrome/browser/ui/ash/launcher/arc_app_shelf_id.h"
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
 #include "chrome/browser/web_applications/test/test_web_app_provider.h"
 #include "chrome/browser/web_applications/test/web_app_test.h"
diff --git a/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_browsertest.cc b/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_browsertest.cc
index 09ca9695..38edfe79 100644
--- a/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_browsertest.cc
+++ b/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_browsertest.cc
@@ -121,10 +121,6 @@
   ~AppServiceAppWindowBrowserTest() override {}
 
   void SetUp() override {
-    if (!base::FeatureList::IsEnabled(features::kAppServiceInstanceRegistry)) {
-      GTEST_SKIP() << "skipping all tests because kAppServiceInstanceRegistry "
-                      "is not enabled";
-    }
     extensions::PlatformAppBrowserTest::SetUp();
   }
 
diff --git a/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_launcher_controller.cc b/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_launcher_controller.cc
index 4051d54..5552c6f 100644
--- a/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_launcher_controller.cc
+++ b/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_launcher_controller.cc
@@ -72,9 +72,10 @@
   if (arc::IsArcAllowedForProfile(owner->profile()))
     arc_tracker_ = std::make_unique<AppServiceAppWindowArcTracker>(this);
 
-  if (crostini::CrostiniFeatures::Get()->IsUIAllowed(owner->profile()))
+  if (crostini::CrostiniFeatures::Get()->IsUIAllowed(owner->profile())) {
     crostini_tracker_ =
         std::make_unique<AppServiceAppWindowCrostiniTracker>(this);
+  }
 
   profile_list_.push_back(owner->profile());
 
diff --git a/chrome/browser/ui/ash/launcher/app_service/app_service_instance_registry_helper.cc b/chrome/browser/ui/ash/launcher/app_service/app_service_instance_registry_helper.cc
index 2e75d20..616c672d 100644
--- a/chrome/browser/ui/ash/launcher/app_service/app_service_instance_registry_helper.cc
+++ b/chrome/browser/ui/ash/launcher/app_service/app_service_instance_registry_helper.cc
@@ -44,9 +44,6 @@
 AppServiceInstanceRegistryHelper::~AppServiceInstanceRegistryHelper() = default;
 
 void AppServiceInstanceRegistryHelper::ActiveUserChanged() {
-  if (!base::FeatureList::IsEnabled(features::kAppServiceInstanceRegistry))
-    return;
-
   proxy_ = apps::AppServiceProxyFactory::GetForProfile(
       ProfileManager::GetActiveUserProfile());
 }
@@ -59,9 +56,6 @@
 void AppServiceInstanceRegistryHelper::OnActiveTabChanged(
     content::WebContents* old_contents,
     content::WebContents* new_contents) {
-  if (!base::FeatureList::IsEnabled(features::kAppServiceInstanceRegistry))
-    return;
-
   if (old_contents) {
     auto* window = old_contents->GetNativeView();
 
@@ -116,18 +110,12 @@
 void AppServiceInstanceRegistryHelper::OnTabReplaced(
     content::WebContents* old_contents,
     content::WebContents* new_contents) {
-  if (!base::FeatureList::IsEnabled(features::kAppServiceInstanceRegistry))
-    return;
-
   OnTabClosing(old_contents);
   OnTabInserted(new_contents);
 }
 
 void AppServiceInstanceRegistryHelper::OnTabInserted(
     content::WebContents* contents) {
-  if (!base::FeatureList::IsEnabled(features::kAppServiceInstanceRegistry))
-    return;
-
   std::string app_id = GetAppId(contents);
   aura::Window* window = GetWindow(contents);
 
@@ -152,9 +140,6 @@
 
 void AppServiceInstanceRegistryHelper::OnTabClosing(
     content::WebContents* contents) {
-  if (!base::FeatureList::IsEnabled(features::kAppServiceInstanceRegistry))
-    return;
-
   aura::Window* window = GetWindow(contents);
 
   // When the tab is closed, if the window does not exists in the AppService
@@ -224,9 +209,6 @@
 
 void AppServiceInstanceRegistryHelper::OnSetShelfIDForBrowserWindowContents(
     content::WebContents* contents) {
-  if (!base::FeatureList::IsEnabled(features::kAppServiceInstanceRegistry))
-    return;
-
   aura::Window* window = GetWindow(contents);
   if (!window || !window->GetToplevelWindow())
     return;
diff --git a/chrome/browser/ui/ash/launcher/app_service/app_service_shelf_context_menu.cc b/chrome/browser/ui/ash/launcher/app_service/app_service_shelf_context_menu.cc
index dc83648..4325349 100644
--- a/chrome/browser/ui/ash/launcher/app_service/app_service_shelf_context_menu.cc
+++ b/chrome/browser/ui/ash/launcher/app_service/app_service_shelf_context_menu.cc
@@ -32,6 +32,7 @@
 #include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
 #include "chrome/browser/ui/app_list/extension_app_utils.h"
+#include "chrome/browser/ui/ash/launcher/arc_app_shelf_id.h"
 #include "chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.h"
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
 #include "chrome/browser/ui/browser_commands.h"
diff --git a/chrome/browser/ui/ash/launcher/arc_app_launcher_browsertest.cc b/chrome/browser/ui/ash/launcher/arc_app_launcher_browsertest.cc
index 23250b2..5a10b01 100644
--- a/chrome/browser/ui/ash/launcher/arc_app_launcher_browsertest.cc
+++ b/chrome/browser/ui/ash/launcher/arc_app_launcher_browsertest.cc
@@ -29,7 +29,7 @@
 #include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
-#include "chrome/browser/ui/ash/launcher/arc_app_window_launcher_controller.h"
+#include "chrome/browser/ui/ash/launcher/arc_app_shelf_id.h"
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_test_util.h"
 #include "chrome/browser/ui/ash/launcher/shelf_spinner_controller.h"
diff --git a/chrome/browser/ui/ash/launcher/arc_app_window.cc b/chrome/browser/ui/ash/launcher/arc_app_window.cc
index 000bdcb..0a1d661 100644
--- a/chrome/browser/ui/ash/launcher/arc_app_window.cc
+++ b/chrome/browser/ui/ash/launcher/arc_app_window.cc
@@ -11,8 +11,7 @@
 #include "chrome/browser/ui/app_list/app_service/app_service_app_icon_loader.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_icon.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
-#include "chrome/browser/ui/ash/launcher/arc_app_window_launcher_controller.h"
-#include "chrome/browser/ui/ash/launcher/arc_app_window_launcher_item_controller.h"
+#include "chrome/browser/ui/ash/launcher/arc_app_window_delegate.h"
 #include "chrome/common/chrome_features.h"
 #include "components/arc/arc_util.h"
 #include "components/exo/shell_surface_base.h"
@@ -44,8 +43,7 @@
   DCHECK(owner_);
 
   // AppService uses app_shelf_id as the app_id to construct ShelfID.
-  if (base::FeatureList::IsEnabled(features::kAppServiceInstanceRegistry))
-    set_shelf_id(ash::ShelfID(app_shelf_id.ToString()));
+  set_shelf_id(ash::ShelfID(app_shelf_id.ToString()));
 
   SetDefaultAppIcon();
 }
diff --git a/chrome/browser/ui/ash/launcher/arc_app_window_launcher_controller.cc b/chrome/browser/ui/ash/launcher/arc_app_window_launcher_controller.cc
deleted file mode 100644
index 41a955ff..0000000
--- a/chrome/browser/ui/ash/launcher/arc_app_window_launcher_controller.cc
+++ /dev/null
@@ -1,581 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/ash/launcher/arc_app_window_launcher_controller.h"
-
-#include <string>
-#include <utility>
-
-#include "ash/public/cpp/app_list/internal_app_id_constants.h"
-#include "ash/public/cpp/multi_user_window_manager.h"
-#include "ash/public/cpp/shelf_model.h"
-#include "ash/public/cpp/window_properties.h"
-#include "base/bind.h"
-#include "base/task/post_task.h"
-#include "base/task/thread_pool.h"
-#include "chrome/browser/apps/app_service/app_icon_factory.h"
-#include "chrome/browser/chromeos/arc/arc_optin_uma.h"
-#include "chrome/browser/chromeos/arc/arc_util.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
-#include "chrome/browser/ui/ash/launcher/app_window_launcher_item_controller.h"
-#include "chrome/browser/ui/ash/launcher/arc_app_window.h"
-#include "chrome/browser/ui/ash/launcher/arc_app_window_launcher_item_controller.h"
-#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
-#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_util.h"
-#include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_helper.h"
-#include "chrome/common/chrome_features.h"
-#include "components/account_id/account_id.h"
-#include "components/arc/arc_util.h"
-#include "components/arc/session/arc_bridge_service.h"
-#include "components/user_manager/user_manager.h"
-#include "ui/aura/env.h"
-#include "ui/base/base_window.h"
-#include "ui/base/ui_base_features.h"
-#include "ui/gfx/image/image_skia.h"
-#include "ui/views/widget/widget.h"
-
-namespace {
-constexpr int kArcAppWindowIconSize = extension_misc::EXTENSION_ICON_MEDIUM;
-}  // namespace
-
-// The information about the arc application window which has to be kept
-// even when its AppWindow is not present.
-class ArcAppWindowLauncherController::AppWindowInfo {
- public:
-  explicit AppWindowInfo(const arc::ArcAppShelfId& app_shelf_id,
-                         const std::string& launch_intent,
-                         const std::string& package_name)
-      : app_shelf_id_(app_shelf_id),
-        launch_intent_(launch_intent),
-        package_name_(package_name) {}
-  ~AppWindowInfo() = default;
-
-  void SetDescription(const std::string& title, const gfx::ImageSkia& icon) {
-    if (base::IsStringUTF8(title))
-      title_ = title;
-    else
-      VLOG(1) << "Task label is not UTF-8 string.";
-    // Chrome has custom Play Store icon. Don't overwrite it.
-    if (app_shelf_id_.app_id() != arc::kPlayStoreAppId)
-      icon_ = icon;
-  }
-
-  void set_app_window(std::unique_ptr<ArcAppWindow> window) {
-    app_window_ = std::move(window);
-  }
-
-  const arc::ArcAppShelfId& app_shelf_id() const { return app_shelf_id_; }
-
-  ArcAppWindow* app_window() { return app_window_.get(); }
-
-  const std::string& launch_intent() { return launch_intent_; }
-
-  const std::string& package_name() { return package_name_; }
-
-  const std::string& title() const { return title_; }
-
-  const gfx::ImageSkia& icon() const { return icon_; }
-
- private:
-  const arc::ArcAppShelfId app_shelf_id_;
-  const std::string launch_intent_;
-  const std::string package_name_;
-  // Keeps overridden window title.
-  std::string title_;
-  // Keeps overridden window icon.
-  gfx::ImageSkia icon_;
-  std::unique_ptr<ArcAppWindow> app_window_;
-
-  DISALLOW_COPY_AND_ASSIGN(AppWindowInfo);
-};
-
-ArcAppWindowLauncherController::ArcAppWindowLauncherController(
-    ChromeLauncherController* owner)
-    : AppWindowLauncherController(owner) {
-  if (arc::IsArcAllowedForProfile(owner->profile())) {
-    observed_profile_ = owner->profile();
-    StartObserving(observed_profile_);
-
-    arc::ArcSessionManager::Get()->AddObserver(this);
-  }
-}
-
-ArcAppWindowLauncherController::~ArcAppWindowLauncherController() {
-  if (observed_profile_)
-    StopObserving(observed_profile_);
-  if (arc::ArcSessionManager::Get())
-    arc::ArcSessionManager::Get()->RemoveObserver(this);
-}
-
-void ArcAppWindowLauncherController::ActiveUserChanged(
-    const std::string& user_email) {
-  const std::string& primary_user_email = user_manager::UserManager::Get()
-                                              ->GetPrimaryUser()
-                                              ->GetAccountId()
-                                              .GetUserEmail();
-  if (user_email == primary_user_email) {
-    // Restore existing ARC window and create controllers for them.
-    AttachControllerToWindowsIfNeeded();
-
-    // Make sure that we created items for all apps, not only which have a
-    // window.
-    for (const auto& info : task_id_to_app_window_info_)
-      AttachControllerToTask(info.first, *info.second);
-
-    // Update active status.
-    OnTaskSetActive(active_task_id_);
-  } else {
-    // Remove all ARC apps and destroy its controllers. There is no mapping
-    // task id to app window because it is not safe when controller is missing.
-    for (auto& it : task_id_to_app_window_info_)
-      UnregisterApp(it.second.get());
-
-    // Some controllers might have no windows attached, for example background
-    // task when foreground tasks is in full screen.
-    for (const auto& it : app_shelf_group_to_controller_map_)
-      owner()->CloseLauncherItem(it.second->shelf_id());
-    app_shelf_group_to_controller_map_.clear();
-  }
-}
-
-void ArcAppWindowLauncherController::AdditionalUserAddedToSession(
-    Profile* profile) {
-  DCHECK(!arc::IsArcAllowedForProfile(profile));
-}
-
-void ArcAppWindowLauncherController::OnWindowInitialized(aura::Window* window) {
-  // An arc window has type WINDOW_TYPE_NORMAL, a WindowDelegate and
-  // is a top level views widget.
-  if (window->type() != aura::client::WINDOW_TYPE_NORMAL || !window->delegate())
-    return;
-  views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window);
-  if (!widget || !widget->is_top_level())
-    return;
-  observed_windows_.push_back(window);
-  window->AddObserver(this);
-}
-
-void ArcAppWindowLauncherController::OnWindowVisibilityChanged(
-    aura::Window* window,
-    bool visible) {
-  const int task_id = arc::GetWindowTaskId(window);
-  if (task_id == arc::kNoTaskId)
-    return;
-
-  // Attach window to multi-user manager now to let it manage visibility state
-  // of the ARC window correctly.
-  if (task_id != arc::kSystemWindowTaskId) {
-    MultiUserWindowManagerHelper::GetWindowManager()->SetWindowOwner(
-        window,
-        user_manager::UserManager::Get()->GetPrimaryUser()->GetAccountId());
-  }
-
-  // The application id property should be set at this time. It is important to
-  // have window->IsVisible set to true before attaching to a controller because
-  // the window is registered in multi-user manager and this manager may
-  // consider this new window as hidden for current profile. Multi-user manager
-  // uses OnWindowVisibilityChanging event to update window state.
-  if (visible && observed_profile_ == owner()->profile())
-    AttachControllerToWindowIfNeeded(window);
-}
-
-void ArcAppWindowLauncherController::OnWindowDestroying(aura::Window* window) {
-  auto it =
-      std::find(observed_windows_.begin(), observed_windows_.end(), window);
-  DCHECK(it != observed_windows_.end());
-  observed_windows_.erase(it);
-  window->RemoveObserver(this);
-
-  auto info_it = std::find_if(
-      task_id_to_app_window_info_.begin(), task_id_to_app_window_info_.end(),
-      [window](TaskIdToAppWindowInfo::value_type& pair) {
-        return pair.second->app_window() &&
-               pair.second->app_window()->GetNativeWindow() == window;
-      });
-  if (info_it != task_id_to_app_window_info_.end()) {
-    // Note, window may be recreated in some cases, so do not close controller
-    // on window destroying. Controller will be closed onTaskDestroyed event
-    // which is generated when actual task is destroyed.
-    UnregisterApp(info_it->second.get());
-  }
-}
-
-ArcAppWindowLauncherController::AppWindowInfo*
-ArcAppWindowLauncherController::GetAppWindowInfoForTask(int task_id) {
-  const auto it = task_id_to_app_window_info_.find(task_id);
-  return it == task_id_to_app_window_info_.end() ? nullptr : it->second.get();
-}
-
-ArcAppWindow* ArcAppWindowLauncherController::GetAppWindowForTask(int task_id) {
-  AppWindowInfo* info = GetAppWindowInfoForTask(task_id);
-  return info ? info->app_window() : nullptr;
-}
-
-void ArcAppWindowLauncherController::AttachControllerToWindowsIfNeeded() {
-  for (auto* window : observed_windows_)
-    AttachControllerToWindowIfNeeded(window);
-}
-
-void ArcAppWindowLauncherController::AttachControllerToWindowIfNeeded(
-    aura::Window* window) {
-  const int task_id = arc::GetWindowTaskId(window);
-  if (task_id == arc::kNoTaskId)
-    return;
-
-  if (task_id == arc::kSystemWindowTaskId)
-    return;
-
-  // Check if we have controller for this task.
-  if (GetAppWindowForTask(task_id))
-    return;
-
-  // TODO(msw): Set shelf item types earlier to avoid ShelfWindowWatcher races.
-  window->SetProperty<int>(ash::kShelfItemTypeKey, ash::TYPE_APP);
-
-  // Create controller if we have task info.
-  AppWindowInfo* info = GetAppWindowInfoForTask(task_id);
-  if (!info) {
-    VLOG(1) << "Could not find AppWindowInfo for task:" << task_id;
-    return;
-  }
-
-  views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window);
-  DCHECK(widget);
-  DCHECK(!info->app_window());
-  info->set_app_window(std::make_unique<ArcAppWindow>(
-      task_id, info->app_shelf_id(), widget, this, observed_profile_));
-  info->app_window()->SetDescription(info->title(), info->icon());
-  RegisterApp(info);
-  DCHECK(info->app_window()->controller());
-  const ash::ShelfID shelf_id(info->app_window()->shelf_id());
-  window->SetProperty(ash::kShelfIDKey, shelf_id.Serialize());
-  window->SetProperty(ash::kArcPackageNameKey,
-                      new std::string(info->package_name()));
-  window->SetProperty(ash::kAppIDKey, new std::string(shelf_id.app_id));
-}
-
-void ArcAppWindowLauncherController::OnAppStatesChanged(
-    const std::string& app_id,
-    const ArcAppListPrefs::AppInfo& app_info) {
-  if (!app_info.ready)
-    OnAppRemoved(app_id);
-}
-
-std::vector<int> ArcAppWindowLauncherController::GetTaskIdsForApp(
-    const std::string& arc_app_id) const {
-  // Note, ArcAppWindow is optional part for a task and it may be not created if
-  // another full screen Android app is currently active. Use
-  // |task_id_to_app_window_info_| that keeps currently running tasks info.
-  std::vector<int> task_ids;
-  for (const auto& it : task_id_to_app_window_info_) {
-    const AppWindowInfo* app_window_info = it.second.get();
-    if (app_window_info->app_shelf_id().app_id() == arc_app_id)
-      task_ids.push_back(it.first);
-  }
-
-  return task_ids;
-}
-
-void ArcAppWindowLauncherController::OnAppRemoved(
-    const std::string& arc_app_id) {
-  const std::vector<int> task_ids_to_remove = GetTaskIdsForApp(arc_app_id);
-  for (const auto task_id : task_ids_to_remove)
-    OnTaskDestroyed(task_id);
-  DCHECK(GetTaskIdsForApp(arc_app_id).empty());
-}
-
-void ArcAppWindowLauncherController::OnTaskCreated(
-    int task_id,
-    const std::string& package_name,
-    const std::string& activity_name,
-    const std::string& intent) {
-  DCHECK(!GetAppWindowForTask(task_id));
-  const std::string arc_app_id =
-      ArcAppListPrefs::GetAppId(package_name, activity_name);
-  const arc::ArcAppShelfId arc_app_shelf_id =
-      arc::ArcAppShelfId::FromIntentAndAppId(intent, arc_app_id);
-  task_id_to_app_window_info_[task_id] =
-      std::make_unique<AppWindowInfo>(arc_app_shelf_id, intent, package_name);
-  // Don't create shelf icon for non-primary user.
-  if (observed_profile_ != owner()->profile())
-    return;
-
-  AttachControllerToWindowsIfNeeded();
-
-  // Some tasks can be started in background and might have no window until
-  // pushed to the front. We need its representation on the shelf to give a user
-  // control over it.
-  AttachControllerToTask(task_id, *task_id_to_app_window_info_[task_id]);
-}
-
-void ArcAppWindowLauncherController::OnTaskDescriptionChanged(
-    int32_t task_id,
-    const std::string& label,
-    const arc::mojom::RawIconPngData& icon) {
-  AppWindowInfo* info = GetAppWindowInfoForTask(task_id);
-  if (!info)
-    return;
-
-  if (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon)) {
-    apps::ArcRawIconPngDataToImageSkia(
-        icon.Clone(), kArcAppWindowIconSize,
-        base::BindOnce(&ArcAppWindowLauncherController::OnIconLoaded,
-                       weak_ptr_factory_.GetWeakPtr(), task_id, label));
-  } else {
-    if (icon.icon_png_data && icon.icon_png_data.has_value()) {
-      apps::CompressedDataToImageSkiaCallback(
-          base::BindOnce(&ArcAppWindowLauncherController::SetDescription,
-                         weak_ptr_factory_.GetWeakPtr(), task_id, label))
-          .Run(icon.icon_png_data.value());
-    }
-  }
-}
-
-void ArcAppWindowLauncherController::OnTaskDestroyed(int task_id) {
-  auto it = task_id_to_app_window_info_.find(task_id);
-  if (it == task_id_to_app_window_info_.end())
-    return;
-
-  UnregisterApp(it->second.get());
-
-  // Check if we may close controller now, at this point we can safely remove
-  // controllers without window.
-  const auto& app_shelf_id = it->second->app_shelf_id();
-  auto it_controller = app_shelf_group_to_controller_map_.find(app_shelf_id);
-  if (it_controller != app_shelf_group_to_controller_map_.end()) {
-    ArcAppWindowLauncherItemController* const controller =
-        it_controller->second;
-    controller->RemoveTaskId(task_id);
-    if (!controller->HasAnyTasks()) {
-      app_shelf_group_to_controller_map_.erase(it_controller);
-      owner()->CloseLauncherItem(controller->shelf_id());
-    }
-  }
-
-  task_id_to_app_window_info_.erase(it);
-}
-
-void ArcAppWindowLauncherController::OnTaskSetActive(int32_t task_id) {
-  if (observed_profile_ != owner()->profile()) {
-    active_task_id_ = task_id;
-    return;
-  }
-
-  ArcAppWindow* previous_app_window = GetAppWindowForTask(active_task_id_);
-  if (previous_app_window) {
-    owner()->SetItemStatus(previous_app_window->shelf_id(),
-                           ash::STATUS_RUNNING);
-    previous_app_window->SetFullscreenMode(
-        previous_app_window->widget() &&
-                previous_app_window->widget()->IsFullscreen()
-            ? ArcAppWindow::FullScreenMode::kActive
-            : ArcAppWindow::FullScreenMode::kNonActive);
-  }
-
-  active_task_id_ = task_id;
-
-  ArcAppWindow* current_app_window = GetAppWindowForTask(task_id);
-  if (current_app_window) {
-    if (current_app_window->widget() && current_app_window->IsActive()) {
-      current_app_window->controller()->SetActiveWindow(
-          current_app_window->GetNativeWindow());
-    }
-    owner()->SetItemStatus(current_app_window->shelf_id(), ash::STATUS_RUNNING);
-    // TODO(reveman): Figure out how to support fullscreen in interleaved
-    // window mode.
-    // if (new_active_app_it->second->widget()) {
-    //   new_active_app_it->second->widget()->SetFullscreen(
-    //       new_active_app_it->second->fullscreen_mode() ==
-    //       ArcAppWindow::FullScreenMode::kActive);
-    // }
-  }
-}
-
-int ArcAppWindowLauncherController::GetActiveTaskId() const {
-  return active_task_id_;
-}
-
-AppWindowLauncherItemController*
-ArcAppWindowLauncherController::ControllerForWindow(aura::Window* window) {
-  if (!window)
-    return nullptr;
-
-  ArcAppWindow* app_window = GetAppWindowForTask(active_task_id_);
-  if (app_window &&
-      app_window->widget() == views::Widget::GetWidgetForNativeWindow(window)) {
-    return app_window->controller();
-  }
-
-  for (auto& it : task_id_to_app_window_info_) {
-    ArcAppWindow* app_window = it.second->app_window();
-    if (app_window && app_window->widget() ==
-                          views::Widget::GetWidgetForNativeWindow(window)) {
-      return it.second->app_window()->controller();
-    }
-  }
-
-  return nullptr;
-}
-
-void ArcAppWindowLauncherController::OnItemDelegateDiscarded(
-    ash::ShelfItemDelegate* delegate) {
-  for (auto& it : task_id_to_app_window_info_) {
-    ArcAppWindow* app_window = it.second->app_window();
-    if (!app_window || app_window->controller() != delegate)
-      continue;
-
-    VLOG(1) << "Item controller was released externally for the app "
-            << delegate->shelf_id().app_id << ".";
-
-    auto it_controller =
-        app_shelf_group_to_controller_map_.find(app_window->app_shelf_id());
-    if (it_controller != app_shelf_group_to_controller_map_.end())
-      app_shelf_group_to_controller_map_.erase(it_controller);
-
-    UnregisterApp(it.second.get());
-  }
-}
-
-void ArcAppWindowLauncherController::OnArcOptInManagementCheckStarted() {
-  // In case of retry this time is updated and we measure only successful run.
-  opt_in_management_check_start_time_ = base::Time::Now();
-}
-
-void ArcAppWindowLauncherController::OnArcSessionStopped(
-    arc::ArcStopReason stop_reason) {
-  opt_in_management_check_start_time_ = base::Time();
-}
-
-void ArcAppWindowLauncherController::OnWindowActivated(
-    wm::ActivationChangeObserver::ActivationReason reason,
-    aura::Window* gained_active,
-    aura::Window* lost_active) {
-  AppWindowLauncherController::OnWindowActivated(reason, gained_active,
-                                                 lost_active);
-  OnTaskSetActive(active_task_id_);
-}
-
-void ArcAppWindowLauncherController::StartObserving(Profile* profile) {
-  aura::Env::GetInstance()->AddObserver(this);
-  ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile);
-  DCHECK(prefs);
-  prefs->AddObserver(this);
-}
-
-void ArcAppWindowLauncherController::StopObserving(Profile* profile) {
-  for (auto* window : observed_windows_)
-    window->RemoveObserver(this);
-  ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile);
-  prefs->RemoveObserver(this);
-  aura::Env::GetInstance()->RemoveObserver(this);
-}
-
-ArcAppWindowLauncherItemController*
-ArcAppWindowLauncherController::AttachControllerToTask(
-    int task_id,
-    const AppWindowInfo& app_window_info) {
-  const arc::ArcAppShelfId& app_shelf_id = app_window_info.app_shelf_id();
-  const auto it = app_shelf_group_to_controller_map_.find(app_shelf_id);
-  if (it != app_shelf_group_to_controller_map_.end()) {
-    DCHECK(it->second->app_id() == app_shelf_id.ToString());
-    it->second->AddTaskId(task_id);
-    return it->second;
-  }
-
-  const ash::ShelfID shelf_id = ash::ShelfID(app_shelf_id.ToString());
-  std::unique_ptr<ArcAppWindowLauncherItemController> controller =
-      std::make_unique<ArcAppWindowLauncherItemController>(shelf_id);
-  ArcAppWindowLauncherItemController* item_controller = controller.get();
-  if (!owner()->GetItem(shelf_id)) {
-    owner()->CreateAppLauncherItem(std::move(controller), ash::STATUS_RUNNING);
-  } else {
-    owner()->shelf_model()->SetShelfItemDelegate(shelf_id,
-                                                 std::move(controller));
-    owner()->SetItemStatus(shelf_id, ash::STATUS_RUNNING);
-  }
-  item_controller->AddTaskId(task_id);
-  app_shelf_group_to_controller_map_[app_shelf_id] = item_controller;
-  return item_controller;
-}
-
-void ArcAppWindowLauncherController::RegisterApp(
-    AppWindowInfo* app_window_info) {
-  ArcAppWindow* app_window = app_window_info->app_window();
-  ArcAppWindowLauncherItemController* controller =
-      AttachControllerToTask(app_window->task_id(), *app_window_info);
-  DCHECK(!controller->app_id().empty());
-  const ash::ShelfID shelf_id(controller->app_id());
-  DCHECK(owner()->GetItem(shelf_id));
-
-  controller->AddWindow(app_window);
-  owner()->SetItemStatus(shelf_id, ash::STATUS_RUNNING);
-  app_window->SetController(controller);
-  app_window->set_shelf_id(shelf_id);
-
-  if (app_window_info->app_shelf_id().app_id() == arc::kPlayStoreAppId)
-    HandlePlayStoreLaunch(app_window_info);
-}
-
-void ArcAppWindowLauncherController::UnregisterApp(
-    AppWindowInfo* app_window_info) {
-  ArcAppWindow* app_window = app_window_info->app_window();
-  if (!app_window)
-    return;
-
-  AppWindowLauncherItemController* controller = app_window->controller();
-  if (controller)
-    controller->RemoveWindow(app_window);
-  app_window->SetController(nullptr);
-  app_window_info->set_app_window(nullptr);
-}
-
-void ArcAppWindowLauncherController::HandlePlayStoreLaunch(
-    AppWindowInfo* app_window_info) {
-  arc::Intent intent;
-  if (!arc::ParseIntent(app_window_info->launch_intent(), &intent))
-    return;
-
-  if (!opt_in_management_check_start_time_.is_null()) {
-    if (intent.HasExtraParam(arc::kInitialStartParam)) {
-      DCHECK(!arc::IsRobotOrOfflineDemoAccountMode());
-      arc::UpdatePlayStoreShownTimeDeprecated(
-          base::Time::Now() - opt_in_management_check_start_time_,
-          owner()->profile());
-      VLOG(1) << "Play Store is initially shown.";
-    }
-    opt_in_management_check_start_time_ = base::Time();
-    return;
-  }
-
-  for (const auto& param : intent.extra_params()) {
-    int64_t start_request_ms;
-    if (sscanf(param.c_str(), arc::kRequestStartTimeParamTemplate,
-               &start_request_ms) != 1)
-      continue;
-    const base::TimeDelta launch_time =
-        base::TimeTicks::Now() - base::TimeTicks() -
-        base::TimeDelta::FromMilliseconds(start_request_ms);
-    DCHECK_GE(launch_time, base::TimeDelta());
-    arc::UpdatePlayStoreLaunchTime(launch_time);
-  }
-}
-
-void ArcAppWindowLauncherController::SetDescription(int32_t task_id,
-                                                    const std::string& title,
-                                                    gfx::ImageSkia icon) {
-  AppWindowInfo* info = GetAppWindowInfoForTask(task_id);
-  if (info) {
-    info->SetDescription(title, icon);
-    if (info->app_window())
-      info->app_window()->SetDescription(title, icon);
-  }
-}
-
-void ArcAppWindowLauncherController::OnIconLoaded(int32_t task_id,
-                                                  const std::string& title,
-                                                  const gfx::ImageSkia& icon) {
-  gfx::ImageSkia image = icon;
-  SetDescription(task_id, title, image);
-}
diff --git a/chrome/browser/ui/ash/launcher/arc_app_window_launcher_controller.h b/chrome/browser/ui/ash/launcher/arc_app_window_launcher_controller.h
deleted file mode 100644
index 98a98a1a..0000000
--- a/chrome/browser/ui/ash/launcher/arc_app_window_launcher_controller.h
+++ /dev/null
@@ -1,148 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_ASH_LAUNCHER_ARC_APP_WINDOW_LAUNCHER_CONTROLLER_H_
-#define CHROME_BROWSER_UI_ASH_LAUNCHER_ARC_APP_WINDOW_LAUNCHER_CONTROLLER_H_
-
-#include <map>
-#include <memory>
-#include <vector>
-
-#include "base/macros.h"
-#include "base/scoped_observer.h"
-#include "base/time/time.h"
-#include "chrome/browser/chromeos/arc/session/arc_session_manager.h"
-#include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
-#include "chrome/browser/ui/ash/launcher/app_window_launcher_controller.h"
-#include "chrome/browser/ui/ash/launcher/arc_app_shelf_id.h"
-#include "chrome/browser/ui/ash/launcher/arc_app_window_delegate.h"
-#include "ui/aura/env_observer.h"
-#include "ui/aura/window_observer.h"
-
-namespace aura {
-class Window;
-}
-
-namespace gfx {
-class ImageSkia;
-}
-
-class ArcAppWindow;
-class ArcAppWindowLauncherItemController;
-class ChromeLauncherController;
-
-class Profile;
-
-class ArcAppWindowLauncherController : public AppWindowLauncherController,
-                                       public aura::EnvObserver,
-                                       public aura::WindowObserver,
-                                       public ArcAppListPrefs::Observer,
-                                       public arc::ArcSessionManager::Observer,
-                                       public ArcAppWindowDelegate {
- public:
-  explicit ArcAppWindowLauncherController(ChromeLauncherController* owner);
-  ~ArcAppWindowLauncherController() override;
-
-  // AppWindowLauncherController:
-  void ActiveUserChanged(const std::string& user_email) override;
-  void AdditionalUserAddedToSession(Profile* profile) override;
-
-  // aura::EnvObserver:
-  void OnWindowInitialized(aura::Window* window) override;
-
-  // aura::WindowObserver:
-  void OnWindowVisibilityChanged(aura::Window* window, bool visible) override;
-  void OnWindowDestroying(aura::Window* window) override;
-
-  // wm::ActivationChangeObserver:
-  void OnWindowActivated(wm::ActivationChangeObserver::ActivationReason reason,
-                         aura::Window* gained_active,
-                         aura::Window* lost_active) override;
-
-  // ArcAppListPrefs::Observer:
-  void OnAppStatesChanged(const std::string& app_id,
-                          const ArcAppListPrefs::AppInfo& app_info) override;
-  void OnAppRemoved(const std::string& app_id) override;
-  void OnTaskCreated(int task_id,
-                     const std::string& package_name,
-                     const std::string& activity,
-                     const std::string& intent) override;
-  void OnTaskDescriptionChanged(
-      int32_t task_id,
-      const std::string& label,
-      const arc::mojom::RawIconPngData& icon) override;
-  void OnTaskDestroyed(int task_id) override;
-  void OnTaskSetActive(int32_t task_id) override;
-
-  // ArcAppWindowDelegate:
-  int GetActiveTaskId() const override;
-
-  const std::vector<aura::Window*>& GetObservedWindows() {
-    return observed_windows_;
-  }
-
- private:
-  class AppWindowInfo;
-
-  using TaskIdToAppWindowInfo = std::map<int, std::unique_ptr<AppWindowInfo>>;
-
-  // Maps shelf group id to controller. Shelf group id is optional parameter for
-  // the Android task. If it is not set, app id is used instead.
-  using ShelfGroupToAppControllerMap =
-      std::map<arc::ArcAppShelfId, ArcAppWindowLauncherItemController*>;
-
-  void StartObserving(Profile* profile);
-  void StopObserving(Profile* profile);
-
-  void RegisterApp(AppWindowInfo* app_window_info);
-  void UnregisterApp(AppWindowInfo* app_window_info);
-  void HandlePlayStoreLaunch(AppWindowInfo* app_window_info);
-
-  AppWindowInfo* GetAppWindowInfoForTask(int task_id);
-  ArcAppWindow* GetAppWindowForTask(int task_id);
-
-  void AttachControllerToWindowIfNeeded(aura::Window* window);
-  void AttachControllerToWindowsIfNeeded();
-  ArcAppWindowLauncherItemController* AttachControllerToTask(
-      int taskId,
-      const AppWindowInfo& app_window_info);
-
-  std::vector<int> GetTaskIdsForApp(const std::string& arc_app_id) const;
-
-  // AppWindowLauncherController:
-  AppWindowLauncherItemController* ControllerForWindow(
-      aura::Window* window) override;
-  void OnItemDelegateDiscarded(ash::ShelfItemDelegate* delegate) override;
-
-  // arc::ArcSessionManager::Observer:
-  void OnArcOptInManagementCheckStarted() override;
-  void OnArcSessionStopped(arc::ArcStopReason stop_reason) override;
-
-  void OnIconLoaded(int32_t task_id,
-                    const std::string& title,
-                    const gfx::ImageSkia& icon);
-
-  // Sets the window title and icon.
-  void SetDescription(int32_t task_id,
-                      const std::string& title,
-                      gfx::ImageSkia icon);
-
-  int active_task_id_ = -1;
-  TaskIdToAppWindowInfo task_id_to_app_window_info_;
-  ShelfGroupToAppControllerMap app_shelf_group_to_controller_map_;
-  std::vector<aura::Window*> observed_windows_;
-  Profile* observed_profile_ = nullptr;
-
-  // The time when the ARC OptIn management check was started. This happens
-  // right after user agrees the ToS or in some cases for managed user when ARC
-  // starts for the first time. OptIn management check is preceding step before
-  // ARC container is actually started.
-  base::Time opt_in_management_check_start_time_;
-
-  base::WeakPtrFactory<ArcAppWindowLauncherController> weak_ptr_factory_{this};
-
-  DISALLOW_COPY_AND_ASSIGN(ArcAppWindowLauncherController);
-};
-
-#endif  // CHROME_BROWSER_UI_ASH_LAUNCHER_ARC_APP_WINDOW_LAUNCHER_CONTROLLER_H_
diff --git a/chrome/browser/ui/ash/launcher/arc_app_window_launcher_item_controller.cc b/chrome/browser/ui/ash/launcher/arc_app_window_launcher_item_controller.cc
deleted file mode 100644
index 4e05af9..0000000
--- a/chrome/browser/ui/ash/launcher/arc_app_window_launcher_item_controller.cc
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/ash/launcher/arc_app_window_launcher_item_controller.h"
-
-#include <utility>
-
-#include "ash/public/cpp/window_properties.h"
-#include "ash/public/cpp/window_state_type.h"
-#include "chrome/browser/chromeos/arc/pip/arc_pip_bridge.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
-#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
-#include "chrome/browser/ui/ash/launcher/launcher_controller_helper.h"
-#include "ui/aura/window.h"
-#include "ui/base/base_window.h"
-
-ArcAppWindowLauncherItemController::ArcAppWindowLauncherItemController(
-    const ash::ShelfID shelf_id)
-    : AppWindowLauncherItemController(shelf_id) {}
-
-ArcAppWindowLauncherItemController::~ArcAppWindowLauncherItemController() =
-    default;
-
-void ArcAppWindowLauncherItemController::AddTaskId(int task_id) {
-  task_ids_.insert(task_id);
-}
-
-void ArcAppWindowLauncherItemController::RemoveTaskId(int task_id) {
-  task_ids_.erase(task_id);
-}
-
-bool ArcAppWindowLauncherItemController::HasAnyTasks() const {
-  return !task_ids_.empty();
-}
-
-void ArcAppWindowLauncherItemController::ItemSelected(
-    std::unique_ptr<ui::Event> event,
-    int64_t display_id,
-    ash::ShelfLaunchSource source,
-    ItemSelectedCallback callback,
-    const ItemFilterPredicate& filter_predicate) {
-  if (window_count()) {
-    // Tapping the shelf icon of an app that's showing PIP means expanding PIP.
-    // Even if the app contains multiple windows, we just expand PIP without
-    // showing the menu on the shelf icon.
-    for (ui::BaseWindow* window : windows()) {
-      aura::Window* native_window = window->GetNativeWindow();
-      if (native_window->GetProperty(ash::kWindowStateTypeKey) ==
-          ash::WindowStateType::kPip) {
-        Profile* profile = ChromeLauncherController::instance()->profile();
-        arc::ArcPipBridge* pip_bridge =
-            arc::ArcPipBridge::GetForBrowserContext(profile);
-        // ClosePip() actually expands PIP.
-        pip_bridge->ClosePip();
-        std::move(callback).Run(ash::SHELF_ACTION_NONE, {});
-        return;
-      }
-    }
-    AppWindowLauncherItemController::ItemSelected(std::move(event), display_id,
-                                                  source, std::move(callback),
-                                                  filter_predicate);
-    return;
-  }
-
-  if (task_ids_.empty()) {
-    NOTREACHED();
-    std::move(callback).Run(ash::SHELF_ACTION_NONE, {});
-    return;
-  }
-  arc::SetTaskActive(*task_ids_.begin());
-  std::move(callback).Run(ash::SHELF_ACTION_NEW_WINDOW_CREATED, {});
-}
diff --git a/chrome/browser/ui/ash/launcher/arc_app_window_launcher_item_controller.h b/chrome/browser/ui/ash/launcher/arc_app_window_launcher_item_controller.h
deleted file mode 100644
index f78b0d8..0000000
--- a/chrome/browser/ui/ash/launcher/arc_app_window_launcher_item_controller.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 CHROME_BROWSER_UI_ASH_LAUNCHER_ARC_APP_WINDOW_LAUNCHER_ITEM_CONTROLLER_H_
-#define CHROME_BROWSER_UI_ASH_LAUNCHER_ARC_APP_WINDOW_LAUNCHER_ITEM_CONTROLLER_H_
-
-#include <string>
-#include <unordered_set>
-
-#include "base/macros.h"
-#include "chrome/browser/ui/ash/launcher/app_window_launcher_item_controller.h"
-
-class ArcAppWindow;
-
-// Shelf item delegate for ARC app windows.
-class ArcAppWindowLauncherItemController
-    : public AppWindowLauncherItemController {
- public:
-  explicit ArcAppWindowLauncherItemController(const ash::ShelfID shelf_id);
-
-  ~ArcAppWindowLauncherItemController() override;
-
-  // AppWindowLauncherItemController overrides:
-  void ItemSelected(std::unique_ptr<ui::Event> event,
-                    int64_t display_id,
-                    ash::ShelfLaunchSource source,
-                    ItemSelectedCallback callback,
-                    const ItemFilterPredicate& filter_predicate) override;
-
-  void AddTaskId(int task_id);
-  void RemoveTaskId(int task_id);
-  bool HasAnyTasks() const;
-
- private:
-  // Update the shelf item's icon for the active window.
-  void UpdateIcon(ArcAppWindow* arc_app_window);
-
-  std::unordered_set<int> task_ids_;
-
-  DISALLOW_COPY_AND_ASSIGN(ArcAppWindowLauncherItemController);
-};
-
-#endif  // CHROME_BROWSER_UI_ASH_LAUNCHER_ARC_APP_WINDOW_LAUNCHER_ITEM_CONTROLLER_H_
diff --git a/chrome/browser/ui/ash/launcher/browser_status_monitor.cc b/chrome/browser/ui/ash/launcher/browser_status_monitor.cc
index 88c4e8c..47976ef 100644
--- a/chrome/browser/ui/ash/launcher/browser_status_monitor.cc
+++ b/chrome/browser/ui/ash/launcher/browser_status_monitor.cc
@@ -97,12 +97,10 @@
       browser_tab_strip_tracker_(this, nullptr) {
   DCHECK(launcher_controller_);
 
-  if (base::FeatureList::IsEnabled(features::kAppServiceInstanceRegistry)) {
-    app_service_instance_helper_ =
-        launcher_controller->app_service_app_window_controller()
-            ->app_service_instance_helper();
-    DCHECK(app_service_instance_helper_);
-  }
+  app_service_instance_helper_ =
+      launcher_controller->app_service_app_window_controller()
+          ->app_service_instance_helper();
+  DCHECK(app_service_instance_helper_);
 }
 
 BrowserStatusMonitor::~BrowserStatusMonitor() {
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc
index 0bdea9bb..d3eb7ca 100644
--- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc
@@ -48,15 +48,11 @@
 #include "chrome/browser/ui/ash/launcher/app_shortcut_launcher_item_controller.h"
 #include "chrome/browser/ui/ash/launcher/app_window_launcher_controller.h"
 #include "chrome/browser/ui/ash/launcher/app_window_launcher_item_controller.h"
-#include "chrome/browser/ui/ash/launcher/arc_app_window_launcher_controller.h"
 #include "chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.h"
 #include "chrome/browser/ui/ash/launcher/browser_status_monitor.h"
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_util.h"
-#include "chrome/browser/ui/ash/launcher/crostini_app_window_shelf_controller.h"
-#include "chrome/browser/ui/ash/launcher/internal_app_window_shelf_controller.h"
 #include "chrome/browser/ui/ash/launcher/launcher_controller_helper.h"
 #include "chrome/browser/ui/ash/launcher/launcher_extension_app_updater.h"
-#include "chrome/browser/ui/ash/launcher/multi_profile_app_window_launcher_controller.h"
 #include "chrome/browser/ui/ash/launcher/multi_profile_browser_status_monitor.h"
 #include "chrome/browser/ui/ash/launcher/shelf_spinner_controller.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
@@ -250,57 +246,21 @@
         new ChromeLauncherControllerUserSwitchObserver(this));
   }
 
-  if (base::FeatureList::IsEnabled(features::kAppServiceInstanceRegistry)) {
-    std::unique_ptr<AppServiceAppWindowLauncherController>
-        app_service_controller =
-            std::make_unique<AppServiceAppWindowLauncherController>(this);
-    app_service_app_window_controller_ = app_service_controller.get();
-    app_window_controllers_.emplace_back(std::move(app_service_controller));
-    if (SessionControllerClientImpl::IsMultiProfileAvailable()) {
-      // If running in separated destkop mode, we create the multi profile
-      // version of status monitor.
-      browser_status_monitor_ =
-          std::make_unique<MultiProfileBrowserStatusMonitor>(this);
-    } else {
-      // Create our v1/v2 application / browser monitors which will inform the
-      // launcher of status changes.
-      browser_status_monitor_ = std::make_unique<BrowserStatusMonitor>(this);
-    }
-    return;
-  }
-
-  std::unique_ptr<AppWindowLauncherController> extension_app_window_controller;
-  // Create our v1/v2 application / browser monitors which will inform the
-  // launcher of status changes.
+  std::unique_ptr<AppServiceAppWindowLauncherController>
+      app_service_controller =
+          std::make_unique<AppServiceAppWindowLauncherController>(this);
+  app_service_app_window_controller_ = app_service_controller.get();
+  app_window_controllers_.emplace_back(std::move(app_service_controller));
   if (SessionControllerClientImpl::IsMultiProfileAvailable()) {
-    // If running in separated destkop mode, we create the multi profile version
-    // of status monitor.
+    // If running in separated desktop mode, we create the multi profile
+    // version of status monitor.
     browser_status_monitor_ =
         std::make_unique<MultiProfileBrowserStatusMonitor>(this);
-    extension_app_window_controller.reset(
-        new MultiProfileAppWindowLauncherController(this));
   } else {
     // Create our v1/v2 application / browser monitors which will inform the
     // launcher of status changes.
     browser_status_monitor_ = std::make_unique<BrowserStatusMonitor>(this);
-    extension_app_window_controller.reset(
-        new ExtensionAppWindowLauncherController(this));
   }
-  app_window_controllers_.push_back(std::move(extension_app_window_controller));
-
-  auto arc_app_window_controller =
-      std::make_unique<ArcAppWindowLauncherController>(this);
-  arc_app_window_controller_ = arc_app_window_controller.get();
-  app_window_controllers_.push_back(std::move(arc_app_window_controller));
-
-  if (crostini::CrostiniFeatures::Get()->IsUIAllowed(profile)) {
-    std::unique_ptr<CrostiniAppWindowShelfController> crostini_controller =
-        std::make_unique<CrostiniAppWindowShelfController>(this);
-    crostini_app_window_shelf_controller_ = crostini_controller.get();
-    app_window_controllers_.emplace_back(std::move(crostini_controller));
-  }
-  app_window_controllers_.push_back(
-      std::make_unique<InternalAppWindowShelfController>(this));
 }
 
 ChromeLauncherController::~ChromeLauncherController() {
@@ -308,7 +268,7 @@
   browser_status_monitor_.reset();
 
   // Reset the app window controllers here since it has a weak pointer to this.
-  arc_app_window_controller_ = nullptr;
+  app_service_app_window_controller_ = nullptr;
   app_window_controllers_.clear();
 
   // Destroy the ShelfSpinnerController before clearing delegates.
@@ -662,19 +622,9 @@
 }
 
 std::vector<aura::Window*> ChromeLauncherController::GetArcWindows() {
-  if (base::FeatureList::IsEnabled(features::kAppServiceInstanceRegistry)) {
-    if (app_service_app_window_controller_)
-      return app_service_app_window_controller_->GetArcWindows();
-    return std::vector<aura::Window*>();
-  }
-
-  std::vector<aura::Window*> windows =
-      arc_app_window_controller_->GetObservedWindows();
-  std::vector<aura::Window*> arc_windows;
-  std::copy_if(windows.begin(), windows.end(),
-               std::inserter(arc_windows, arc_windows.end()),
-               [](aura::Window* w) { return arc::IsArcAppWindow(w); });
-  return arc_windows;
+  if (app_service_app_window_controller_)
+    return app_service_app_window_controller_->GetArcWindows();
+  return std::vector<aura::Window*>();
 }
 
 void ChromeLauncherController::ActivateShellApp(const std::string& app_id,
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.h b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.h
index 40c3857..850e98e3 100644
--- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.h
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.h
@@ -18,7 +18,6 @@
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/ui/app_icon_loader_delegate.h"
 #include "chrome/browser/ui/app_list/app_list_syncable_service.h"
-#include "chrome/browser/ui/ash/launcher/arc_app_window_launcher_controller.h"
 #include "chrome/browser/ui/ash/launcher/discover_window_observer.h"
 #include "chrome/browser/ui/ash/launcher/launcher_app_updater.h"
 #include "chrome/browser/ui/ash/launcher/settings_window_observer.h"
@@ -32,7 +31,6 @@
 class BrowserShortcutLauncherItemController;
 class BrowserStatusMonitor;
 class ChromeLauncherControllerUserSwitchObserver;
-class CrostiniAppWindowShelfController;
 class GURL;
 class Profile;
 class LauncherControllerHelper;
@@ -85,11 +83,6 @@
     return app_service_app_window_controller_;
   }
 
-  CrostiniAppWindowShelfController* crostini_app_window_shelf_controller()
-      const {
-    return crostini_app_window_shelf_controller_;
-  }
-
   // Initializes this ChromeLauncherController.
   void Init();
 
@@ -414,10 +407,6 @@
   AppServiceAppWindowLauncherController* app_service_app_window_controller_ =
       nullptr;
 
-  // The shelf controller for Crostini apps.
-  CrostiniAppWindowShelfController* crostini_app_window_shelf_controller_ =
-      nullptr;
-
   // When true, changes to pinned shelf items should update the sync model.
   bool should_sync_pin_changes_ = true;
 
@@ -447,9 +436,6 @@
   std::vector<std::unique_ptr<AppWindowLauncherController>>
       app_window_controllers_;
 
-  // Pointer to the ARC app window controller owned by app_window_controllers_.
-  ArcAppWindowLauncherController* arc_app_window_controller_ = nullptr;
-
   // Used to handle app load/unload events.
   std::map<Profile*, std::vector<std::unique_ptr<LauncherAppUpdater>>>
       app_updaters_;
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc
index 2787631a..22d916b 100644
--- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc
@@ -64,12 +64,12 @@
 #include "chrome/browser/ui/app_list/internal_app/internal_app_metadata.h"
 #include "chrome/browser/ui/apps/chrome_app_delegate.h"
 #include "chrome/browser/ui/ash/chrome_launcher_prefs.h"
+#include "chrome/browser/ui/ash/launcher/app_service/app_service_app_window_launcher_item_controller.h"
 #include "chrome/browser/ui/ash/launcher/app_window_launcher_controller.h"
+#include "chrome/browser/ui/ash/launcher/app_window_launcher_item_controller.h"
 #include "chrome/browser/ui/ash/launcher/arc_app_window.h"
-#include "chrome/browser/ui/ash/launcher/arc_app_window_launcher_controller.h"
 #include "chrome/browser/ui/ash/launcher/browser_status_monitor.h"
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_util.h"
-#include "chrome/browser/ui/ash/launcher/extension_app_window_launcher_item_controller.h"
 #include "chrome/browser/ui/ash/launcher/launcher_controller_helper.h"
 #include "chrome/browser/ui/ash/launcher/shelf_spinner_controller.h"
 #include "chrome/browser/ui/ash/launcher/shelf_spinner_item_controller.h"
@@ -2133,7 +2133,8 @@
   // platform app.
   model_->SetShelfItemDelegate(
       shelf_id,
-      std::make_unique<ExtensionAppWindowLauncherItemController>(shelf_id));
+      std::make_unique<AppServiceAppWindowLauncherItemController>(
+          shelf_id, launcher_controller_->app_service_app_window_controller()));
   launcher_controller_->SetItemStatus(shelf_id, ash::STATUS_RUNNING);
 
   // This launch request should be ignored in case of active app.
@@ -4052,7 +4053,8 @@
                     std::unique_ptr<AppIconLoader>(app_icon_loader2));
 
   launcher_controller_->CreateAppLauncherItem(
-      std::make_unique<ExtensionAppWindowLauncherItemController>(shelf_id3),
+      std::make_unique<AppServiceAppWindowLauncherItemController>(
+          shelf_id3, launcher_controller_->app_service_app_window_controller()),
       ash::STATUS_RUNNING);
   EXPECT_EQ(0, app_icon_loader1->fetch_count());
   EXPECT_EQ(0, app_icon_loader1->clear_count());
@@ -4060,7 +4062,8 @@
   EXPECT_EQ(0, app_icon_loader2->clear_count());
 
   launcher_controller_->CreateAppLauncherItem(
-      std::make_unique<ExtensionAppWindowLauncherItemController>(shelf_id2),
+      std::make_unique<AppServiceAppWindowLauncherItemController>(
+          shelf_id2, launcher_controller_->app_service_app_window_controller()),
       ash::STATUS_RUNNING);
   EXPECT_EQ(0, app_icon_loader1->fetch_count());
   EXPECT_EQ(0, app_icon_loader1->clear_count());
@@ -4068,7 +4071,8 @@
   EXPECT_EQ(0, app_icon_loader2->clear_count());
 
   launcher_controller_->CreateAppLauncherItem(
-      std::make_unique<ExtensionAppWindowLauncherItemController>(shelf_id1),
+      std::make_unique<AppServiceAppWindowLauncherItemController>(
+          shelf_id1, launcher_controller_->app_service_app_window_controller()),
       ash::STATUS_RUNNING);
   EXPECT_EQ(1, app_icon_loader1->fetch_count());
   EXPECT_EQ(0, app_icon_loader1->clear_count());
diff --git a/chrome/browser/ui/ash/launcher/crostini_app_window_shelf_controller.cc b/chrome/browser/ui/ash/launcher/crostini_app_window_shelf_controller.cc
deleted file mode 100644
index 1b2389e..0000000
--- a/chrome/browser/ui/ash/launcher/crostini_app_window_shelf_controller.cc
+++ /dev/null
@@ -1,410 +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 "chrome/browser/ui/ash/launcher/crostini_app_window_shelf_controller.h"
-
-#include <memory>
-#include <string>
-#include <utility>
-
-#include "ash/public/cpp/app_types.h"
-#include "ash/public/cpp/multi_user_window_manager.h"
-#include "ash/public/cpp/shelf_model.h"
-#include "ash/public/cpp/shell_window_ids.h"
-#include "ash/public/cpp/window_properties.h"
-#include "base/bind.h"
-#include "base/containers/flat_tree.h"
-#include "base/strings/string_util.h"
-#include "base/time/time.h"
-#include "chrome/browser/chromeos/crostini/crostini_force_close_watcher.h"
-#include "chrome/browser/chromeos/crostini/crostini_shelf_utils.h"
-#include "chrome/browser/chromeos/crostini/crostini_util.h"
-#include "chrome/browser/chromeos/guest_os/guest_os_registry_service.h"
-#include "chrome/browser/chromeos/guest_os/guest_os_registry_service_factory.h"
-#include "chrome/browser/chromeos/plugin_vm/plugin_vm_util.h"
-#include "chrome/browser/chromeos/profiles/profile_helper.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/ash/launcher/app_window_base.h"
-#include "chrome/browser/ui/ash/launcher/app_window_launcher_item_controller.h"
-#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
-#include "chrome/browser/ui/ash/launcher/shelf_spinner_controller.h"
-#include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_helper.h"
-#include "chrome/browser/ui/ash/session_controller_client_impl.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/browser_finder.h"
-#include "chrome/browser/ui/browser_window.h"
-#include "chrome/common/chrome_features.h"
-#include "components/arc/arc_util.h"
-#include "components/exo/permission.h"
-#include "components/exo/shell_surface_util.h"
-#include "components/user_manager/user_manager.h"
-#include "ui/aura/client/aura_constants.h"
-#include "ui/aura/env.h"
-#include "ui/base/base_window.h"
-#include "ui/base/ui_base_features.h"
-#include "ui/display/display.h"
-#include "ui/display/screen.h"
-#include "ui/views/widget/widget.h"
-#include "ui/wm/core/window_util.h"
-
-namespace {
-
-// Time allowed for apps to self-activate after launch, see
-// go/crostini-self-activate for details.
-constexpr base::TimeDelta kSelfActivationTimeout =
-    base::TimeDelta::FromSeconds(5);
-
-void MoveWindowFromOldDisplayToNewDisplay(aura::Window* window,
-                                          display::Display& old_display,
-                                          display::Display& new_display) {
-  // Adjust the window size and origin in proportion to the relative size of the
-  // display.
-  int old_width = old_display.bounds().width();
-  int new_width = new_display.bounds().width();
-  int old_height = old_display.bounds().height();
-  int new_height = new_display.bounds().height();
-  gfx::Rect old_bounds = window->bounds();
-  gfx::Rect new_bounds(old_bounds.x() * new_width / old_width,
-                       old_bounds.y() * new_height / old_height,
-                       old_bounds.width() * new_width / old_width,
-                       old_bounds.height() * new_height / old_height);
-
-  // Transform the bounds in display to that in screen.
-  gfx::Point new_origin = new_display.bounds().origin();
-  new_origin.Offset(new_bounds.x(), new_bounds.y());
-  new_bounds.set_origin(new_origin);
-  window->SetBoundsInScreen(new_bounds, new_display);
-}
-
-}  // namespace
-
-CrostiniAppWindowShelfController::CrostiniAppWindowShelfController(
-    ChromeLauncherController* owner)
-    : AppWindowLauncherController(owner) {
-  aura::Env::GetInstance()->AddObserver(this);
-}
-
-CrostiniAppWindowShelfController::~CrostiniAppWindowShelfController() {
-  for (auto* window : observed_windows_)
-    window->RemoveObserver(this);
-  aura::Env::GetInstance()->RemoveObserver(this);
-}
-
-void CrostiniAppWindowShelfController::AddToShelf(aura::Window* window,
-                                                  AppWindowBase* app_window) {
-  window->SetProperty<int>(ash::kShelfItemTypeKey, ash::TYPE_APP);
-  ash::ShelfID shelf_id = app_window->shelf_id();
-  AppWindowLauncherItemController* item_controller =
-      owner()->shelf_model()->GetAppWindowLauncherItemController(shelf_id);
-  if (item_controller == nullptr) {
-    auto controller =
-        std::make_unique<AppWindowLauncherItemController>(shelf_id);
-    item_controller = controller.get();
-    if (!owner()->GetItem(shelf_id)) {
-      owner()->CreateAppLauncherItem(std::move(controller),
-                                     ash::STATUS_RUNNING);
-    } else {
-      owner()->shelf_model()->SetShelfItemDelegate(shelf_id,
-                                                   std::move(controller));
-      owner()->SetItemStatus(shelf_id, ash::STATUS_RUNNING);
-    }
-  }
-
-  window->SetProperty(ash::kShelfIDKey, shelf_id.Serialize());
-  item_controller->AddWindow(app_window);
-  app_window->SetController(item_controller);
-}
-
-ash::ShelfID CrostiniAppWindowShelfController::RemoveFromShelf(
-    aura::Window* window,
-    AppWindowBase* app_window) {
-  UnregisterAppWindow(app_window);
-
-  // Check if we may close controller now, at this point we can safely remove
-  // controllers without window.
-  AppWindowLauncherItemController* item_controller =
-      owner()->shelf_model()->GetAppWindowLauncherItemController(
-          app_window->shelf_id());
-
-  if (item_controller && item_controller->window_count() == 0) {
-    ash::ShelfID shelf_id = item_controller->shelf_id();
-    owner()->CloseLauncherItem(shelf_id);
-    return shelf_id;
-  }
-  return ash::ShelfID();
-}
-
-void CrostiniAppWindowShelfController::ActiveUserChanged(
-    const std::string& user_email) {
-  for (auto& w : aura_window_to_app_window_) {
-    if (MultiUserWindowManagerHelper::GetWindowManager()
-            ->GetWindowOwner(w.first)
-            .GetUserEmail() == user_email) {
-      AddToShelf(w.first, w.second.get());
-    } else {
-      RemoveFromShelf(w.first, w.second.get());
-    }
-  }
-}
-
-void CrostiniAppWindowShelfController::OnWindowInitialized(
-    aura::Window* window) {
-  // An Crostini window has type WINDOW_TYPE_NORMAL, a WindowDelegate and
-  // is a top level views widget. Tooltips, menus, and other kinds of transient
-  // windows that can't activate are filtered out. The transient child is set
-  // up after window Init so add it here but remove it later.
-  if (window->type() != aura::client::WINDOW_TYPE_NORMAL || !window->delegate())
-    return;
-  views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window);
-  if (!widget || !widget->is_top_level())
-    return;
-  if (!widget->CanActivate())
-    return;
-
-  observed_windows_.emplace(window);
-  window->AddObserver(this);
-}
-
-void CrostiniAppWindowShelfController::OnWindowVisibilityChanging(
-    aura::Window* window,
-    bool visible) {
-  if (!visible)
-    return;
-
-  // Transient windows are set up after window Init, so remove them here.
-  // TODO(timloh): ARC and Plugin VM are similar, but Crostini shouldn't need to
-  // know about them.
-  if (wm::GetTransientParent(window) ||
-      arc::GetWindowTaskId(window) != arc::kNoTaskId ||
-      plugin_vm::IsPluginVmAppWindow(window)) {
-    DCHECK(aura_window_to_app_window_.find(window) ==
-           aura_window_to_app_window_.end());
-    auto it = observed_windows_.find(window);
-    DCHECK(it != observed_windows_.end());
-    observed_windows_.erase(it);
-    window->RemoveObserver(this);
-    return;
-  }
-
-  // Skip when this window has been handled. This can happen when the window
-  // becomes visible again.
-  auto app_window_it = aura_window_to_app_window_.find(window);
-  if (app_window_it != aura_window_to_app_window_.end())
-    return;
-
-  // Handle browser windows, such as the Crostini terminal.
-  Browser* browser = chrome::FindBrowserWithWindow(window);
-  if (browser) {
-    base::Optional<std::string> app_id =
-        crostini::CrostiniAppIdFromAppName(browser->app_name());
-    if (!app_id)
-      return;
-    RegisterAppWindow(window, app_id.value());
-    return;
-  }
-
-  // Currently Crostini can only be used from the primary profile. In the
-  // future, this may be replaced by some way of matching the container that
-  // runs this app with the user that owns it.
-  const AccountId& primary_account_id =
-      user_manager::UserManager::Get()->GetPrimaryUser()->GetAccountId();
-
-  Profile* primary_account_profile =
-      chromeos::ProfileHelper::Get()->GetProfileByAccountId(primary_account_id);
-
-  const std::string& shelf_app_id = crostini::GetCrostiniShelfAppId(
-      primary_account_profile, exo::GetShellApplicationId(window),
-      exo::GetShellStartupId(window));
-  // Windows without an application id set will get filtered out here.
-  if (shelf_app_id.empty())
-    return;
-
-  auto* registry_service =
-      guest_os::GuestOsRegistryServiceFactory::GetForProfile(
-          primary_account_profile);
-
-  // At this point, all remaining windows are Crostini windows. Firstly, we add
-  // support for forcibly closing it. We use the registration to retrieve the
-  // app's name, but this may be null in the case of apps with no associated
-  // launcher entry (i.e. no .desktop file), in which case the app's name is
-  // unknown.
-  base::Optional<guest_os::GuestOsRegistryService::Registration> registration =
-      registry_service->GetRegistration(shelf_app_id);
-  RegisterCrostiniWindowForForceClose(
-      window, registration.has_value() ? registration->Name() : "");
-
-  // Failed to uniquely identify the Crostini app that this window is for.
-  // The spinners on the shelf have internal app IDs which are valid
-  // extensions IDs. If the ID here starts with "crostini:" then it implies
-  // that it has failed to identify the exact app that's starting.
-  // The existing spinner that fails to be linked back should be closed,
-  // otherwise it will be left on the shelf indefinetely until it is closed
-  // manually by the user.
-  // When the condition is triggered here, the container is up and at least
-  // one app is starting. It's safe to close all the spinners since their
-  // respective apps take at most another few seconds to start.
-  // Work is ongoing to make this occur as infrequently as possible.
-  // See https://crbug.com/854911.
-  if (crostini::IsUnmatchedCrostiniShelfAppId(shelf_app_id)) {
-    owner()->GetShelfSpinnerController()->CloseCrostiniSpinners();
-  }
-
-  // Prevent Crostini window from showing up after user switch.
-  MultiUserWindowManagerHelper::GetWindowManager()->SetWindowOwner(
-      window, primary_account_id);
-
-  RegisterAppWindow(window, shelf_app_id);
-
-  // Move the Crostini app window to the right display if necessary.
-  int64_t display_id = crostini_app_display_.GetDisplayIdForAppId(shelf_app_id);
-  if (display_id == display::kInvalidDisplayId)
-    return;
-
-  display::Display new_display;
-  if (!display::Screen::GetScreen()->GetDisplayWithDisplayId(display_id,
-                                                             &new_display))
-    return;
-  display::Display old_display =
-      display::Screen::GetScreen()->GetDisplayNearestWindow(window);
-
-  if (new_display != old_display)
-    MoveWindowFromOldDisplayToNewDisplay(window, old_display, new_display);
-}
-
-void CrostiniAppWindowShelfController::RegisterAppWindow(
-    aura::Window* window,
-    const std::string& shelf_app_id) {
-  window->SetProperty(aura::client::kAppType,
-                      static_cast<int>(ash::AppType::CROSTINI_APP));
-  window->SetProperty(ash::kAppIDKey, shelf_app_id);
-
-  const ash::ShelfID shelf_id(shelf_app_id);
-  views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window);
-  aura_window_to_app_window_[window] =
-      std::make_unique<AppWindowBase>(shelf_id, widget);
-  AppWindowBase* app_window = aura_window_to_app_window_[window].get();
-
-  // Only add an app to the shelf if it's associated with the currently active
-  // user (which should always be the primary user at this time).
-  if (SessionControllerClientImpl::IsMultiProfileAvailable() &&
-      user_manager::UserManager::Get()->GetActiveUser()->GetAccountId() !=
-          MultiUserWindowManagerHelper::GetWindowManager()->GetWindowOwner(
-              window))
-    return;
-  AddToShelf(window, app_window);
-}
-
-void CrostiniAppWindowShelfController::OnWindowDestroying(
-    aura::Window* window) {
-  auto it = observed_windows_.find(window);
-  DCHECK(it != observed_windows_.end());
-  observed_windows_.erase(it);
-  window->RemoveObserver(this);
-
-  auto app_window_it = aura_window_to_app_window_.find(window);
-  if (app_window_it == aura_window_to_app_window_.end())
-    return;
-
-  ash::ShelfID shelf_id = RemoveFromShelf(window, app_window_it->second.get());
-  if (!shelf_id.IsNull()) {
-    const std::string& app_id = shelf_id.app_id;
-    if (app_id == app_id_to_restart_) {
-      crostini::LaunchCrostiniApp(
-          ChromeLauncherController::instance()->profile(), app_id,
-          display_id_to_restart_in_);
-      app_id_to_restart_.clear();
-    }
-  }
-
-  aura_window_to_app_window_.erase(app_window_it);
-
-  base::EraseIf(activation_permissions_, [&window](const auto& element) {
-    return element.first == window;
-  });
-}
-
-AppWindowLauncherItemController*
-CrostiniAppWindowShelfController::ControllerForWindow(aura::Window* window) {
-  if (!window)
-    return nullptr;
-
-  auto app_window_it = aura_window_to_app_window_.find(window);
-  if (app_window_it == aura_window_to_app_window_.end())
-    return nullptr;
-
-  AppWindowBase* app_window = app_window_it->second.get();
-
-  if (app_window == nullptr)
-    return nullptr;
-
-  return app_window->controller();
-}
-
-void CrostiniAppWindowShelfController::UnregisterAppWindow(
-    AppWindowBase* app_window) {
-  if (!app_window)
-    return;
-
-  AppWindowLauncherItemController* controller = app_window->controller();
-  if (controller)
-    controller->RemoveWindow(app_window);
-  app_window->SetController(nullptr);
-}
-
-void CrostiniAppWindowShelfController::RegisterCrostiniWindowForForceClose(
-    aura::Window* window,
-    const std::string& app_name) {
-  if (!base::FeatureList::IsEnabled(features::kCrostiniForceClose))
-    return;
-  exo::ShellSurfaceBase* surface = exo::GetShellSurfaceBaseForWindow(window);
-  if (!surface)
-    return;
-  crostini::ForceCloseWatcher::Watch(
-      std::make_unique<crostini::ShellSurfaceForceCloseDelegate>(surface,
-                                                                 app_name));
-}
-
-void CrostiniAppWindowShelfController::OnItemDelegateDiscarded(
-    ash::ShelfItemDelegate* delegate) {
-  for (auto& it : aura_window_to_app_window_) {
-    AppWindowBase* app_window = it.second.get();
-    if (!app_window || app_window->controller() != delegate)
-      continue;
-
-    VLOG(1) << "Item controller was released externally for the app "
-            << delegate->shelf_id().app_id << ".";
-
-    UnregisterAppWindow(it.second.get());
-  }
-}
-
-void CrostiniAppWindowShelfController::OnAppLaunchRequested(
-    const std::string& app_id,
-    int64_t display_id) {
-  crostini_app_display_.Register(app_id, display_id);
-  // Remove the old permissions and add a permission for every window the app
-  // currently has open.
-  activation_permissions_.clear();
-  ash::ShelfModel* model = owner()->shelf_model();
-  AppWindowLauncherItemController* launcher_item_controller =
-      model->GetAppWindowLauncherItemController(
-          model->items()[model->ItemIndexByAppID(app_id)].id);
-  // Apps run for the first time won't have a launcher controller yet, return
-  // early because they won't have windows either so permissions aren't
-  // necessary.
-  if (!launcher_item_controller)
-    return;
-  for (ui::BaseWindow* app_window : launcher_item_controller->windows()) {
-    activation_permissions_.emplace(
-        app_window->GetNativeWindow(),
-        exo::GrantPermissionToActivate(app_window->GetNativeWindow(),
-                                       kSelfActivationTimeout));
-  }
-}
-
-void CrostiniAppWindowShelfController::Restart(const ash::ShelfID& shelf_id,
-                                               int64_t display_id) {
-  app_id_to_restart_ = shelf_id.app_id;
-  display_id_to_restart_in_ = display_id;
-  ChromeLauncherController::instance()->Close(shelf_id);
-}
diff --git a/chrome/browser/ui/ash/launcher/crostini_app_window_shelf_controller.h b/chrome/browser/ui/ash/launcher/crostini_app_window_shelf_controller.h
deleted file mode 100644
index e715298..0000000
--- a/chrome/browser/ui/ash/launcher/crostini_app_window_shelf_controller.h
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_ASH_LAUNCHER_CROSTINI_APP_WINDOW_SHELF_CONTROLLER_H_
-#define CHROME_BROWSER_UI_ASH_LAUNCHER_CROSTINI_APP_WINDOW_SHELF_CONTROLLER_H_
-
-#include <memory>
-#include <vector>
-
-#include "base/containers/flat_map.h"
-#include "base/macros.h"
-#include "base/scoped_observer.h"
-#include "base/time/time.h"
-#include "chrome/browser/ui/ash/launcher/app_window_launcher_controller.h"
-#include "chrome/browser/ui/ash/launcher/crostini_app_display.h"
-#include "ui/aura/env_observer.h"
-#include "ui/aura/window_observer.h"
-#include "ui/display/display.h"
-
-namespace aura {
-class Window;
-}
-
-namespace exo {
-class Permission;
-}
-
-class AppWindowBase;
-class ChromeLauncherController;
-
-// A controller to manage Crostini app shelf items. It listens to window events
-// and events from the container bridge to put running Crostini apps on the
-// Chrome OS shelf.
-class CrostiniAppWindowShelfController : public AppWindowLauncherController,
-                                         public aura::EnvObserver,
-                                         public aura::WindowObserver {
- public:
-  explicit CrostiniAppWindowShelfController(ChromeLauncherController* owner);
-  ~CrostiniAppWindowShelfController() override;
-
-  // AppWindowLauncherController:
-  void ActiveUserChanged(const std::string& user_email) override;
-
-  // aura::EnvObserver:
-  void OnWindowInitialized(aura::Window* window) override;
-
-  // aura::WindowObserver:
-  void OnWindowVisibilityChanging(aura::Window* window, bool visible) override;
-  void OnWindowDestroying(aura::Window* window) override;
-
-  // A Crostini app with |app_id| is requested to launch on display with
-  // |display_id|.
-  void OnAppLaunchRequested(const std::string& app_id, int64_t display_id);
-
-  // Close app with |shelf_id| and then restart it on |display_id|.
-  void Restart(const ash::ShelfID& shelf_id, int64_t display_id);
-
- private:
-  using AuraWindowToAppWindow =
-      base::flat_map<aura::Window*, std::unique_ptr<AppWindowBase>>;
-
-  void RegisterAppWindow(aura::Window* window, const std::string& shelf_app_id);
-  void UnregisterAppWindow(AppWindowBase* app_window);
-  void AddToShelf(aura::Window* window, AppWindowBase* app_window);
-  void RegisterCrostiniWindowForForceClose(aura::Window* window,
-                                           const std::string& app_name);
-
-  // Returns ID of the shelf item that is removed, or a null id.
-  ash::ShelfID RemoveFromShelf(aura::Window* window, AppWindowBase* app_window);
-
-  // AppWindowLauncherController:
-  AppWindowLauncherItemController* ControllerForWindow(
-      aura::Window* window) override;
-  void OnItemDelegateDiscarded(ash::ShelfItemDelegate* delegate) override;
-
-  AuraWindowToAppWindow aura_window_to_app_window_;
-  std::set<aura::Window*> observed_windows_;
-  CrostiniAppDisplay crostini_app_display_;
-
-  // Permission objects that allow this controller to manage which application
-  // windows can activate themselves.
-  base::flat_map<aura::Window*, std::unique_ptr<exo::Permission>>
-      activation_permissions_;
-
-  // These two member variables track an app restart request. When
-  // app_id_to_restart_ is not empty the controller observes that app and
-  // relaunches it when all of its windows are closed.
-  std::string app_id_to_restart_;
-  int64_t display_id_to_restart_in_;
-
-  DISALLOW_COPY_AND_ASSIGN(CrostiniAppWindowShelfController);
-};
-
-#endif  // CHROME_BROWSER_UI_ASH_LAUNCHER_CROSTINI_APP_WINDOW_SHELF_CONTROLLER_H_
diff --git a/chrome/browser/ui/ash/launcher/extension_app_window_launcher_controller.cc b/chrome/browser/ui/ash/launcher/extension_app_window_launcher_controller.cc
deleted file mode 100644
index 0b4c794..0000000
--- a/chrome/browser/ui/ash/launcher/extension_app_window_launcher_controller.cc
+++ /dev/null
@@ -1,247 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/ash/launcher/extension_app_window_launcher_controller.h"
-
-#include <memory>
-
-#include "ash/public/cpp/app_list/internal_app_id_constants.h"
-#include "ash/public/cpp/shelf_model.h"
-#include "ash/public/cpp/shelf_types.h"
-#include "ash/public/cpp/window_properties.h"
-#include "base/stl_util.h"
-#include "base/strings/stringprintf.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
-#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_util.h"
-#include "chrome/browser/ui/ash/launcher/extension_app_window_launcher_item_controller.h"
-#include "extensions/browser/app_window/app_window.h"
-#include "extensions/browser/app_window/native_app_window.h"
-#include "extensions/common/extension.h"
-#include "ui/aura/window.h"
-#include "ui/aura/window_event_dispatcher.h"
-#include "ui/base/base_window.h"
-#include "ui/views/widget/widget.h"
-
-using extensions::AppWindow;
-using extensions::AppWindowRegistry;
-
-namespace {
-
-// Get the ShelfID for a given |app_window|.
-ash::ShelfID GetShelfId(AppWindow* app_window) {
-  // Set launch_id default value to an empty string. If showInShelf parameter
-  // is true and the window key is not empty, its value is appended to the
-  // launch_id. Otherwise, if the window key is empty, the session_id is used.
-  std::string launch_id;
-  if (app_window->show_in_shelf()) {
-    if (!app_window->window_key().empty())
-      launch_id = app_window->window_key();
-    else
-      launch_id = base::StringPrintf("%d", app_window->session_id().id());
-  }
-  return ash::ShelfID(app_window->extension_id(), launch_id);
-}
-
-}  // namespace
-
-ExtensionAppWindowLauncherController::ExtensionAppWindowLauncherController(
-    ChromeLauncherController* owner)
-    : AppWindowLauncherController(owner),
-      registry_(AppWindowRegistry::Get(owner->profile())) {
-  registry_->AddObserver(this);
-}
-
-ExtensionAppWindowLauncherController::~ExtensionAppWindowLauncherController() {
-  registry_->RemoveObserver(this);
-
-  for (const auto& iter : window_to_shelf_id_map_) {
-    iter.first->RemoveObserver(this);
-    views::Widget* widget = views::Widget::GetWidgetForNativeView(iter.first);
-    DCHECK(widget);  // Extension windows are always backed by Widgets.
-    widget->RemoveObserver(this);
-  }
-  CHECK(!views::WidgetObserver::IsInObserverList());
-}
-
-AppWindowLauncherItemController*
-ExtensionAppWindowLauncherController::ControllerForWindow(
-    aura::Window* window) {
-  const auto window_iter = window_to_shelf_id_map_.find(window);
-  if (window_iter == window_to_shelf_id_map_.end())
-    return nullptr;
-  const auto controller_iter = app_controller_map_.find(window_iter->second);
-  if (controller_iter == app_controller_map_.end())
-    return nullptr;
-  return controller_iter->second;
-}
-
-void ExtensionAppWindowLauncherController::OnItemDelegateDiscarded(
-    ash::ShelfItemDelegate* delegate) {
-  AppControllerMap::iterator it =
-      app_controller_map_.find(delegate->shelf_id());
-  if (it == app_controller_map_.end() || it->second != delegate)
-    return;
-
-  ExtensionAppWindowLauncherItemController* controller = it->second;
-  // Existing controller is no longer bound with shelf. We have to clean the
-  // cache. See crbug.com/770005.
-  VLOG(1) << "Item controller was released externally for the app "
-          << delegate->shelf_id().app_id << ".";
-  app_controller_map_.erase(it);
-
-  for (ui::BaseWindow* base_window : controller->windows()) {
-    aura::Window* window = base_window->GetNativeWindow();
-    if (window)
-      UnregisterApp(window);
-  }
-}
-
-void ExtensionAppWindowLauncherController::OnWindowActivated(
-    wm::ActivationChangeObserver::ActivationReason reason,
-    aura::Window* gained_active,
-    aura::Window* lost_active) {
-  // All work is done in OnWidgetActivationChanged(). This does nothing as the
-  // supplied windows are created by ash, which is *not* the same as the windows
-  // created by the browser when running in mash.
-}
-
-void ExtensionAppWindowLauncherController::OnAppWindowAdded(
-    extensions::AppWindow* app_window) {
-  RegisterApp(app_window);
-}
-
-void ExtensionAppWindowLauncherController::OnAppWindowShown(
-    AppWindow* app_window,
-    bool was_hidden) {
-  aura::Window* window = app_window->GetNativeWindow();
-  if (!IsRegisteredApp(window))
-    RegisterApp(app_window);
-}
-
-void ExtensionAppWindowLauncherController::OnAppWindowHidden(
-    AppWindow* app_window) {
-  aura::Window* window = app_window->GetNativeWindow();
-  if (IsRegisteredApp(window))
-    UnregisterApp(window);
-}
-
-// Called from aura::Window::~Window(), before delegate_->OnWindowDestroyed()
-// which destroys AppWindow, so both |window| and the associated AppWindow
-// are valid here.
-void ExtensionAppWindowLauncherController::OnWindowDestroying(
-    aura::Window* window) {
-  UnregisterApp(window);
-}
-
-void ExtensionAppWindowLauncherController::OnWidgetActivationChanged(
-    views::Widget* widget,
-    bool active) {
-  AppWindowLauncherItemController* controller = nullptr;
-  if (active) {
-    aura::Window* active_window = widget->GetNativeWindow();
-    DCHECK(active_window);
-    controller = ControllerForWindow(active_window);
-    DCHECK(controller);  // Observer is only added for known controllers.
-    controller->SetActiveWindow(active_window);
-  }
-  if (!active_shelf_id_.IsNull() &&
-      (!controller || controller->shelf_id() != active_shelf_id_)) {
-    owner()->SetItemStatus(active_shelf_id_, ash::STATUS_RUNNING);
-  }
-  active_shelf_id_ = controller ? controller->shelf_id() : ash::ShelfID();
-}
-
-void ExtensionAppWindowLauncherController::OnWidgetDestroying(
-    views::Widget* widget) {
-  widget->RemoveObserver(this);
-}
-
-void ExtensionAppWindowLauncherController::RegisterApp(AppWindow* app_window) {
-  aura::Window* window = app_window->GetNativeWindow();
-  const ash::ShelfID shelf_id = GetShelfId(app_window);
-  DCHECK(!shelf_id.IsNull());
-
-  window->SetProperty(ash::kAppIDKey, shelf_id.app_id);
-  window->SetProperty(ash::kShelfIDKey, shelf_id.Serialize());
-  // TODO(msw): Set shelf item types earlier to avoid ShelfWindowWatcher races.
-  window->SetProperty<int>(ash::kShelfItemTypeKey, ash::TYPE_APP);
-
-  // Windows created by IME extension should be treated the same way as the
-  // virtual keyboard window, which does not register itself in launcher.
-  if (app_window->is_ime_window())
-    return;
-
-  // Get the app's shelf identifier and add an entry to the map.
-  DCHECK_EQ(window_to_shelf_id_map_.count(window), 0u);
-  window_to_shelf_id_map_[window] = shelf_id;
-  window->AddObserver(this);
-
-  views::Widget* widget = views::Widget::GetWidgetForNativeView(window);
-  DCHECK(widget);  // Extension windows are always backed by Widgets.
-  widget->AddObserver(this);
-
-  // Find or create an item controller and launcher item.
-  AppControllerMap::iterator app_controller_iter =
-      app_controller_map_.find(shelf_id);
-  if (app_controller_iter != app_controller_map_.end()) {
-    ExtensionAppWindowLauncherItemController* controller =
-        app_controller_iter->second;
-    DCHECK_EQ(controller->app_id(), shelf_id.app_id);
-    controller->AddAppWindow(app_window);
-  } else {
-    std::unique_ptr<ExtensionAppWindowLauncherItemController> controller =
-        std::make_unique<ExtensionAppWindowLauncherItemController>(shelf_id);
-    app_controller_map_[shelf_id] = controller.get();
-
-    // Check for any existing pinned shelf item with a matching |shelf_id|.
-    if (!owner()->GetItem(shelf_id)) {
-      owner()->CreateAppLauncherItem(std::move(controller), ash::STATUS_RUNNING,
-                                     app_window->GetTitle());
-    } else {
-      owner()->shelf_model()->SetShelfItemDelegate(shelf_id,
-                                                   std::move(controller));
-    }
-
-    // Register the window after a shelf item is created to let the controller
-    // set the shelf icon property.
-    app_controller_map_[shelf_id]->AddAppWindow(app_window);
-  }
-
-  owner()->SetItemStatus(shelf_id, ash::STATUS_RUNNING);
-}
-
-void ExtensionAppWindowLauncherController::UnregisterApp(aura::Window* window) {
-  const auto window_iter = window_to_shelf_id_map_.find(window);
-  DCHECK(window_iter != window_to_shelf_id_map_.end());
-  ash::ShelfID shelf_id = window_iter->second;
-  window_to_shelf_id_map_.erase(window_iter);
-  window->RemoveObserver(this);
-
-  views::Widget* widget = views::Widget::GetWidgetForNativeView(window);
-  DCHECK(widget);  // Extension windows are always backed by Widgets.
-  widget->RemoveObserver(this);
-
-  AppControllerMap::iterator app_controller_iter =
-      app_controller_map_.find(shelf_id);
-  if (app_controller_iter == app_controller_map_.end())
-    return;
-
-  ExtensionAppWindowLauncherItemController* controller =
-      app_controller_iter->second;
-
-  controller->RemoveWindow(
-      controller->GetAppWindow(window, true /*include_hidden*/));
-  if (controller->window_count() == 0) {
-    // If this is the last window associated with the app window shelf id,
-    // close the shelf item.
-    app_controller_map_.erase(app_controller_iter);
-    owner()->CloseLauncherItem(shelf_id);
-  }
-}
-
-bool ExtensionAppWindowLauncherController::IsRegisteredApp(
-    aura::Window* window) {
-  return window_to_shelf_id_map_.find(window) != window_to_shelf_id_map_.end();
-}
diff --git a/chrome/browser/ui/ash/launcher/extension_app_window_launcher_controller.h b/chrome/browser/ui/ash/launcher/extension_app_window_launcher_controller.h
deleted file mode 100644
index 05f68fb..0000000
--- a/chrome/browser/ui/ash/launcher/extension_app_window_launcher_controller.h
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_ASH_LAUNCHER_EXTENSION_APP_WINDOW_LAUNCHER_CONTROLLER_H_
-#define CHROME_BROWSER_UI_ASH_LAUNCHER_EXTENSION_APP_WINDOW_LAUNCHER_CONTROLLER_H_
-
-#include <list>
-#include <map>
-#include <string>
-
-#include "ash/public/cpp/shelf_types.h"
-#include "base/macros.h"
-#include "chrome/browser/ui/ash/launcher/app_window_launcher_controller.h"
-#include "extensions/browser/app_window/app_window_registry.h"
-#include "ui/aura/window_observer.h"
-#include "ui/views/widget/widget_observer.h"
-
-namespace aura {
-class Window;
-}
-
-namespace extensions {
-class AppWindow;
-}
-
-class ChromeLauncherController;
-class ExtensionAppWindowLauncherItemController;
-
-// AppWindowLauncherController observes the app window registry and the
-// aura window manager. It handles adding and removing launcher items from
-// ChromeLauncherController.
-//
-// This uses WidgetObserver to observe activation changes rather than
-// wm::ActivationChangeObserver (inherited from the superclass). WidgetObserver
-// works with mash, where as wm::ActivationChangeObserver only works in the
-// ash process. See https://crbug.com/826386 for details.
-class ExtensionAppWindowLauncherController
-    : public AppWindowLauncherController,
-      public extensions::AppWindowRegistry::Observer,
-      public aura::WindowObserver,
-      public views::WidgetObserver {
- public:
-  explicit ExtensionAppWindowLauncherController(
-      ChromeLauncherController* owner);
-  ~ExtensionAppWindowLauncherController() override;
-
-  // AppWindowLauncherController:
-  AppWindowLauncherItemController* ControllerForWindow(
-      aura::Window* window) override;
-  void OnItemDelegateDiscarded(ash::ShelfItemDelegate* delegate) override;
-  void OnWindowActivated(wm::ActivationChangeObserver::ActivationReason reason,
-                         aura::Window* gained_active,
-                         aura::Window* lost_active) override;
-
-  // Overridden from AppWindowRegistry::Observer:
-  void OnAppWindowAdded(extensions::AppWindow* app_window) override;
-  void OnAppWindowShown(extensions::AppWindow* app_window,
-                        bool was_hidden) override;
-  void OnAppWindowHidden(extensions::AppWindow* app_window) override;
-
-  // Overriden from aura::WindowObserver:
-  void OnWindowDestroying(aura::Window* window) override;
-
-  // Overriden from views::WidgetObserver:
-  void OnWidgetActivationChanged(views::Widget* widget, bool active) override;
-  void OnWidgetDestroying(views::Widget* widget) override;
-
- protected:
-  // Registers an app window with the shelf and this object.
-  void RegisterApp(extensions::AppWindow* app_window);
-
-  // Unregisters an app window with the shelf and this object.
-  void UnregisterApp(aura::Window* window);
-
-  // Check if a given window is known to the launcher controller.
-  bool IsRegisteredApp(aura::Window* window);
-
- private:
-  using AppControllerMap =
-      std::map<ash::ShelfID, ExtensionAppWindowLauncherItemController*>;
-
-  // The AppWindowRegistry for the active user (represented by |owner()|).
-  extensions::AppWindowRegistry* registry_;
-
-  // Map of shelf id to controller.
-  AppControllerMap app_controller_map_;
-
-  // Map of aura::Windows to shelf ids.
-  std::map<aura::Window*, ash::ShelfID> window_to_shelf_id_map_;
-
-  // If the active Widget is an extension, this is its ShelfID, otherwise this
-  // is empty.
-  ash::ShelfID active_shelf_id_;
-
-  DISALLOW_COPY_AND_ASSIGN(ExtensionAppWindowLauncherController);
-};
-
-#endif  // CHROME_BROWSER_UI_ASH_LAUNCHER_EXTENSION_APP_WINDOW_LAUNCHER_CONTROLLER_H_
diff --git a/chrome/browser/ui/ash/launcher/extension_app_window_launcher_item_controller.cc b/chrome/browser/ui/ash/launcher/extension_app_window_launcher_item_controller.cc
deleted file mode 100644
index 5a09d88..0000000
--- a/chrome/browser/ui/ash/launcher/extension_app_window_launcher_item_controller.cc
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/ash/launcher/extension_app_window_launcher_item_controller.h"
-
-#include "ash/public/cpp/shelf_item_delegate.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
-#include "components/favicon/content/content_favicon_driver.h"
-#include "extensions/browser/app_window/app_window.h"
-#include "extensions/browser/app_window/app_window_registry.h"
-#include "extensions/browser/app_window/native_app_window.h"
-#include "ui/aura/client/aura_constants.h"
-#include "ui/aura/window.h"
-#include "ui/gfx/image/image.h"
-
-ExtensionAppWindowLauncherItemController::
-    ExtensionAppWindowLauncherItemController(const ash::ShelfID& shelf_id)
-    : AppWindowLauncherItemController(shelf_id) {}
-
-ExtensionAppWindowLauncherItemController::
-    ~ExtensionAppWindowLauncherItemController() {}
-
-void ExtensionAppWindowLauncherItemController::AddAppWindow(
-    extensions::AppWindow* app_window) {
-  AddWindow(app_window->GetBaseWindow());
-}
-
-ash::ShelfItemDelegate::AppMenuItems
-ExtensionAppWindowLauncherItemController::GetAppMenuItems(
-    int event_flags,
-    const ItemFilterPredicate& filter_predicate) {
-  AppMenuItems items;
-  extensions::AppWindowRegistry* app_window_registry =
-      extensions::AppWindowRegistry::Get(
-          ChromeLauncherController::instance()->profile());
-
-  int command_id = -1;
-  for (const ui::BaseWindow* window : windows()) {
-    ++command_id;
-    auto* native_window = window->GetNativeWindow();
-    if (!filter_predicate.is_null() && !filter_predicate.Run(native_window))
-      continue;
-
-    extensions::AppWindow* app_window =
-        app_window_registry->GetAppWindowForNativeWindow(native_window);
-    DCHECK(app_window);
-
-    // Use the app's web contents favicon, or the app window's icon.
-    favicon::FaviconDriver* favicon_driver =
-        favicon::ContentFaviconDriver::FromWebContents(
-            app_window->web_contents());
-    gfx::ImageSkia image = favicon_driver->GetFavicon().AsImageSkia();
-    if (image.isNull()) {
-      const gfx::ImageSkia* app_icon = nullptr;
-      if (app_window->GetNativeWindow()) {
-        app_icon = app_window->GetNativeWindow()->GetProperty(
-            aura::client::kAppIconKey);
-      }
-      if (app_icon && !app_icon->isNull())
-        image = *app_icon;
-    }
-
-    items.push_back({command_id, app_window->GetTitle(), image});
-  }
-  return items;
-}
-
-void ExtensionAppWindowLauncherItemController::ExecuteCommand(
-    bool from_context_menu,
-    int64_t command_id,
-    int32_t event_flags,
-    int64_t display_id) {
-  if (from_context_menu && ExecuteContextMenuCommand(command_id, event_flags))
-    return;
-
-  ChromeLauncherController::instance()->ActivateShellApp(app_id(), command_id);
-}
-
-void ExtensionAppWindowLauncherItemController::OnWindowTitleChanged(
-    aura::Window* window) {
-  ui::BaseWindow* base_window = GetAppWindow(window, true /*include_hidden*/);
-  extensions::AppWindowRegistry* app_window_registry =
-      extensions::AppWindowRegistry::Get(
-          ChromeLauncherController::instance()->profile());
-  extensions::AppWindow* app_window =
-      app_window_registry->GetAppWindowForNativeWindow(
-          base_window->GetNativeWindow());
-
-  // Use the window title (if set) to differentiate show_in_shelf window shelf
-  // items instead of the default behavior of using the app name.
-  if (app_window->show_in_shelf()) {
-    base::string16 title = window->GetTitle();
-    if (!title.empty())
-      ChromeLauncherController::instance()->SetItemTitle(shelf_id(), title);
-  }
-}
diff --git a/chrome/browser/ui/ash/launcher/extension_app_window_launcher_item_controller.h b/chrome/browser/ui/ash/launcher/extension_app_window_launcher_item_controller.h
deleted file mode 100644
index a6904f7..0000000
--- a/chrome/browser/ui/ash/launcher/extension_app_window_launcher_item_controller.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 CHROME_BROWSER_UI_ASH_LAUNCHER_EXTENSION_APP_WINDOW_LAUNCHER_ITEM_CONTROLLER_H_
-#define CHROME_BROWSER_UI_ASH_LAUNCHER_EXTENSION_APP_WINDOW_LAUNCHER_ITEM_CONTROLLER_H_
-
-#include <string>
-
-#include "base/macros.h"
-#include "chrome/browser/ui/ash/launcher/app_window_launcher_item_controller.h"
-
-namespace extensions {
-class AppWindow;
-}
-
-// Shelf item delegate for extension app windows.
-class ExtensionAppWindowLauncherItemController
-    : public AppWindowLauncherItemController {
- public:
-  explicit ExtensionAppWindowLauncherItemController(
-      const ash::ShelfID& shelf_id);
-
-  ~ExtensionAppWindowLauncherItemController() override;
-
-  void AddAppWindow(extensions::AppWindow* app_window);
-
-  // aura::WindowObserver overrides:
-  void OnWindowTitleChanged(aura::Window* window) override;
-
-  // AppWindowLauncherItemController:
-  AppMenuItems GetAppMenuItems(
-      int event_flags,
-      const ItemFilterPredicate& filter_predicate) override;
-  void ExecuteCommand(bool from_context_menu,
-                      int64_t command_id,
-                      int32_t event_flags,
-                      int64_t display_id) override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ExtensionAppWindowLauncherItemController);
-};
-
-#endif  // CHROME_BROWSER_UI_ASH_LAUNCHER_EXTENSION_APP_WINDOW_LAUNCHER_ITEM_CONTROLLER_H_
diff --git a/chrome/browser/ui/ash/launcher/internal_app_shelf_context_menu.cc b/chrome/browser/ui/ash/launcher/internal_app_shelf_context_menu.cc
deleted file mode 100644
index 23da503..0000000
--- a/chrome/browser/ui/ash/launcher/internal_app_shelf_context_menu.cc
+++ /dev/null
@@ -1,65 +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 "chrome/browser/ui/ash/launcher/internal_app_shelf_context_menu.h"
-
-#include <memory>
-#include <utility>
-
-#include "ash/public/cpp/app_menu_constants.h"
-#include "ash/public/cpp/shelf_item.h"
-#include "chrome/browser/apps/app_service/app_service_metrics.h"
-#include "chrome/browser/chromeos/plugin_vm/plugin_vm_manager.h"
-#include "chrome/browser/chromeos/plugin_vm/plugin_vm_manager_factory.h"
-#include "chrome/browser/chromeos/plugin_vm/plugin_vm_util.h"
-#include "chrome/browser/ui/app_list/internal_app/internal_app_metadata.h"
-#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
-#include "chrome/grit/generated_resources.h"
-
-InternalAppShelfContextMenu::InternalAppShelfContextMenu(
-    ChromeLauncherController* controller,
-    const ash::ShelfItem* item,
-    int64_t display_id)
-    : ShelfContextMenu(controller, item, display_id) {}
-
-void InternalAppShelfContextMenu::GetMenuModel(GetMenuModelCallback callback) {
-  auto menu_model = GetBaseMenuModel();
-  const bool app_is_open = controller()->IsOpen(item().id);
-  if (!app_is_open) {
-    AddContextMenuOption(menu_model.get(), ash::MENU_OPEN_NEW,
-                         IDS_APP_CONTEXT_MENU_ACTIVATE_ARC);
-  }
-
-  const auto* internal_app = app_list::FindInternalApp(item().id.app_id);
-  DCHECK(internal_app);
-  if (internal_app->show_in_launcher)
-    AddPinMenu(menu_model.get());
-
-  if (app_is_open) {
-    AddContextMenuOption(menu_model.get(), ash::MENU_CLOSE,
-                         IDS_SHELF_CONTEXT_MENU_CLOSE);
-
-    if (internal_app->internal_app_name == apps::BuiltInAppName::kPluginVm &&
-        plugin_vm::IsPluginVmRunning(controller()->profile())) {
-      AddContextMenuOption(menu_model.get(), ash::SHUTDOWN_GUEST_OS,
-                           IDS_PLUGIN_VM_SHUT_DOWN_MENU_ITEM);
-    }
-  }
-  std::move(callback).Run(std::move(menu_model));
-}
-
-void InternalAppShelfContextMenu::ExecuteCommand(int command_id,
-                                                 int event_flags) {
-  if (ExecuteCommonCommand(command_id, event_flags))
-    return;
-
-  const auto* internal_app = app_list::FindInternalApp(item().id.app_id);
-  DCHECK(internal_app);
-  DCHECK_EQ(internal_app->internal_app_name, apps::BuiltInAppName::kPluginVm);
-  if (command_id == ash::SHUTDOWN_GUEST_OS) {
-    plugin_vm::PluginVmManagerFactory::GetForProfile(controller()->profile())
-        ->StopPluginVm(plugin_vm::kPluginVmName, /*force=*/false);
-    return;
-  }
-}
diff --git a/chrome/browser/ui/ash/launcher/internal_app_shelf_context_menu.h b/chrome/browser/ui/ash/launcher/internal_app_shelf_context_menu.h
deleted file mode 100644
index 2de832a..0000000
--- a/chrome/browser/ui/ash/launcher/internal_app_shelf_context_menu.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_ASH_LAUNCHER_INTERNAL_APP_SHELF_CONTEXT_MENU_H_
-#define CHROME_BROWSER_UI_ASH_LAUNCHER_INTERNAL_APP_SHELF_CONTEXT_MENU_H_
-
-#include "base/macros.h"
-#include "chrome/browser/ui/ash/launcher/shelf_context_menu.h"
-
-// Class for context menu which is shown for internal app in the shelf.
-class InternalAppShelfContextMenu : public ShelfContextMenu {
- public:
-  InternalAppShelfContextMenu(ChromeLauncherController* controller,
-                              const ash::ShelfItem* item,
-                              int64_t display_id);
-  ~InternalAppShelfContextMenu() override = default;
-
-  // ShelfContextMenu:
-  void GetMenuModel(GetMenuModelCallback callback) override;
-  void ExecuteCommand(int command_id, int event_flags) override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(InternalAppShelfContextMenu);
-};
-
-#endif  // CHROME_BROWSER_UI_ASH_LAUNCHER_INTERNAL_APP_SHELF_CONTEXT_MENU_H_
diff --git a/chrome/browser/ui/ash/launcher/internal_app_window_shelf_controller.cc b/chrome/browser/ui/ash/launcher/internal_app_window_shelf_controller.cc
deleted file mode 100644
index 5e837bd..0000000
--- a/chrome/browser/ui/ash/launcher/internal_app_window_shelf_controller.cc
+++ /dev/null
@@ -1,251 +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 "chrome/browser/ui/ash/launcher/internal_app_window_shelf_controller.h"
-
-#include "ash/public/cpp/app_list/internal_app_id_constants.h"
-#include "ash/public/cpp/multi_user_window_manager.h"
-#include "ash/public/cpp/shelf_model.h"
-#include "ash/public/cpp/window_properties.h"
-#include "base/stl_util.h"
-#include "base/strings/utf_string_conversions.h"
-#include "chrome/browser/chromeos/plugin_vm/plugin_vm_util.h"
-#include "chrome/browser/ui/app_list/internal_app/internal_app_metadata.h"
-#include "chrome/browser/ui/ash/launcher/app_window_base.h"
-#include "chrome/browser/ui/ash/launcher/app_window_launcher_item_controller.h"
-#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
-#include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_helper.h"
-#include "components/exo/shell_surface_util.h"
-#include "components/user_manager/user_manager.h"
-#include "extensions/common/constants.h"
-#include "ui/aura/client/aura_constants.h"
-#include "ui/aura/env.h"
-#include "ui/aura/window.h"
-#include "ui/base/base_window.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/base/ui_base_features.h"
-#include "ui/views/widget/widget.h"
-
-InternalAppWindowShelfController::InternalAppWindowShelfController(
-    ChromeLauncherController* owner)
-    : AppWindowLauncherController(owner) {
-  aura::Env::GetInstance()->AddObserver(this);
-}
-
-InternalAppWindowShelfController::~InternalAppWindowShelfController() {
-  for (auto* window : observed_windows_)
-    window->RemoveObserver(this);
-  aura::Env::GetInstance()->RemoveObserver(this);
-}
-
-void InternalAppWindowShelfController::ActiveUserChanged(
-    const std::string& user_email) {
-  for (const auto& w : aura_window_to_app_window_) {
-    AppWindowBase* app_window = w.second.get();
-    if (app_window->shelf_id().app_id ==
-        ash::kInternalAppIdKeyboardShortcutViewer) {
-      continue;
-    }
-
-    if (MultiUserWindowManagerHelper::GetWindowManager()
-            ->GetWindowOwner(w.first)
-            .GetUserEmail() == user_email) {
-      AddToShelf(app_window);
-    } else {
-      RemoveFromShelf(app_window);
-    }
-  }
-}
-
-void InternalAppWindowShelfController::OnWindowInitialized(
-    aura::Window* window) {
-  // An internal app window has type WINDOW_TYPE_NORMAL, a WindowDelegate and
-  // is a top level views widget.
-  if (window->type() != aura::client::WINDOW_TYPE_NORMAL || !window->delegate())
-    return;
-  views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window);
-  if (!widget || !widget->is_top_level())
-    return;
-
-  observed_windows_.push_back(window);
-  window->AddObserver(this);
-}
-
-void InternalAppWindowShelfController::OnWindowPropertyChanged(
-    aura::Window* window,
-    const void* key,
-    intptr_t old) {
-  if (key != ash::kShelfIDKey)
-    return;
-
-  ash::ShelfID shelf_id =
-      ash::ShelfID::Deserialize(window->GetProperty(ash::kShelfIDKey));
-  if (!shelf_id.IsNull() && app_list::IsInternalApp(shelf_id.app_id))
-    RegisterAppWindow(window, shelf_id);
-}
-
-void InternalAppWindowShelfController::OnWindowVisibilityChanging(
-    aura::Window* window,
-    bool visible) {
-  if (!visible)
-    return;
-
-  // Skip OnWindowVisibilityChanged for ancestors/descendants.
-  if (!base::Contains(observed_windows_, window))
-    return;
-
-  ash::ShelfID shelf_id =
-      ash::ShelfID::Deserialize(window->GetProperty(ash::kShelfIDKey));
-
-  if (shelf_id.IsNull()) {
-    if (!plugin_vm::IsPluginVmAppWindow(window))
-      return;
-    shelf_id = ash::ShelfID(plugin_vm::kPluginVmShelfAppId);
-    window->SetProperty(ash::kShelfIDKey,
-                        new std::string(shelf_id.Serialize()));
-    window->SetProperty(ash::kAppIDKey, new std::string(shelf_id.app_id));
-  }
-
-  if (!app_list::IsInternalApp(shelf_id.app_id))
-    return;
-
-  RegisterAppWindow(window, shelf_id);
-}
-
-void InternalAppWindowShelfController::OnWindowDestroying(
-    aura::Window* window) {
-  auto it =
-      std::find(observed_windows_.begin(), observed_windows_.end(), window);
-  DCHECK(it != observed_windows_.end());
-  observed_windows_.erase(it);
-  window->RemoveObserver(this);
-
-  auto app_window_it = aura_window_to_app_window_.find(window);
-  if (app_window_it == aura_window_to_app_window_.end())
-    return;
-
-  RemoveFromShelf(app_window_it->second.get());
-
-  aura_window_to_app_window_.erase(app_window_it);
-}
-
-void InternalAppWindowShelfController::RegisterAppWindow(
-    aura::Window* window,
-    const ash::ShelfID& shelf_id) {
-  // Skip when this window has been handled. This can happen when the window
-  // becomes visible again.
-  auto app_window_it = aura_window_to_app_window_.find(window);
-  if (app_window_it != aura_window_to_app_window_.end())
-    return;
-
-  // Prevent InternalAppWindow from showing up after user switch.
-  // Keyboard Shortcut Viewer has a global instance so it can be shared with
-  // different users.
-  const user_manager::User* window_owner = nullptr;
-  if (shelf_id.app_id != ash::kInternalAppIdKeyboardShortcutViewer) {
-    // Plugin VM is only allowed on the primary profile. If the user switches
-    // profile while it is launching, we associate it with the primary profile,
-    // which hides the window, and don't show it on the shelf.
-    if (shelf_id.app_id == plugin_vm::kPluginVmShelfAppId)
-      window_owner = user_manager::UserManager::Get()->GetPrimaryUser();
-    else
-      window_owner = user_manager::UserManager::Get()->GetActiveUser();
-
-    MultiUserWindowManagerHelper::GetWindowManager()->SetWindowOwner(
-        window, window_owner->GetAccountId());
-  }
-
-  views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window);
-  auto app_window_ptr = std::make_unique<AppWindowBase>(shelf_id, widget);
-  AppWindowBase* app_window = app_window_ptr.get();
-  aura_window_to_app_window_[window] = std::move(app_window_ptr);
-
-  if (window_owner &&
-      window_owner != user_manager::UserManager::Get()->GetActiveUser()) {
-    return;
-  }
-
-  AddToShelf(app_window);
-}
-
-void InternalAppWindowShelfController::AddToShelf(AppWindowBase* app_window) {
-  const ash::ShelfID shelf_id = app_window->shelf_id();
-
-  AppWindowLauncherItemController* item_controller =
-      owner()->shelf_model()->GetAppWindowLauncherItemController(shelf_id);
-  if (item_controller == nullptr) {
-    auto controller =
-        std::make_unique<AppWindowLauncherItemController>(shelf_id);
-    item_controller = controller.get();
-    if (!owner()->GetItem(shelf_id)) {
-      owner()->CreateAppLauncherItem(std::move(controller),
-                                     ash::STATUS_RUNNING);
-    } else {
-      owner()->shelf_model()->SetShelfItemDelegate(shelf_id,
-                                                   std::move(controller));
-      owner()->SetItemStatus(shelf_id, ash::STATUS_RUNNING);
-    }
-  }
-
-  item_controller->AddWindow(app_window);
-  app_window->SetController(item_controller);
-}
-
-void InternalAppWindowShelfController::RemoveFromShelf(
-    AppWindowBase* app_window) {
-  const ash::ShelfID shelf_id = app_window->shelf_id();
-
-  UnregisterAppWindow(app_window);
-
-  // Check if we may close controller now, at this point we can safely remove
-  // controllers without window.
-  AppWindowLauncherItemController* item_controller =
-      owner()->shelf_model()->GetAppWindowLauncherItemController(
-          app_window->shelf_id());
-
-  if (item_controller && item_controller->window_count() == 0)
-    owner()->CloseLauncherItem(item_controller->shelf_id());
-}
-
-void InternalAppWindowShelfController::UnregisterAppWindow(
-    AppWindowBase* app_window) {
-  if (!app_window)
-    return;
-
-  AppWindowLauncherItemController* controller = app_window->controller();
-  if (controller)
-    controller->RemoveWindow(app_window);
-
-  app_window->SetController(nullptr);
-}
-
-AppWindowLauncherItemController*
-InternalAppWindowShelfController::ControllerForWindow(aura::Window* window) {
-  if (!window)
-    return nullptr;
-
-  auto app_window_it = aura_window_to_app_window_.find(window);
-  if (app_window_it == aura_window_to_app_window_.end())
-    return nullptr;
-
-  AppWindowBase* app_window = app_window_it->second.get();
-  if (app_window == nullptr)
-    return nullptr;
-
-  return app_window->controller();
-}
-
-void InternalAppWindowShelfController::OnItemDelegateDiscarded(
-    ash::ShelfItemDelegate* delegate) {
-  for (auto& it : aura_window_to_app_window_) {
-    AppWindowBase* app_window = it.second.get();
-    if (!app_window || app_window->controller() != delegate)
-      continue;
-
-    VLOG(1) << "Item controller was released externally for the app "
-            << delegate->shelf_id().app_id << ".";
-
-    UnregisterAppWindow(it.second.get());
-  }
-}
diff --git a/chrome/browser/ui/ash/launcher/internal_app_window_shelf_controller.h b/chrome/browser/ui/ash/launcher/internal_app_window_shelf_controller.h
deleted file mode 100644
index 7bc3716..0000000
--- a/chrome/browser/ui/ash/launcher/internal_app_window_shelf_controller.h
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_ASH_LAUNCHER_INTERNAL_APP_WINDOW_SHELF_CONTROLLER_H_
-#define CHROME_BROWSER_UI_ASH_LAUNCHER_INTERNAL_APP_WINDOW_SHELF_CONTROLLER_H_
-
-#include <map>
-#include <memory>
-#include <vector>
-
-#include "ash/public/cpp/shelf_types.h"
-#include "base/macros.h"
-#include "chrome/browser/ui/ash/launcher/app_window_launcher_controller.h"
-#include "chrome/browser/ui/settings_window_manager_observer_chromeos.h"
-#include "ui/aura/env_observer.h"
-#include "ui/aura/window_observer.h"
-#include "ui/aura/window_tracker.h"
-
-namespace aura {
-class Window;
-}
-
-class AppWindowBase;
-class ChromeLauncherController;
-
-// A controller to manage internal app shelf items.
-class InternalAppWindowShelfController : public AppWindowLauncherController,
-                                         public aura::EnvObserver,
-                                         public aura::WindowObserver {
- public:
-  explicit InternalAppWindowShelfController(ChromeLauncherController* owner);
-  ~InternalAppWindowShelfController() override;
-
-  // AppWindowLauncherController:
-  void ActiveUserChanged(const std::string& user_email) override;
-
-  // aura::EnvObserver:
-  void OnWindowInitialized(aura::Window* window) override;
-
-  // aura::WindowObserver:
-  void OnWindowPropertyChanged(aura::Window* window,
-                               const void* key,
-                               intptr_t old) override;
-  void OnWindowVisibilityChanging(aura::Window* window, bool visible) override;
-  void OnWindowDestroying(aura::Window* window) override;
-
-  // Creates an AppWindow and updates its AppWindowLauncherItemController by
-  // |window| and |shelf_id|.
-  void RegisterAppWindow(aura::Window* window, const ash::ShelfID& shelf_id);
-
- private:
-  using AuraWindowToAppWindow =
-      std::map<aura::Window*, std::unique_ptr<AppWindowBase>>;
-
-  void AddToShelf(AppWindowBase* app_window);
-  void RemoveFromShelf(AppWindowBase* app_window);
-
-  // Removes an AppWindow from its AppWindowLauncherItemController.
-  void UnregisterAppWindow(AppWindowBase* app_window);
-
-  // AppWindowLauncherController:
-  AppWindowLauncherItemController* ControllerForWindow(
-      aura::Window* window) override;
-  void OnItemDelegateDiscarded(ash::ShelfItemDelegate* delegate) override;
-
-  AuraWindowToAppWindow aura_window_to_app_window_;
-  std::vector<aura::Window*> observed_windows_;
-
-  DISALLOW_COPY_AND_ASSIGN(InternalAppWindowShelfController);
-};
-
-#endif  // CHROME_BROWSER_UI_ASH_LAUNCHER_INTERNAL_APP_WINDOW_SHELF_CONTROLLER_H_
diff --git a/chrome/browser/ui/ash/launcher/multi_profile_app_window_launcher_controller.cc b/chrome/browser/ui/ash/launcher/multi_profile_app_window_launcher_controller.cc
deleted file mode 100644
index 0ea2080..0000000
--- a/chrome/browser/ui/ash/launcher/multi_profile_app_window_launcher_controller.cc
+++ /dev/null
@@ -1,146 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/ash/launcher/multi_profile_app_window_launcher_controller.h"
-
-#include "ash/public/cpp/multi_user_window_manager.h"
-#include "ash/public/cpp/shelf_types.h"
-#include "ash/public/cpp/window_properties.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
-#include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
-#include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_helper.h"
-#include "components/account_id/account_id.h"
-#include "extensions/browser/app_window/app_window.h"
-#include "extensions/browser/app_window/native_app_window.h"
-#include "ui/aura/window.h"
-
-MultiProfileAppWindowLauncherController::
-    MultiProfileAppWindowLauncherController(ChromeLauncherController* owner)
-    : ExtensionAppWindowLauncherController(owner) {
-  // We might have already active windows.
-  extensions::AppWindowRegistry* registry =
-      extensions::AppWindowRegistry::Get(owner->profile());
-  app_window_list_.insert(app_window_list_.end(),
-                          registry->app_windows().begin(),
-                          registry->app_windows().end());
-}
-
-MultiProfileAppWindowLauncherController::
-    ~MultiProfileAppWindowLauncherController() {
-  // We need to remove all Registry observers for added users.
-  for (extensions::AppWindowRegistry* registry : multi_user_registry_)
-    registry->RemoveObserver(this);
-}
-
-void MultiProfileAppWindowLauncherController::ActiveUserChanged(
-    const std::string& user_email) {
-  // The active user has changed and we need to traverse our list of items to
-  // show / hide them one by one. To avoid that a user dependent state
-  // "survives" in a launcher item, we first delete all items making sure that
-  // nothing remains and then re-create them again.
-  for (extensions::AppWindow* app_window : app_window_list_) {
-    Profile* profile =
-        Profile::FromBrowserContext(app_window->browser_context());
-    if (!multi_user_util::IsProfileFromActiveUser(profile)) {
-      if (IsRegisteredApp(app_window->GetNativeWindow()))
-        UnregisterApp(app_window->GetNativeWindow());
-    }
-  }
-  for (extensions::AppWindow* app_window : app_window_list_) {
-    Profile* profile =
-        Profile::FromBrowserContext(app_window->browser_context());
-    if (multi_user_util::IsProfileFromActiveUser(profile) &&
-        !IsRegisteredApp(app_window->GetNativeWindow()) &&
-        (app_window->GetBaseWindow()->IsMinimized() ||
-         app_window->GetNativeWindow()->IsVisible())) {
-      RegisterApp(app_window);
-    }
-  }
-}
-
-void MultiProfileAppWindowLauncherController::AdditionalUserAddedToSession(
-    Profile* profile) {
-  // Each users AppWindowRegistry needs to be observed.
-  extensions::AppWindowRegistry* registry =
-      extensions::AppWindowRegistry::Get(profile);
-  DCHECK(registry->app_windows().empty());
-  multi_user_registry_.push_back(registry);
-  registry->AddObserver(this);
-}
-
-void MultiProfileAppWindowLauncherController::OnAppWindowAdded(
-    extensions::AppWindow* app_window) {
-  app_window_list_.push_back(app_window);
-  Profile* profile = Profile::FromBrowserContext(app_window->browser_context());
-  // If the window was created for an inactive user, but the user allowed the
-  // app to teleport to the current user's desktop, teleport this window now.
-  if (!multi_user_util::IsProfileFromActiveUser(profile) &&
-      UserHasAppOnActiveDesktop(app_window)) {
-    MultiUserWindowManagerHelper::GetWindowManager()->ShowWindowForUser(
-        app_window->GetNativeWindow(), multi_user_util::GetCurrentAccountId());
-  }
-
-  // If the window was created for the active user or it has been teleported to
-  // the current user's desktop, register it to show an item on the shelf.
-  if (multi_user_util::IsProfileFromActiveUser(profile) ||
-      UserHasAppOnActiveDesktop(app_window)) {
-    RegisterApp(app_window);
-  }
-}
-
-void MultiProfileAppWindowLauncherController::OnAppWindowShown(
-    extensions::AppWindow* app_window,
-    bool was_hidden) {
-  Profile* profile = Profile::FromBrowserContext(app_window->browser_context());
-
-  if (multi_user_util::IsProfileFromActiveUser(profile) &&
-      !IsRegisteredApp(app_window->GetNativeWindow())) {
-    RegisterApp(app_window);
-    return;
-  }
-}
-
-void MultiProfileAppWindowLauncherController::OnAppWindowHidden(
-    extensions::AppWindow* app_window) {
-  Profile* profile = Profile::FromBrowserContext(app_window->browser_context());
-  if (multi_user_util::IsProfileFromActiveUser(profile) &&
-      IsRegisteredApp(app_window->GetNativeWindow())) {
-    UnregisterApp(app_window->GetNativeWindow());
-  }
-}
-
-void MultiProfileAppWindowLauncherController::OnAppWindowRemoved(
-    extensions::AppWindow* app_window) {
-  // If the application is registered with AppWindowLauncher (because the user
-  // is currently active), the OnWindowDestroying observer has already (or will
-  // soon) unregister it independently from the shelf. If it was not registered
-  // we don't need to do anything anyways. As such, all which is left to do here
-  // is to get rid of our own reference.
-  AppWindowList::iterator it =
-      std::find(app_window_list_.begin(), app_window_list_.end(), app_window);
-  DCHECK(it != app_window_list_.end());
-  app_window_list_.erase(it);
-}
-
-bool MultiProfileAppWindowLauncherController::UserHasAppOnActiveDesktop(
-    extensions::AppWindow* app_window) {
-  const std::string& app_id = app_window->extension_id();
-  content::BrowserContext* app_context = app_window->browser_context();
-  DCHECK(!app_context->IsOffTheRecord());
-  const AccountId current_account_id = multi_user_util::GetCurrentAccountId();
-  MultiUserWindowManagerHelper* helper =
-      MultiUserWindowManagerHelper::GetInstance();
-  for (extensions::AppWindow* other_window : app_window_list_) {
-    DCHECK(!other_window->browser_context()->IsOffTheRecord());
-    if (helper->IsWindowOnDesktopOfUser(other_window->GetNativeWindow(),
-                                        current_account_id) &&
-        app_id == other_window->extension_id() &&
-        app_context == other_window->browser_context()) {
-      return true;
-    }
-  }
-  return false;
-}
diff --git a/chrome/browser/ui/ash/launcher/multi_profile_app_window_launcher_controller.h b/chrome/browser/ui/ash/launcher/multi_profile_app_window_launcher_controller.h
deleted file mode 100644
index e910158d..0000000
--- a/chrome/browser/ui/ash/launcher/multi_profile_app_window_launcher_controller.h
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_ASH_LAUNCHER_MULTI_PROFILE_APP_WINDOW_LAUNCHER_CONTROLLER_H_
-#define CHROME_BROWSER_UI_ASH_LAUNCHER_MULTI_PROFILE_APP_WINDOW_LAUNCHER_CONTROLLER_H_
-
-#include "base/macros.h"
-#include "chrome/browser/ui/ash/launcher/extension_app_window_launcher_controller.h"
-
-// Inherits from AppWindowLauncherController and overwrites the AppWindow
-// observing functions to switch between users dynamically.
-class MultiProfileAppWindowLauncherController
-    : public ExtensionAppWindowLauncherController {
- public:
-  explicit MultiProfileAppWindowLauncherController(
-      ChromeLauncherController* owner);
-  ~MultiProfileAppWindowLauncherController() override;
-
-  // Overridden from AppWindowLauncherController:
-  void ActiveUserChanged(const std::string& user_email) override;
-  void AdditionalUserAddedToSession(Profile* profile) override;
-
-  // Overridden from AppWindowRegistry::Observer:
-  void OnAppWindowAdded(extensions::AppWindow* app_window) override;
-  void OnAppWindowRemoved(extensions::AppWindow* app_window) override;
-  void OnAppWindowShown(extensions::AppWindow* app_window,
-                        bool was_hidden) override;
-  void OnAppWindowHidden(extensions::AppWindow* app_window) override;
-
- private:
-  typedef std::vector<extensions::AppWindow*> AppWindowList;
-  typedef std::vector<extensions::AppWindowRegistry*> AppWindowRegistryList;
-
-  // Returns true if the owner of the given |app_window| has a window teleported
-  // of the |app_window|'s application type to the current desktop.
-  bool UserHasAppOnActiveDesktop(extensions::AppWindow* app_window);
-
-  // A list of all app windows for all users.
-  AppWindowList app_window_list_;
-
-  // A list of the app window registries which we additionally observe.
-  AppWindowRegistryList multi_user_registry_;
-
-  DISALLOW_COPY_AND_ASSIGN(MultiProfileAppWindowLauncherController);
-};
-
-#endif  // CHROME_BROWSER_UI_ASH_LAUNCHER_MULTI_PROFILE_APP_WINDOW_LAUNCHER_CONTROLLER_H_
diff --git a/chrome/browser/ui/ash/launcher/shelf_context_menu.cc b/chrome/browser/ui/ash/launcher/shelf_context_menu.cc
index 7a40e43..aad2a34 100644
--- a/chrome/browser/ui/ash/launcher/shelf_context_menu.cc
+++ b/chrome/browser/ui/ash/launcher/shelf_context_menu.cc
@@ -25,7 +25,6 @@
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_util.h"
 #include "chrome/browser/ui/ash/launcher/extension_shelf_context_menu.h"
 #include "chrome/browser/ui/ash/launcher/extension_uninstaller.h"
-#include "chrome/browser/ui/ash/launcher/internal_app_shelf_context_menu.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/vector_icons/vector_icons.h"
diff --git a/chrome/browser/ui/ash/launcher/shelf_context_menu_unittest.cc b/chrome/browser/ui/ash/launcher/shelf_context_menu_unittest.cc
index fbd2cf9..c31b8f2 100644
--- a/chrome/browser/ui/ash/launcher/shelf_context_menu_unittest.cc
+++ b/chrome/browser/ui/ash/launcher/shelf_context_menu_unittest.cc
@@ -39,7 +39,6 @@
 #include "chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.h"
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
 #include "chrome/browser/ui/ash/launcher/extension_shelf_context_menu.h"
-#include "chrome/browser/ui/ash/launcher/internal_app_shelf_context_menu.h"
 #include "chrome/browser/web_applications/components/web_app_provider_base.h"
 #include "chrome/browser/web_applications/test/test_system_web_app_manager.h"
 #include "chrome/browser/web_applications/test/test_web_app_provider.h"
diff --git a/chrome/browser/ui/aura/accessibility/automation_manager_aura.cc b/chrome/browser/ui/aura/accessibility/automation_manager_aura.cc
index efa01b1..6479fbe 100644
--- a/chrome/browser/ui/aura/accessibility/automation_manager_aura.cc
+++ b/chrome/browser/ui/aura/accessibility/automation_manager_aura.cc
@@ -12,6 +12,7 @@
 #include "components/crash/core/common/crash_key.h"
 #include "ui/accessibility/aura/aura_window_properties.h"
 #include "ui/accessibility/ax_action_data.h"
+#include "ui/accessibility/ax_action_handler_base.h"
 #include "ui/accessibility/ax_enum_util.h"
 #include "ui/accessibility/ax_enums.mojom.h"
 #include "ui/accessibility/ax_event.h"
@@ -257,10 +258,10 @@
     child_ax_tree_id = ui::AXTreeID::FromString(*child_ax_tree_id_ptr);
 
   // If the window has a child AX tree ID, forward the action to the
-  // associated AXActionHandler.
+  // associated AXActionHandlerBase.
   if (child_ax_tree_id != ui::AXTreeIDUnknown()) {
     ui::AXTreeIDRegistry* registry = ui::AXTreeIDRegistry::GetInstance();
-    ui::AXActionHandler* action_handler =
+    ui::AXActionHandlerBase* action_handler =
         registry->GetActionHandler(child_ax_tree_id);
     CHECK(action_handler);
 
diff --git a/chrome/browser/ui/aura/accessibility/automation_manager_aura.h b/chrome/browser/ui/aura/accessibility/automation_manager_aura.h
index 3e8f293..3c00e0c7 100644
--- a/chrome/browser/ui/aura/accessibility/automation_manager_aura.h
+++ b/chrome/browser/ui/aura/accessibility/automation_manager_aura.h
@@ -57,7 +57,7 @@
   // Handles a textual alert.
   void HandleAlert(const std::string& text);
 
-  // AXActionHandler implementation.
+  // AXActionHandlerBase implementation.
   void PerformAction(const ui::AXActionData& data) override;
 
   // views::AXAuraObjCache::Delegate implementation.
diff --git a/chrome/browser/ui/messages/android/BUILD.gn b/chrome/browser/ui/messages/android/BUILD.gn
index 1212f481..5324d4e7 100644
--- a/chrome/browser/ui/messages/android/BUILD.gn
+++ b/chrome/browser/ui/messages/android/BUILD.gn
@@ -27,6 +27,7 @@
     "java/src/org/chromium/chrome/browser/ui/messages/snackbar/Snackbar.java",
     "java/src/org/chromium/chrome/browser/ui/messages/snackbar/SnackbarCollection.java",
     "java/src/org/chromium/chrome/browser/ui/messages/snackbar/SnackbarManager.java",
+    "java/src/org/chromium/chrome/browser/ui/messages/snackbar/SnackbarManagerProvider.java",
     "java/src/org/chromium/chrome/browser/ui/messages/snackbar/SnackbarView.java",
   ]
 
diff --git a/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/SnackbarManager.java b/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/SnackbarManager.java
index 4ff86c1..2882777 100644
--- a/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/SnackbarManager.java
+++ b/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/SnackbarManager.java
@@ -16,6 +16,7 @@
 import org.chromium.base.ActivityState;
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.ApplicationStatus.ActivityStateListener;
+import org.chromium.base.UnownedUserData;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.chrome.browser.util.ChromeAccessibilityUtil;
 import org.chromium.ui.base.WindowAndroid;
@@ -30,7 +31,7 @@
  * {@link SnackbarController#onDismissNoAction(Object)}. Note, snackbars of
  * {@link Snackbar#TYPE_PERSISTENT} do not get automatically dismissed after a timeout.
  */
-public class SnackbarManager implements OnClickListener, ActivityStateListener {
+public class SnackbarManager implements OnClickListener, ActivityStateListener, UnownedUserData {
     /**
      * Interface that shows the ability to provide a snackbar manager.
      */
diff --git a/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/SnackbarManagerProvider.java b/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/SnackbarManagerProvider.java
new file mode 100644
index 0000000..d5bf763
--- /dev/null
+++ b/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/SnackbarManagerProvider.java
@@ -0,0 +1,48 @@
+// Copyright 2020 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.ui.messages.snackbar;
+
+import org.chromium.base.UnownedUserDataKey;
+import org.chromium.ui.base.WindowAndroid;
+
+/**
+ * A class responsible for binding and unbinding a {@link SnackbarManager} to a
+ * {@link WindowAndroid}.
+ */
+public class SnackbarManagerProvider {
+    /** The key for accessing this object on an {@link org.chromium.base.UnownedUserDataHost}. */
+    private static final UnownedUserDataKey<SnackbarManager> KEY =
+            new UnownedUserDataKey<>(SnackbarManager.class);
+
+    /**
+     * Get the activity's main {@link SnackbarManager} from the provided {@link WindowAndroid}.
+     * @param window The window to get the manager from.
+     * @return The activity's main {@link SnackbarManager}.
+     */
+    public static SnackbarManager from(WindowAndroid window) {
+        return KEY.retrieveDataFromHost(window.getUnownedUserDataHost());
+    }
+
+    /**
+     * WARNING: Do not use this unless you know what you're doing!
+     *
+     * Make a snackbar manager available through the activity's window.
+     * @param window A {@link WindowAndroid} to attach to.
+     * @param manager The {@link SnackbarManager} to attach.
+     */
+    public static void attach(WindowAndroid window, SnackbarManager manager) {
+        KEY.attachToHost(window.getUnownedUserDataHost(), manager);
+    }
+
+    /**
+     * WARNING: Do not use this unless you know what you're doing!
+     *
+     * Detach the provided snackbar manager from any host it is associated with.
+     * @param manager The {@link SnackbarManager} to detach.
+     */
+    public static void detach(SnackbarManager manager) {
+        KEY.detachFromAllHosts(manager);
+    }
+}
diff --git a/chrome/browser/ui/views/crostini/crostini_app_restart_view.cc b/chrome/browser/ui/views/crostini/crostini_app_restart_view.cc
index dfe2508..52c5ba2 100644
--- a/chrome/browser/ui/views/crostini/crostini_app_restart_view.cc
+++ b/chrome/browser/ui/views/crostini/crostini_app_restart_view.cc
@@ -7,7 +7,6 @@
 #include "chrome/browser/ui/ash/launcher/app_service/app_service_app_window_crostini_tracker.h"
 #include "chrome/browser/ui/ash/launcher/app_service/app_service_app_window_launcher_controller.h"
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
-#include "chrome/browser/ui/ash/launcher/crostini_app_window_shelf_controller.h"
 #include "chrome/browser/ui/browser_dialogs.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/common/chrome_features.h"
diff --git a/chrome/browser/ui/views/crostini/crostini_recovery_view.cc b/chrome/browser/ui/views/crostini/crostini_recovery_view.cc
index 072e1db..da071008 100644
--- a/chrome/browser/ui/views/crostini/crostini_recovery_view.cc
+++ b/chrome/browser/ui/views/crostini/crostini_recovery_view.cc
@@ -5,9 +5,11 @@
 #include "chrome/browser/ui/views/crostini/crostini_recovery_view.h"
 
 #include "base/metrics/histogram_functions.h"
+#include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/chromeos/crostini/crostini_features.h"
 #include "chrome/browser/chromeos/crostini/crostini_manager.h"
+#include "chrome/browser/chromeos/crostini/crostini_terminal.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser_dialogs.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
@@ -34,35 +36,31 @@
     crostini::CrostiniUISurface ui_surface,
     const std::string& app_id,
     int64_t display_id,
-    crostini::LaunchCrostiniAppCallback callback) {
-  bool allow_app_launch = CrostiniRecoveryView::Show(
-      profile, app_id, display_id, std::move(callback));
-  if (!allow_app_launch) {
-    // App launches are prevented by the view's can_launch_apps_. In this case,
-    // we want to sample the Show call.
-    base::UmaHistogramEnumeration(kCrostiniRecoverySourceHistogram, ui_surface,
-                                  crostini::CrostiniUISurface::kCount);
-  }
+    const std::vector<storage::FileSystemURL>& files,
+    crostini::CrostiniSuccessCallback callback) {
+  CrostiniRecoveryView::Show(profile, app_id, display_id, files,
+                             std::move(callback));
+  base::UmaHistogramEnumeration(kCrostiniRecoverySourceHistogram, ui_surface,
+                                crostini::CrostiniUISurface::kCount);
 }
 
-bool CrostiniRecoveryView::Show(Profile* profile,
-                                const std::string& app_id,
-                                int64_t display_id,
-                                crostini::LaunchCrostiniAppCallback callback) {
+void CrostiniRecoveryView::Show(
+    Profile* profile,
+    const std::string& app_id,
+    int64_t display_id,
+    const std::vector<storage::FileSystemURL>& files,
+    crostini::CrostiniSuccessCallback callback) {
   DCHECK(crostini::CrostiniFeatures::Get()->IsUIAllowed(profile));
-  if (!g_crostini_recovery_view) {
-    g_crostini_recovery_view = new CrostiniRecoveryView(profile);
+  // Any new apps launched during recovery are immediately cancelled.
+  if (g_crostini_recovery_view) {
+    std::move(callback).Run(false, "recovery in progress");
+  } else {
+    g_crostini_recovery_view = new CrostiniRecoveryView(
+        profile, app_id, display_id, files, std::move(callback));
     CreateDialogWidget(g_crostini_recovery_view, nullptr, nullptr);
   }
-  g_crostini_recovery_view->Reset(app_id, display_id, std::move(callback));
+  // Always call Show to bring the dialog to the front of the screen.
   g_crostini_recovery_view->GetWidget()->Show();
-  return g_crostini_recovery_view->can_launch_apps_;
-}
-
-bool CrostiniRecoveryView::IsDialogButtonEnabled(
-    ui::DialogButton button) const {
-  // Buttons are disabled after Accept or Cancel have been clicked.
-  return closed_reason_ == views::Widget::ClosedReason::kUnspecified;
 }
 
 gfx::Size CrostiniRecoveryView::CalculatePreferredSize() const {
@@ -73,56 +71,35 @@
 }
 
 bool CrostiniRecoveryView::Accept() {
-  closed_reason_ = views::Widget::ClosedReason::kAcceptButtonClicked;
-  if (can_launch_apps_) {
-    return true;
-  }
+  SetButtonEnabled(ui::DIALOG_BUTTON_OK, false);
+  SetButtonEnabled(ui::DIALOG_BUTTON_CANCEL, false);
   crostini::CrostiniManager::GetForProfile(profile_)->StopVm(
       crostini::kCrostiniDefaultVmName,
-      base::BindOnce(&CrostiniRecoveryView::ScheduleAppLaunch,
-                     weak_ptr_factory_.GetWeakPtr(), app_id_, display_id_,
-                     std::move(callback_)));
+      base::BindOnce(&CrostiniRecoveryView::OnStopVm,
+                     weak_ptr_factory_.GetWeakPtr()));
   DialogModelChanged();
   return false;
 }
 
-void CrostiniRecoveryView::ScheduleAppLaunch(
-    const std::string app_id,
-    int64_t display_id,
-    crostini::LaunchCrostiniAppCallback callback,
-    crostini::CrostiniResult result) {
-  VLOG(1) << "Scheduling app launch " << app_id;
+void CrostiniRecoveryView::OnStopVm(crostini::CrostiniResult result) {
+  VLOG(1) << "Scheduling app launch " << app_id_;
   if (result != crostini::CrostiniResult::SUCCESS) {
     LOG(ERROR) << "Error stopping VM for recovery: " << (int)result;
   }
   base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(&CrostiniRecoveryView::CompleteAppLaunch,
-                                weak_ptr_factory_.GetWeakPtr(), app_id,
-                                display_id, std::move(callback)));
-}
-
-void CrostiniRecoveryView::CompleteAppLaunch(
-    const std::string app_id,
-    int64_t display_id,
-    crostini::LaunchCrostiniAppCallback callback) {
-  can_launch_apps_ = true;
-  crostini::LaunchCrostiniApp(profile_, app_id, display_id, {},
-                              std::move(callback));
-  GetWidget()->CloseWithReason(closed_reason_);
+      FROM_HERE,
+      base::BindOnce(&crostini::LaunchCrostiniApp, profile_, app_id_,
+                     display_id_, std::move(files_), std::move(callback_)));
+  GetWidget()->CloseWithReason(
+      views::Widget::ClosedReason::kAcceptButtonClicked);
 }
 
 bool CrostiniRecoveryView::Cancel() {
-  closed_reason_ = views::Widget::ClosedReason::kCancelButtonClicked;
   if (callback_) {
     std::move(callback_).Run(false, "cancelled for recovery");
+    crostini::LaunchTerminal(profile_, display_id_);
   }
-  if (can_launch_apps_) {
-    return true;
-  }
-  ScheduleAppLaunch(crostini::kCrostiniTerminalSystemAppId, display_id_,
-                    base::DoNothing(), crostini::CrostiniResult::SUCCESS);
-  DialogModelChanged();
-  return false;
+  return true;
 }
 
 // static
@@ -130,8 +107,18 @@
   return g_crostini_recovery_view;
 }
 
-CrostiniRecoveryView::CrostiniRecoveryView(Profile* profile)
-    : profile_(profile), weak_ptr_factory_(this) {
+CrostiniRecoveryView::CrostiniRecoveryView(
+    Profile* profile,
+    const std::string& app_id,
+    int64_t display_id,
+    const std::vector<storage::FileSystemURL>& files,
+    crostini::CrostiniSuccessCallback callback)
+    : profile_(profile),
+      app_id_(app_id),
+      display_id_(display_id),
+      files_(files),
+      callback_(std::move(callback)),
+      weak_ptr_factory_(this) {
   SetButtons(ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL);
   SetButtonLabel(
       ui::DIALOG_BUTTON_OK,
@@ -158,14 +145,6 @@
   chrome::RecordDialogCreation(chrome::DialogIdentifier::CROSTINI_RECOVERY);
 }
 
-void CrostiniRecoveryView::Reset(const std::string app_id,
-                                 int64_t display_id,
-                                 crostini::LaunchCrostiniAppCallback callback) {
-  app_id_ = app_id;
-  display_id_ = display_id;
-  callback_ = std::move(callback);
-}
-
 CrostiniRecoveryView::~CrostiniRecoveryView() {
   g_crostini_recovery_view = nullptr;
 }
diff --git a/chrome/browser/ui/views/crostini/crostini_recovery_view.h b/chrome/browser/ui/views/crostini/crostini_recovery_view.h
index 156a57f4..ef45537 100644
--- a/chrome/browser/ui/views/crostini/crostini_recovery_view.h
+++ b/chrome/browser/ui/views/crostini/crostini_recovery_view.h
@@ -5,7 +5,8 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_CROSTINI_CROSTINI_RECOVERY_VIEW_H_
 #define CHROME_BROWSER_UI_VIEWS_CROSTINI_CROSTINI_RECOVERY_VIEW_H_
 
-#include "chrome/browser/chromeos/crostini/crostini_util.h"
+#include "chrome/browser/chromeos/crostini/crostini_simple_types.h"
+#include "storage/browser/file_system/file_system_url.h"
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
 
 namespace crostini {
@@ -18,41 +19,34 @@
 // connection is needed.
 class CrostiniRecoveryView : public views::BubbleDialogDelegateView {
  public:
-  static bool Show(Profile* profile,
+  static void Show(Profile* profile,
                    const std::string& app_id,
                    int64_t display_id,
-                   crostini::LaunchCrostiniAppCallback callback);
+                   const std::vector<storage::FileSystemURL>& files,
+                   crostini::CrostiniSuccessCallback callback);
 
   // views::DialogDelegateView:
   gfx::Size CalculatePreferredSize() const override;
   bool Accept() override;
   bool Cancel() override;
-  bool IsDialogButtonEnabled(ui::DialogButton button) const override;
 
   static CrostiniRecoveryView* GetActiveViewForTesting();
 
  private:
-  explicit CrostiniRecoveryView(Profile* profile);
+  CrostiniRecoveryView(Profile* profile,
+                       const std::string& app_id,
+                       int64_t display_id,
+                       const std::vector<storage::FileSystemURL>& files,
+                       crostini::CrostiniSuccessCallback callback);
   ~CrostiniRecoveryView() override;
 
-  void Reset(const std::string app_id,
-             int64_t display_id,
-             crostini::LaunchCrostiniAppCallback callback);
-  void ScheduleAppLaunch(const std::string app_id,
-                         int64_t display_id,
-                         crostini::LaunchCrostiniAppCallback callback,
-                         crostini::CrostiniResult result);
-  void CompleteAppLaunch(const std::string app_id,
-                         int64_t display_id,
-                         crostini::LaunchCrostiniAppCallback callback);
+  void OnStopVm(crostini::CrostiniResult result);
 
   Profile* profile_;  // Not owned.
   std::string app_id_;
   int64_t display_id_;
-  crostini::LaunchCrostiniAppCallback callback_;
-  bool can_launch_apps_ = false;
-  views::Widget::ClosedReason closed_reason_ =
-      views::Widget::ClosedReason::kUnspecified;
+  const std::vector<storage::FileSystemURL> files_;
+  crostini::CrostiniSuccessCallback callback_;
 
   base::WeakPtrFactory<CrostiniRecoveryView> weak_ptr_factory_;
 };
diff --git a/chrome/browser/ui/views/crostini/crostini_recovery_view_browsertest.cc b/chrome/browser/ui/views/crostini/crostini_recovery_view_browsertest.cc
index 8de34a9..3760172 100644
--- a/chrome/browser/ui/views/crostini/crostini_recovery_view_browsertest.cc
+++ b/chrome/browser/ui/views/crostini/crostini_recovery_view_browsertest.cc
@@ -16,6 +16,8 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/views/crostini/crostini_browser_test_util.h"
+#include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h"
+#include "chrome/browser/web_applications/web_app_provider.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/fake_concierge_client.h"
 #include "content/public/test/browser_test.h"
@@ -26,6 +28,16 @@
 constexpr char kDesktopFileId[] = "test_app";
 constexpr int kDisplayId = 0;
 
+namespace {
+
+void ExpectFailure(const std::string& expected_failure_reason,
+                   bool success,
+                   const std::string& failure_reason) {
+  EXPECT_FALSE(success);
+  EXPECT_EQ(expected_failure_reason, failure_reason);
+}
+}  // namespace
+
 class CrostiniRecoveryViewBrowserTest : public CrostiniDialogBrowserTest {
  public:
   CrostiniRecoveryViewBrowserTest()
@@ -39,7 +51,7 @@
   // DialogBrowserTest:
   void ShowUi(const std::string& name) override {
     ShowCrostiniRecoveryView(browser()->profile(), kUiSurface, app_id(),
-                             kDisplayId, base::DoNothing());
+                             kDisplayId, {}, base::DoNothing());
   }
 
   void SetUncleanStartup() {
@@ -51,10 +63,6 @@
     return CrostiniRecoveryView::GetActiveViewForTesting();
   }
 
-  bool HasAcceptButton() { return ActiveView()->GetOkButton() != nullptr; }
-
-  bool HasCancelButton() { return ActiveView()->GetCancelButton() != nullptr; }
-
   void WaitForViewDestroyed() {
     base::RunLoop().RunUntilIdle();
     ExpectNoView();
@@ -68,8 +76,10 @@
     EXPECT_EQ(ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL,
               ActiveView()->GetDialogButtons());
 
-    EXPECT_TRUE(HasAcceptButton());
-    EXPECT_TRUE(HasCancelButton());
+    EXPECT_NE(ActiveView()->GetOkButton(), nullptr);
+    EXPECT_NE(ActiveView()->GetCancelButton(), nullptr);
+    EXPECT_TRUE(ActiveView()->IsDialogButtonEnabled(ui::DIALOG_BUTTON_OK));
+    EXPECT_TRUE(ActiveView()->IsDialogButtonEnabled(ui::DIALOG_BUTTON_CANCEL));
   }
 
   void ExpectNoView() {
@@ -112,42 +122,48 @@
 
   crostini::LaunchCrostiniApp(browser()->profile(), app_id(), kDisplayId);
   ExpectNoView();
-}
-
-IN_PROC_BROWSER_TEST_F(CrostiniRecoveryViewBrowserTest, ShowsOnUncleanStart) {
-  base::HistogramTester histogram_tester;
-
-  SetUncleanStartup();
-  RegisterApp();
-
-  crostini::LaunchCrostiniApp(browser()->profile(), app_id(), kDisplayId);
-  ExpectView();
-
-  ActiveView()->CancelDialog();
-
-  WaitForViewDestroyed();
-  // Canceling means we aren't restarting the VM.
-  EXPECT_TRUE(IsUncleanStartup());
 
   histogram_tester.ExpectUniqueSample(
       "Crostini.RecoverySource",
-      static_cast<base::HistogramBase::Sample>(kUiSurface), 1);
+      static_cast<base::HistogramBase::Sample>(kUiSurface), 0);
 }
 
-IN_PROC_BROWSER_TEST_F(CrostiniRecoveryViewBrowserTest,
-                       ReshowViewIfRestartNotSelected) {
+IN_PROC_BROWSER_TEST_F(CrostiniRecoveryViewBrowserTest, Cancel) {
   base::HistogramTester histogram_tester;
 
   SetUncleanStartup();
   RegisterApp();
+  // Ensure Terminal System App is installed.
+  web_app::WebAppProvider::Get(browser()->profile())
+      ->system_web_app_manager()
+      .InstallSystemAppsForTesting();
 
-  crostini::LaunchCrostiniApp(browser()->profile(), app_id(), kDisplayId);
+  // First app should fail with 'cancelled for recovery'.
+  crostini::LaunchCrostiniApp(
+      browser()->profile(), app_id(), kDisplayId, {},
+      base::BindOnce(&ExpectFailure, "cancelled for recovery"));
   ExpectView();
 
+  // Apps launched while dialog is shown should fail with 'recovery in
+  // progress'.
+  crostini::LaunchCrostiniApp(
+      browser()->profile(), app_id(), kDisplayId, {},
+      base::BindOnce(&ExpectFailure, "recovery in progress"));
+
+  // Click 'Cancel'.
   ActiveView()->CancelDialog();
   WaitForViewDestroyed();
 
-  crostini::LaunchCrostiniApp(browser()->profile(), app_id(), kDisplayId);
+  // Terminal should launch after use clicks 'Cancel'.
+  Browser* terminal_browser = web_app::FindSystemWebAppBrowser(
+      browser()->profile(), web_app::SystemAppType::TERMINAL);
+  CHECK_NE(nullptr, terminal_browser);
+  WaitForLoadFinished(terminal_browser->tab_strip_model()->GetWebContentsAt(0));
+
+  // Any new apps launched should show the dialog again.
+  crostini::LaunchCrostiniApp(
+      browser()->profile(), app_id(), kDisplayId, {},
+      base::BindOnce(&ExpectFailure, "cancelled for recovery"));
   ExpectView();
 
   ActiveView()->CancelDialog();
@@ -157,10 +173,10 @@
 
   histogram_tester.ExpectUniqueSample(
       "Crostini.RecoverySource",
-      static_cast<base::HistogramBase::Sample>(kUiSurface), 2);
+      static_cast<base::HistogramBase::Sample>(kUiSurface), 3);
 }
 
-IN_PROC_BROWSER_TEST_F(CrostiniRecoveryViewBrowserTest, NoViewAfterRestart) {
+IN_PROC_BROWSER_TEST_F(CrostiniRecoveryViewBrowserTest, Accept) {
   base::HistogramTester histogram_tester;
 
   SetUncleanStartup();
@@ -169,16 +185,28 @@
   crostini::LaunchCrostiniApp(browser()->profile(), app_id(), kDisplayId);
   ExpectView();
 
+  // Apps launched while dialog is shown should fail with 'recovery in
+  // progress'.
+  crostini::LaunchCrostiniApp(
+      browser()->profile(), app_id(), kDisplayId, {},
+      base::BindOnce(&ExpectFailure, "recovery in progress"));
+
+  // Click 'Accept'.
   ActiveView()->AcceptDialog();
 
+  // Buttons should be disabled after clicking Accept.
+  EXPECT_FALSE(ActiveView()->IsDialogButtonEnabled(ui::DIALOG_BUTTON_OK));
+  EXPECT_FALSE(ActiveView()->IsDialogButtonEnabled(ui::DIALOG_BUTTON_CANCEL));
+
   WaitForViewDestroyed();
 
   EXPECT_FALSE(IsUncleanStartup());
 
+  // Apps now launch successfully.
   crostini::LaunchCrostiniApp(browser()->profile(), app_id(), kDisplayId);
   ExpectNoView();
 
   histogram_tester.ExpectUniqueSample(
       "Crostini.RecoverySource",
-      static_cast<base::HistogramBase::Sample>(kUiSurface), 1);
+      static_cast<base::HistogramBase::Sample>(kUiSurface), 2);
 }
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
index f980521..3fc54c1 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
@@ -44,6 +44,7 @@
 #include "components/omnibox/browser/omnibox_popup_model.h"
 #include "components/omnibox/browser/omnibox_prefs.h"
 #include "components/omnibox/common/omnibox_features.h"
+#include "components/prefs/pref_service.h"
 #include "components/security_state/core/security_state.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/url_formatter/elide_url.h"
@@ -2168,6 +2169,14 @@
   if (base::FeatureList::IsEnabled(omnibox::kOmniboxContextMenuShowFullUrls)) {
     menu_contents->AddCheckItemWithStringId(IDC_SHOW_FULL_URLS,
                                             IDS_CONTEXT_MENU_SHOW_FULL_URLS);
+
+    const PrefService::Preference* show_full_urls_pref =
+        location_bar_view_->profile()->GetPrefs()->FindPreference(
+            omnibox::kPreventUrlElisionsInOmnibox);
+    if (show_full_urls_pref->IsManaged()) {
+      menu_contents->SetEnabledAt(
+          menu_contents->GetIndexOfCommandId(IDC_SHOW_FULL_URLS), false);
+    }
   }
 }
 
diff --git a/chrome/browser/ui/views/sharesheet_bubble_view.cc b/chrome/browser/ui/views/sharesheet_bubble_view.cc
index 2f97f297..aa87003 100644
--- a/chrome/browser/ui/views/sharesheet_bubble_view.cc
+++ b/chrome/browser/ui/views/sharesheet_bubble_view.cc
@@ -4,13 +4,17 @@
 
 #include "chrome/browser/ui/views/sharesheet_bubble_view.h"
 
+#include "chrome/browser/sharesheet/sharesheet_service_delegate.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "ui/gfx/geometry/insets.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/widget/widget.h"
 
-SharesheetBubbleView::SharesheetBubbleView(views::View* anchor_view) {
+SharesheetBubbleView::SharesheetBubbleView(
+    views::View* anchor_view,
+    sharesheet::SharesheetServiceDelegate* delegate)
+    : delegate_(delegate) {
   SetButtons(ui::DIALOG_BUTTON_NONE);
 
   SetAnchorView(anchor_view);
@@ -29,6 +33,15 @@
   widget->Show();
 }
 
+void SharesheetBubbleView::CloseBubble() {
+  views::Widget* widget = View::GetWidget();
+  widget->CloseWithReason(views::Widget::ClosedReason::kAcceptButtonClicked);
+}
+
+void SharesheetBubbleView::OnWidgetDestroyed(views::Widget* widget) {
+  delegate_->OnBubbleClosed();
+}
+
 gfx::Size SharesheetBubbleView::CalculatePreferredSize() const {
   const int width = ChromeLayoutProvider::Get()->GetDistanceMetric(
                         DISTANCE_BUBBLE_PREFERRED_WIDTH) -
diff --git a/chrome/browser/ui/views/sharesheet_bubble_view.h b/chrome/browser/ui/views/sharesheet_bubble_view.h
index bc5fc39a..3254e14e 100644
--- a/chrome/browser/ui/views/sharesheet_bubble_view.h
+++ b/chrome/browser/ui/views/sharesheet_bubble_view.h
@@ -7,17 +7,28 @@
 
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
 
+namespace sharesheet {
+class SharesheetServiceDelegate;
+}
+
 class SharesheetBubbleView : public views::BubbleDialogDelegateView {
  public:
-  explicit SharesheetBubbleView(views::View* anchor_view);
+  SharesheetBubbleView(views::View* anchor_view,
+                       sharesheet::SharesheetServiceDelegate* delegate);
   SharesheetBubbleView(const SharesheetBubbleView&) = delete;
   SharesheetBubbleView& operator=(const SharesheetBubbleView&) = delete;
   ~SharesheetBubbleView() override;
 
   void ShowBubble();
+  void CloseBubble();
 
   // views::BubbleDialogDelegateView overrides
   gfx::Size CalculatePreferredSize() const override;
+  void OnWidgetDestroyed(views::Widget* widget) override;
+
+ private:
+  // Owns this class.
+  sharesheet::SharesheetServiceDelegate* delegate_;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_SHARESHEET_BUBBLE_VIEW_H_
diff --git a/chrome/browser/ui/views/tab_search/tab_search_bubble_view.cc b/chrome/browser/ui/views/tab_search/tab_search_bubble_view.cc
index 6d4b820d..615fa30 100644
--- a/chrome/browser/ui/views/tab_search/tab_search_bubble_view.cc
+++ b/chrome/browser/ui/views/tab_search/tab_search_bubble_view.cc
@@ -12,6 +12,12 @@
 #include "chrome/common/webui_url_constants.h"
 #include "ui/views/controls/webview/webview.h"
 #include "ui/views/layout/fill_layout.h"
+#include "ui/views/widget/widget.h"
+
+#if defined(USE_AURA)
+#include "ui/aura/window.h"
+#include "ui/wm/public/activation_client.h"
+#endif
 
 namespace {
 
@@ -29,26 +35,117 @@
 
   ~TabSearchWebView() override = default;
 
-  // WebView:
+  // views::WebView:
   void PreferredSizeChanged() override {
-    View::PreferredSizeChanged();
+    WebView::PreferredSizeChanged();
     parent_->OnWebViewSizeChanged();
   }
 
+  void OnWebContentsAttached() override { SetVisible(false); }
+
+  void ResizeDueToAutoResize(content::WebContents* web_contents,
+                             const gfx::Size& new_size) override {
+    // Don't actually do anything with this information until we have been
+    // shown. Size changes will not be honored by lower layers while we are
+    // hidden.
+    if (!GetVisible()) {
+      pending_preferred_size_ = new_size;
+      return;
+    }
+    WebView::ResizeDueToAutoResize(web_contents, new_size);
+  }
+
+  void DocumentOnLoadCompletedInMainFrame() override {
+    GetWidget()->Show();
+    GetWebContents()->Focus();
+  }
+
+  void DidStopLoading() override {
+    if (GetVisible())
+      return;
+
+    SetVisible(true);
+    ResizeDueToAutoResize(web_contents(), pending_preferred_size_);
+  }
+
  private:
   TabSearchBubbleView* parent_;
+
+  // What we should set the preferred width to once TabSearch has loaded.
+  gfx::Size pending_preferred_size_;
 };
 
 }  // namespace
 
+#if defined(USE_AURA)
+class TabSearchBubbleView::TabSearchWindowObserverAura
+    : public wm::ActivationChangeObserver {
+ public:
+  explicit TabSearchWindowObserverAura(TabSearchBubbleView* bubble)
+      : bubble_(bubble) {
+    gfx::NativeView native_view = bubble_->GetWidget()->GetNativeView();
+    // This is removed in the destructor called by
+    // TabSearchBubbleView::OnWidgetDestroying(), which is guaranteed to be
+    // called before the Widget goes away.  It's not safe to use a
+    // ScopedObserver for this, since the activation client may be deleted
+    // without a call back to this class.
+    wm::GetActivationClient(native_view->GetRootWindow())->AddObserver(this);
+  }
+
+  ~TabSearchWindowObserverAura() override {
+    auto* activation_client = wm::GetActivationClient(
+        bubble_->GetWidget()->GetNativeWindow()->GetRootWindow());
+    DCHECK(activation_client);
+    activation_client->RemoveObserver(this);
+  }
+
+  // wm::ActivationChangeObserver:
+  void OnWindowActivated(wm::ActivationChangeObserver::ActivationReason reason,
+                         aura::Window* gained_active,
+                         aura::Window* lost_active) override {
+    // Close on anchor window activation (i.e. user clicked the browser window).
+    // DesktopNativeWidgetAura does not trigger the expected browser widget
+    // [de]activation events when activating widgets in its own root window.
+    // This additional check handles those cases. See https://crbug.com/320889 .
+    views::Widget* anchor_widget = bubble_->anchor_widget();
+    if (anchor_widget && gained_active == anchor_widget->GetNativeWindow()) {
+      bubble_->GetWidget()->CloseWithReason(
+          views::Widget::ClosedReason::kLostFocus);
+    }
+  }
+
+ private:
+  TabSearchBubbleView* bubble_;
+};
+#endif
+
 void TabSearchBubbleView::CreateTabSearchBubble(Browser* browser) {
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
   DCHECK(browser_view);
-  auto delegate = base::WrapUnique(
-      new TabSearchBubbleView(browser, browser_view->toolbar()));
-  BubbleDialogDelegateView::CreateBubble(delegate.release())->Show();
+  auto delegate =
+      std::make_unique<TabSearchBubbleView>(browser, browser_view->toolbar());
+  BubbleDialogDelegateView::CreateBubble(delegate.release());
 }
 
+TabSearchBubbleView::TabSearchBubbleView(Browser* browser,
+                                         views::View* anchor_view)
+    : BubbleDialogDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
+      web_view_(AddChildView(
+          std::make_unique<TabSearchWebView>(browser->profile(), this))) {
+  observed_anchor_widget_.Add(anchor_view->GetWidget());
+
+  set_close_on_deactivate(false);
+
+  SetButtons(ui::DIALOG_BUTTON_NONE);
+  set_margins(gfx::Insets());
+
+  SetLayoutManager(std::make_unique<views::FillLayout>());
+  web_view_->EnableSizingFromWebContents(kMinSize, kMaxSize);
+  web_view_->LoadInitialURL(GURL(chrome::kChromeUITabSearchURL));
+}
+
+TabSearchBubbleView::~TabSearchBubbleView() = default;
+
 gfx::Size TabSearchBubbleView::CalculatePreferredSize() const {
   // Constrain the size to popup min/max.
   gfx::Size preferred_size = views::View::CalculatePreferredSize();
@@ -57,26 +154,31 @@
   return preferred_size;
 }
 
-void TabSearchBubbleView::OnWebViewSizeChanged() {
-  SizeToContents();
+void TabSearchBubbleView::AddedToWidget() {
+  BubbleDialogDelegateView::AddedToWidget();
+  observed_bubble_widget_.Add(GetWidget());
+#if defined(USE_AURA)
+  // |window_observer_| deals with activation issues relevant to Aura platforms.
+  // This special case handling is not needed on Mac.
+  window_observer_ = std::make_unique<TabSearchWindowObserverAura>(this);
+#endif
 }
 
-TabSearchBubbleView::TabSearchBubbleView(Browser* browser,
-                                         views::View* anchor_view)
-    : BubbleDialogDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
-      web_view_(AddChildView(
-          std::make_unique<TabSearchWebView>(browser->profile(), this))) {
-  SetButtons(ui::DIALOG_BUTTON_NONE);
-  set_margins(gfx::Insets());
+void TabSearchBubbleView::OnWidgetActivationChanged(views::Widget* widget,
+                                                    bool active) {
+  // The widget is shown asynchronously and may take a long time to appear, so
+  // only close if it's actually been shown.
+  if (GetWidget()->IsVisible() && widget == anchor_widget() && active)
+    GetWidget()->CloseWithReason(views::Widget::ClosedReason::kLostFocus);
+}
 
-  SetLayoutManager(std::make_unique<views::FillLayout>());
-  web_view_->EnableSizingFromWebContents(kMinSize, kMaxSize);
-  web_view_->LoadInitialURL(GURL(chrome::kChromeUITabSearchURL));
+void TabSearchBubbleView::OnWidgetDestroying(views::Widget* widget) {
+#if defined(USE_AURA)
+  if (widget == GetWidget())
+    window_observer_.reset();
+#endif
+}
 
-  // TODO(crbug.com/1010589) WebContents are initially assumed to be visible by
-  // default unless explicitly hidden. The WebContents need to be set to hidden
-  // so that the visibility state of the document in JavaScript is correctly
-  // initially set to 'hidden', and the 'visibilitychange' events correctly get
-  // fired.
-  web_view_->GetWebContents()->WasHidden();
+void TabSearchBubbleView::OnWebViewSizeChanged() {
+  SizeToContents();
 }
diff --git a/chrome/browser/ui/views/tab_search/tab_search_bubble_view.h b/chrome/browser/ui/views/tab_search/tab_search_bubble_view.h
index 737d6b19..d11e70c 100644
--- a/chrome/browser/ui/views/tab_search/tab_search_bubble_view.h
+++ b/chrome/browser/ui/views/tab_search/tab_search_bubble_view.h
@@ -5,30 +5,57 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_TAB_SEARCH_TAB_SEARCH_BUBBLE_VIEW_H_
 #define CHROME_BROWSER_UI_VIEWS_TAB_SEARCH_TAB_SEARCH_BUBBLE_VIEW_H_
 
+#include "base/scoped_observer.h"
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
 #include "ui/views/controls/webview/webview.h"
 #include "ui/web_dialogs/web_dialog_delegate.h"
 
+namespace views {
+class Widget;
+class WidgetObserver;
+}  // namespace views
+
 class Browser;
 
-// TODO(tluk): Only show the bubble once web contents are available to prevent
-// akward resizing when web content finally loads in.
-class TabSearchBubbleView : public views::BubbleDialogDelegateView {
+class TabSearchBubbleView : public views::BubbleDialogDelegateView,
+                            public views::WidgetObserver {
  public:
+  // TODO(tluk): Since the Bubble is shown asynchronously, we shouldn't call
+  // this if the Widget is hidden and yet to be revealed.
   static void CreateTabSearchBubble(Browser* browser);
 
-  ~TabSearchBubbleView() override = default;
+  TabSearchBubbleView(Browser* browser, views::View* anchor_view);
+  ~TabSearchBubbleView() override;
 
   // views::BubbleDialogDelegateView:
   gfx::Size CalculatePreferredSize() const override;
+  void AddedToWidget() override;
+
+  // views::WidgetObserver:
+  void OnWidgetActivationChanged(views::Widget* widget, bool active) override;
+  void OnWidgetDestroying(views::Widget* widget) override;
 
   void OnWebViewSizeChanged();
 
  private:
-  TabSearchBubbleView(Browser* browser, views::View* anchor_view);
+#if defined(USE_AURA)
+  // TabSearchWindowObserverAura deals with issues in bubble deactivation on
+  // Aura platforms. See comments in OnWindowActivated().
+  // These issues are not present on Mac.
+  class TabSearchWindowObserverAura;
+
+  // |window_observer_| is a helper that hooks into the TabSearchBubbleView's
+  // widget lifecycle events.
+  std::unique_ptr<TabSearchWindowObserverAura> window_observer_;
+#endif
 
   views::WebView* web_view_;
 
+  ScopedObserver<views::Widget, views::WidgetObserver> observed_anchor_widget_{
+      this};
+  ScopedObserver<views::Widget, views::WidgetObserver> observed_bubble_widget_{
+      this};
+
   DISALLOW_COPY_AND_ASSIGN(TabSearchBubbleView);
 };
 
diff --git a/chrome/browser/ui/views/tabs/tab_group_header.cc b/chrome/browser/ui/views/tabs/tab_group_header.cc
index 7f4ddcf..8d62478 100644
--- a/chrome/browser/ui/views/tabs/tab_group_header.cc
+++ b/chrome/browser/ui/views/tabs/tab_group_header.cc
@@ -120,8 +120,10 @@
       bool successful_toggle =
           tab_strip_->controller()->ToggleTabGroupCollapsedState(
               group().value(), true);
-      if (successful_toggle)
+      if (successful_toggle) {
+        NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
         LogCollapseTime();
+      }
     } else {
       editor_bubble_tracker_.Opened(TabGroupEditorBubbleView::Show(
           tab_strip_->controller()->GetBrowser(), group().value(), this));
diff --git a/chrome/browser/ui/views/toolbar/sharesheet_button.cc b/chrome/browser/ui/views/toolbar/sharesheet_button.cc
index a00100a5..b1f49554 100644
--- a/chrome/browser/ui/views/toolbar/sharesheet_button.cc
+++ b/chrome/browser/ui/views/toolbar/sharesheet_button.cc
@@ -6,7 +6,9 @@
 
 #include <memory>
 
-#include "chrome/browser/sharesheet/sharesheet_service_delegate.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/sharesheet/sharesheet_service.h"
+#include "chrome/browser/sharesheet/sharesheet_service_factory.h"
 #include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
@@ -30,11 +32,11 @@
 void SharesheetButton::ButtonPressed(views::Button* sender,
                                      const ui::Event& event) {
   // On button press show sharesheet bubble.
-  auto sharesheet_service_delegate =
-      std::make_unique<sharesheet::SharesheetServiceDelegate>(
-          browser_->tab_strip_model()->GetActiveWebContents(),
-          /* bubble_anchor_view */ this);
-  sharesheet_service_delegate->ShowBubble();
+  auto* profile = Profile::FromBrowserContext(
+      browser_->tab_strip_model()->GetActiveWebContents()->GetBrowserContext());
+  auto* sharesheet_service =
+      sharesheet::SharesheetServiceFactory::GetForProfile(profile);
+  sharesheet_service->ShowBubble(/* bubble_anchor_view */ this);
 }
 
 int SharesheetButton::GetIconSize() const {
diff --git a/chrome/browser/ui/web_applications/test/web_app_browsertest_util.cc b/chrome/browser/ui/web_applications/test/web_app_browsertest_util.cc
index c4361f1..c64a286c1 100644
--- a/chrome/browser/ui/web_applications/test/web_app_browsertest_util.cc
+++ b/chrome/browser/ui/web_applications/test/web_app_browsertest_util.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
 
 #include "base/run_loop.h"
+#include "base/strings/utf_string_conversions.h"
 #include "base/test/bind_test_util.h"
 #include "chrome/browser/apps/app_service/app_launch_params.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
@@ -42,6 +43,9 @@
 
 AppId InstallWebApp(Profile* profile,
                     std::unique_ptr<WebApplicationInfo> web_app_info) {
+  if (web_app_info->title.empty())
+    web_app_info->title = base::ASCIIToUTF16("WebApplicationInfo App Name");
+
   AppId app_id;
   base::RunLoop run_loop;
   auto* provider = WebAppProviderBase::GetProviderBase(profile);
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index c2ec652..90b4e03 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -674,10 +674,12 @@
         chromeos::ArcGraphicsTracingMode::kOverview>>;
 
 #if !defined(OFFICIAL_BUILD)
+#if !defined(USE_REAL_DBUS_CLIENTS)
   if (!base::SysInfo::IsRunningOnChromeOS()) {
     if (url.host_piece() == chrome::kChromeUIDeviceEmulatorHost)
       return &NewWebUI<DeviceEmulatorUI>;
   }
+#endif  // !defined(USE_REAL_DBUS_CLIENTS)
   if (url.host_piece() == chromeos::kChromeUISampleSystemWebAppHost)
     return &NewWebUI<chromeos::SampleSystemWebAppUI>;
   if (url.host_piece() == chromeos::kChromeUITelemetryExtensionHost) {
diff --git a/chrome/browser/ui/webui/chromeos/arc_graphics_tracing/arc_graphics_tracing_handler.cc b/chrome/browser/ui/webui/chromeos/arc_graphics_tracing/arc_graphics_tracing_handler.cc
index 1c281a9..465498d 100644
--- a/chrome/browser/ui/webui/chromeos/arc_graphics_tracing/arc_graphics_tracing_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/arc_graphics_tracing/arc_graphics_tracing_handler.cc
@@ -30,7 +30,6 @@
 #include "chrome/browser/chromeos/file_manager/path_util.h"
 #include "chrome/browser/platform_util.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/ash/launcher/arc_app_window_launcher_controller.h"
 #include "components/arc/arc_prefs.h"
 #include "components/arc/arc_util.h"
 #include "components/exo/shell_surface_util.h"
diff --git a/chrome/browser/ui/webui/chromeos/login/oobe_display_chooser_unittest.cc b/chrome/browser/ui/webui/chromeos/login/oobe_display_chooser_unittest.cc
index e64ac29..06eee30 100644
--- a/chrome/browser/ui/webui/chromeos/login/oobe_display_chooser_unittest.cc
+++ b/chrome/browser/ui/webui/chromeos/login/oobe_display_chooser_unittest.cc
@@ -71,6 +71,9 @@
                         ash::mojom::TouchCalibrationPtr calibration,
                         TouchCalibrationCallback callback) override {}
   void HighlightDisplay(int64_t id) override {}
+  void DragDisplayDelta(int64_t display_id,
+                        int32_t delta_x,
+                        int32_t delta_y) override {}
 
  private:
   mojo::Receiver<ash::mojom::CrosDisplayConfigController> receiver_{this};
diff --git a/chrome/browser/ui/webui/chromeos/network_ui.cc b/chrome/browser/ui/webui/chromeos/network_ui.cc
index 41069694..41395ad 100644
--- a/chrome/browser/ui/webui/chromeos/network_ui.cc
+++ b/chrome/browser/ui/webui/chromeos/network_ui.cc
@@ -198,12 +198,8 @@
     }
     NetworkHandler::Get()->network_device_handler()->GetDeviceProperties(
         device->path(),
-        base::BindOnce(
-            &NetworkConfigMessageHandler::GetShillDevicePropertiesSuccess,
-            weak_ptr_factory_.GetWeakPtr(), callback_id),
-        base::Bind(&NetworkConfigMessageHandler::ErrorCallback,
-                   weak_ptr_factory_.GetWeakPtr(), callback_id, type,
-                   kGetDeviceProperties));
+        base::BindOnce(&NetworkConfigMessageHandler::OnGetShillDeviceProperties,
+                       weak_ptr_factory_.GetWeakPtr(), callback_id, type));
   }
 
   void GetShillEthernetEAP(const base::ListValue* arg_list) {
@@ -271,18 +267,21 @@
     InternetConfigDialog::ShowDialogForNetworkType(::onc::network_type::kWiFi);
   }
 
-  void GetShillDevicePropertiesSuccess(
-      const std::string& callback_id,
-      const std::string& device_path,
-      const base::DictionaryValue& dictionary) {
-    std::unique_ptr<base::DictionaryValue> dictionary_copy(
-        dictionary.DeepCopy());
+  void OnGetShillDeviceProperties(const std::string& callback_id,
+                                  const std::string& type,
+                                  const std::string& device_path,
+                                  base::Optional<base::Value> result) {
+    if (!result) {
+      ErrorCallback(callback_id, type, kGetDeviceProperties,
+                    "GetDeviceProperties failed", nullptr);
+      return;
+    }
 
     // Set the 'device_path' property for debugging.
-    dictionary_copy->SetKey("device_path", base::Value(device_path));
+    result->SetKey("device_path", base::Value(device_path));
 
     base::ListValue return_arg_list;
-    return_arg_list.Append(std::move(dictionary_copy));
+    return_arg_list.Append(std::move(*result));
     Respond(callback_id, return_arg_list);
   }
 
diff --git a/chrome/browser/ui/webui/management_ui.cc b/chrome/browser/ui/webui/management_ui.cc
index 531f705e..472b952 100644
--- a/chrome/browser/ui/webui/management_ui.cc
+++ b/chrome/browser/ui/webui/management_ui.cc
@@ -114,6 +114,9 @@
     {kManagementOnBulkDataEntryEvent, IDS_MANAGEMENT_TEXT_ENTERED_EVENT},
     {kManagementOnBulkDataEntryVisibleData,
      IDS_MANAGEMENT_TEXT_ENTERED_VISIBLE_DATA},
+    {kManagementOnPageVisitedEvent, IDS_MANAGEMENT_PAGE_VISITED_EVENT},
+    {kManagementOnPageVisitedVisibleData,
+     IDS_MANAGEMENT_PAGE_VISITED_VISIBLE_DATA},
   };
 
   AddLocalizedStringsBulk(source, kLocalizedStrings);
diff --git a/chrome/browser/ui/webui/management_ui_handler.cc b/chrome/browser/ui/webui/management_ui_handler.cc
index d80ffaa..5094c6b 100644
--- a/chrome/browser/ui/webui/management_ui_handler.cc
+++ b/chrome/browser/ui/webui/management_ui_handler.cc
@@ -133,6 +133,10 @@
 const char kManagementOnBulkDataEntryVisibleData[] =
     "managementOnBulkDataEntryVisibleData";
 
+const char kManagementOnPageVisitedEvent[] = "managementOnPageVisitedEvent";
+const char kManagementOnPageVisitedVisibleData[] =
+    "managementOnPageVisitedVisibleData";
+
 const char kReportingTypeDevice[] = "device";
 const char kReportingTypeExtensions[] = "extensions";
 const char kReportingTypeSecurity[] = "security";
@@ -781,6 +785,17 @@
     info.Append(std::move(value));
   }
 
+  auto* on_page_visited_event =
+      chrome_policies.GetValue(policy::key::kEnterpriseRealTimeUrlCheckMode);
+  if (on_page_visited_event && on_page_visited_event->is_int() &&
+      on_page_visited_event->GetInt() !=
+          safe_browsing::REAL_TIME_CHECK_DISABLED) {
+    base::Value value(base::Value::Type::DICTIONARY);
+    value.SetStringKey("title", kManagementOnPageVisitedEvent);
+    value.SetStringKey("permission", kManagementOnPageVisitedVisibleData);
+    info.Append(std::move(value));
+  }
+
 #if defined(OS_CHROMEOS)
   std::string management_domain = GetDeviceDomain();
   if (management_domain.empty())
diff --git a/chrome/browser/ui/webui/management_ui_handler.h b/chrome/browser/ui/webui/management_ui_handler.h
index a546d63..59543bd 100644
--- a/chrome/browser/ui/webui/management_ui_handler.h
+++ b/chrome/browser/ui/webui/management_ui_handler.h
@@ -68,6 +68,8 @@
 extern const char kManagementOnFileDownloadedVisibleData[];
 extern const char kManagementOnBulkDataEntryEvent[];
 extern const char kManagementOnBulkDataEntryVisibleData[];
+extern const char kManagementOnPageVisitedEvent[];
+extern const char kManagementOnPageVisitedVisibleData[];
 
 extern const char kPolicyKeyReportMachineIdData[];
 extern const char kPolicyKeyReportUserIdData[];
diff --git a/chrome/browser/ui/webui/management_ui_handler_unittest.cc b/chrome/browser/ui/webui/management_ui_handler_unittest.cc
index d5192e4..138a9a8 100644
--- a/chrome/browser/ui/webui/management_ui_handler_unittest.cc
+++ b/chrome/browser/ui/webui/management_ui_handler_unittest.cc
@@ -1153,10 +1153,12 @@
   SetConnectorPolicyValue(policy::key::kOnSecurityEventEnterpriseConnector,
                           "[{\"service_provider\":\"google\"}]",
                           chrome_policies);
+  SetConnectorPolicyValue(policy::key::kEnterpriseRealTimeUrlCheckMode, "1",
+                          chrome_policies);
 
   info = handler_.GetThreatProtectionInfo(profile_no_domain.get());
   info.GetAsDictionary(&threat_protection_info);
-  EXPECT_EQ(4u, threat_protection_info->FindListKey("info")->GetList().size());
+  EXPECT_EQ(5u, threat_protection_info->FindListKey("info")->GetList().size());
   EXPECT_EQ(
       l10n_util::GetStringUTF16(IDS_MANAGEMENT_THREAT_PROTECTION_DESCRIPTION),
       base::UTF8ToUTF16(*threat_protection_info->FindStringKey("description")));
@@ -1186,6 +1188,12 @@
     value.SetStringKey("permission", kManagementEnterpriseReportingVisibleData);
     expected_info.Append(std::move(value));
   }
+  {
+    base::Value value(base::Value::Type::DICTIONARY);
+    value.SetStringKey("title", kManagementOnPageVisitedEvent);
+    value.SetStringKey("permission", kManagementOnPageVisitedVisibleData);
+    expected_info.Append(std::move(value));
+  }
 
   EXPECT_EQ(expected_info, *threat_protection_info->FindListKey("info"));
 }
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
index f2051be..25e7d3d 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
@@ -227,7 +227,7 @@
 }  // namespace
 
 NewTabPageUI::NewTabPageUI(content::WebUI* web_ui)
-    : ui::MojoWebUIController(web_ui, true),
+    : ui::MojoWebUIController(web_ui, /*enable_chrome_send=*/false),
       content::WebContentsObserver(web_ui->GetWebContents()),
       page_factory_receiver_(this),
       profile_(Profile::FromWebUI(web_ui)),
diff --git a/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc b/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc
index e789192..e4f0fa4 100644
--- a/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc
@@ -50,7 +50,7 @@
 #include "chromeos/printing/printer_configuration.h"
 #include "chromeos/printing/printer_translator.h"
 #include "chromeos/printing/printing_constants.h"
-#include "chromeos/printing/uri_components.h"
+#include "chromeos/printing/uri.h"
 #include "components/device_event_log/device_event_log.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
@@ -95,22 +95,32 @@
   }
 }
 
-// Query an IPP printer to check for autoconf support where the printer is
-// located at |printer_uri|.  Results are reported through |callback|.  It is an
-// error to attempt this with a non-IPP printer.
-void QueryAutoconf(const std::string& printer_uri,
-                   PrinterInfoCallback callback) {
-  auto optional = ParseUri(printer_uri);
-  // Behavior for querying a non-IPP uri is undefined and disallowed.
-  if (!IsIppUri(printer_uri) || !optional.has_value()) {
-    PRINTER_LOG(ERROR) << "Printer uri is invalid: " << printer_uri;
-    std::move(callback).Run(PrinterQueryResult::UNKNOWN_FAILURE,
-                            printing::PrinterStatus(), "", "", "", {}, false);
+// Split the given |address| into host and port by the last occurrence of ':'.
+// If |address| has no ':', the |port| parameter is set to an empty string.
+// |host| and |port| cannot be nullptr.
+void SplitAddress(const std::string& address,
+                  std::string* host,
+                  std::string* port) {
+  DCHECK(host);
+  DCHECK(port);
+  const size_t pos = address.find_last_of(':');
+  if (pos == std::string::npos) {
+    // |address| has no ':' characters.
+    *host = address;
+    *port = "";
     return;
   }
+  *host = address.substr(0, pos);
+  // If |pos| points to the last character substr() returns an empty string.
+  *port = address.substr(pos + 1);
+}
 
-  UriComponents uri = optional.value();
-  QueryIppPrinter(uri.host(), uri.port(), uri.path(), uri.encrypted(),
+// Query an IPP printer to check for autoconf support where the printer is
+// located at |printer_uri|.  Results are reported through |callback|.  The
+// scheme of |printer_uri| must equal "ipp" or "ipps".
+void QueryAutoconf(const Uri& uri, PrinterInfoCallback callback) {
+  QueryIppPrinter(uri.GetHostEncoded(), uri.GetPort(),
+                  uri.GetPathEncodedAsString(), uri.GetScheme() == kIppsScheme,
                   std::move(callback));
 }
 
@@ -129,23 +139,6 @@
   return response;
 }
 
-// Extracts a sanitized value of printerQueue from |printer_dict|.  Returns an
-// empty string if the value was not present in the dictionary.
-std::string GetPrinterQueue(const base::DictionaryValue& printer_dict) {
-  std::string queue;
-  if (!printer_dict.GetString("printerQueue", &queue)) {
-    return queue;
-  }
-
-  if (!queue.empty() && queue[0] == '/') {
-    // Strip the leading backslash. It is expected that this results in an
-    // empty string if the input is just a backslash.
-    queue = queue.substr(1);
-  }
-
-  return queue;
-}
-
 // Generates a Printer from |printer_dict| where |printer_dict| is a
 // CupsPrinterInfo representation.  If any of the required fields are missing,
 // returns nullptr.
@@ -173,13 +166,8 @@
     return nullptr;
   }
 
-  std::string printer_queue = GetPrinterQueue(printer_dict);
-
-  std::string printer_uri =
-      printer_protocol + url::kStandardSchemeSeparator + printer_address;
-  if (!printer_queue.empty()) {
-    printer_uri += "/" + printer_queue;
-  }
+  std::string printer_queue;
+  printer_dict.GetString("printerQueue", &printer_queue);
 
   auto printer = std::make_unique<chromeos::Printer>(printer_id);
   printer->set_display_name(printer_name);
@@ -187,9 +175,19 @@
   printer->set_manufacturer(printer_manufacturer);
   printer->set_model(printer_model);
   printer->set_make_and_model(printer_make_and_model);
-  printer->set_uri(printer_uri);
   printer->set_print_server_uri(print_server_uri);
 
+  std::string printer_host;
+  std::string printer_port;
+  SplitAddress(printer_address, &printer_host, &printer_port);
+
+  Uri uri;
+  if (!uri.SetScheme(printer_protocol) || !uri.SetHostEncoded(printer_host) ||
+      !uri.SetPort(printer_port) || !uri.SetPathEncoded(printer_queue) ||
+      !printer->SetUri(uri)) {
+    return nullptr;
+  }
+
   return printer;
 }
 
@@ -476,14 +474,8 @@
     return;
   }
 
-  if (printer_address.empty()) {
-    // Run the failure callback.
-    OnAutoconfQueried(callback_id, PrinterQueryResult::UNKNOWN_FAILURE,
-                      printing::PrinterStatus(), "", "", "", {}, false);
-    return;
-  }
-
-  std::string printer_queue = GetPrinterQueue(*printer_dict);
+  std::string printer_queue;
+  printer_dict->GetString("printerQueue", &printer_queue);
 
   std::string printer_protocol;
   if (!printer_dict->GetString("printerProtocol", &printer_protocol)) {
@@ -493,13 +485,24 @@
 
   DCHECK(printer_protocol == kIppScheme || printer_protocol == kIppsScheme)
       << "Printer info requests only supported for IPP and IPPS printers";
+
+  std::string printer_host;
+  std::string printer_port;
+  SplitAddress(printer_address, &printer_host, &printer_port);
+
+  Uri uri;
+  if (!uri.SetScheme(printer_protocol) || !uri.SetHostEncoded(printer_host) ||
+      !uri.SetPort(printer_port) || !uri.SetPathEncoded(printer_queue) ||
+      !IsValidPrinterUri(uri)) {
+    // Run the failure callback.
+    OnAutoconfQueried(callback_id, PrinterQueryResult::UNKNOWN_FAILURE,
+                      printing::PrinterStatus(), "", "", "", {}, false);
+    return;
+  }
+
   PRINTER_LOG(DEBUG) << "Querying printer info";
-  std::string printer_uri =
-      base::StringPrintf("%s://%s/%s", printer_protocol.c_str(),
-                         printer_address.c_str(), printer_queue.c_str());
-  QueryAutoconf(printer_uri,
-                base::BindOnce(&CupsPrintersHandler::OnAutoconfQueried,
-                               weak_factory_.GetWeakPtr(), callback_id));
+  QueryAutoconf(uri, base::BindOnce(&CupsPrintersHandler::OnAutoconfQueried,
+                                    weak_factory_.GetWeakPtr(), callback_id));
 }
 
 void CupsPrintersHandler::OnAutoconfQueriedDiscovered(
@@ -663,14 +666,6 @@
     return;
   }
 
-  if (!printer->GetUriComponents().has_value()) {
-    // If the returned optional does not contain a value then it means that the
-    // printer's uri was not able to be parsed successfully.
-    PRINTER_LOG(ERROR) << "Failed to parse printer URI";
-    OnAddOrEditPrinterError(callback_id, PrinterSetupResult::kFatalError);
-    return;
-  }
-
   // Grab the existing printer object and check that we are not making any
   // changes that will make |existing_printer_object| unusable.
   if (printer->id().empty()) {
@@ -1090,7 +1085,7 @@
     return;
   }
 
-  if (!printer->GetUriComponents().has_value()) {
+  if (!printer->HasUri()) {
     PRINTER_LOG(DEBUG) << "Could not parse uri";
     // The printer uri was not parsed successfully. Fail the add.
     ResolveJavascriptCallback(
@@ -1225,14 +1220,13 @@
   }
 
   PRINTER_LOG(EVENT) << printer.make_and_model() << " IP Resolution succeeded";
-  std::string resolved_uri = printer.ReplaceHostAndPort(endpoint);
+  const Uri uri = printer.ReplaceHostAndPort(endpoint);
 
-  if (IsIppUri(resolved_uri)) {
+  if (IsIppUri(uri)) {
     PRINTER_LOG(EVENT) << "Query printer for IPP attributes";
     QueryAutoconf(
-        resolved_uri,
-        base::BindOnce(&CupsPrintersHandler::OnAutoconfQueriedDiscovered,
-                       weak_factory_.GetWeakPtr(), callback_id, printer));
+        uri, base::BindOnce(&CupsPrintersHandler::OnAutoconfQueriedDiscovered,
+                            weak_factory_.GetWeakPtr(), callback_id, printer));
     return;
   }
 
@@ -1298,7 +1292,7 @@
   std::set<GURL> known_printers;
   for (const Printer& printer : saved_printers) {
     base::Optional<GURL> gurl =
-        GenerateServerPrinterUrlWithValidScheme(printer.uri());
+        GenerateServerPrinterUrlWithValidScheme(printer.uri().GetNormalized());
     if (gurl)
       known_printers.insert(gurl.value());
   }
@@ -1310,8 +1304,8 @@
   printers.reserve(returned_printers.size());
   for (PrinterDetector::DetectedPrinter& printer : returned_printers) {
     printers.push_back(std::move(printer.printer));
-    base::Optional<GURL> printer_gurl =
-        GenerateServerPrinterUrlWithValidScheme(printers.back().uri());
+    base::Optional<GURL> printer_gurl = GenerateServerPrinterUrlWithValidScheme(
+        printers.back().uri().GetNormalized());
     if (printer_gurl && known_printers.count(printer_gurl.value()))
       printers.pop_back();
   }
diff --git a/chrome/browser/ui/webui/settings/chromeos/device_display_handler.cc b/chrome/browser/ui/webui/settings/chromeos/device_display_handler.cc
index fdfe3d1..e5e6117 100644
--- a/chrome/browser/ui/webui/settings/chromeos/device_display_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/device_display_handler.cc
@@ -26,10 +26,13 @@
 void DisplayHandler::RegisterMessages() {
   web_ui()->RegisterMessageCallback(
       "highlightDisplay",
-      base::BindRepeating(
-          &DisplayHandler::HandleHighlightDisplay,
-          base::Unretained(this)));  // base::Unretained(this) is acceptable
-                                     // here as |this| is owned by web_ui().
+      base::BindRepeating(&DisplayHandler::HandleHighlightDisplay,
+                          base::Unretained(this)));
+
+  web_ui()->RegisterMessageCallback(
+      "dragDisplayDelta",
+      base::BindRepeating(&DisplayHandler::HandleDragDisplayDelta,
+                          base::Unretained(this)));
 }
 
 void DisplayHandler::HandleHighlightDisplay(const base::ListValue* args) {
@@ -47,5 +50,23 @@
   cros_display_config_->HighlightDisplay(display_id);
 }
 
+void DisplayHandler::HandleDragDisplayDelta(const base::ListValue* args) {
+  DCHECK_EQ(3U, args->GetSize());
+  AllowJavascript();
+
+  const auto& args_list = args->GetList();
+  const std::string& display_id_str = args_list[0].GetString();
+  int32_t delta_x = static_cast<int32_t>(args_list[1].GetInt());
+  int32_t delta_y = static_cast<int32_t>(args_list[2].GetInt());
+
+  int64_t display_id;
+  if (!base::StringToInt64(display_id_str, &display_id)) {
+    NOTREACHED() << "Unable to parse |display_id| for HandleDragDisplayDelta";
+    return;
+  }
+
+  cros_display_config_->DragDisplayDelta(display_id, delta_x, delta_y);
+}
+
 }  // namespace settings
 }  // namespace chromeos
diff --git a/chrome/browser/ui/webui/settings/chromeos/device_display_handler.h b/chrome/browser/ui/webui/settings/chromeos/device_display_handler.h
index 62f3f40..0331b77 100644
--- a/chrome/browser/ui/webui/settings/chromeos/device_display_handler.h
+++ b/chrome/browser/ui/webui/settings/chromeos/device_display_handler.h
@@ -32,6 +32,7 @@
 
  private:
   void HandleHighlightDisplay(const base::ListValue* args);
+  void HandleDragDisplayDelta(const base::ListValue* args);
 
   mojo::Remote<ash::mojom::CrosDisplayConfigController> cros_display_config_;
 };
diff --git a/chrome/browser/ui/webui/settings/chromeos/device_section.cc b/chrome/browser/ui/webui/settings/chromeos/device_section.cc
index 7921e88..e8e3f6d 100644
--- a/chrome/browser/ui/webui/settings/chromeos/device_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/device_section.cc
@@ -654,6 +654,10 @@
 
   html_source->AddString("invalidDisplayId",
                          base::NumberToString(display::kInvalidDisplayId));
+
+  html_source->AddBoolean(
+      "allowDisplayAlignmentApi",
+      base::FeatureList::IsEnabled(ash::features::kDisplayAlignAssist));
 }
 
 void AddDeviceStorageStrings(content::WebUIDataSource* html_source,
@@ -845,7 +849,8 @@
 }
 
 void DeviceSection::AddHandlers(content::WebUI* web_ui) {
-  if (ash::features::IsDisplayIdentificationEnabled()) {
+  if (ash::features::IsDisplayIdentificationEnabled() ||
+      ash::features::IsDisplayAlignmentAssistanceEnabled()) {
     web_ui->AddMessageHandler(
         std::make_unique<chromeos::settings::DisplayHandler>());
   }
diff --git a/chrome/browser/web_applications/components/web_app_install_utils.cc b/chrome/browser/web_applications/components/web_app_install_utils.cc
index 3c962ba..811f0de 100644
--- a/chrome/browser/web_applications/components/web_app_install_utils.cc
+++ b/chrome/browser/web_applications/components/web_app_install_utils.cc
@@ -133,8 +133,8 @@
   if (!manifest.short_name.is_null())
     web_app_info->title = manifest.short_name.string();
 
-  // Give the full length name priority.
-  if (!manifest.name.is_null())
+  // Give the full length name priority if it's not empty.
+  if (!manifest.name.is_null() && !manifest.name.string().empty())
     web_app_info->title = manifest.name.string();
 
   // Set the url based on the manifest value, if any.
diff --git a/chrome/browser/web_applications/components/web_app_install_utils_unittest.cc b/chrome/browser/web_applications/components/web_app_install_utils_unittest.cc
index 7b5b6710..6c795eef 100644
--- a/chrome/browser/web_applications/components/web_app_install_utils_unittest.cc
+++ b/chrome/browser/web_applications/components/web_app_install_utils_unittest.cc
@@ -158,6 +158,19 @@
   EXPECT_EQ(protocol_handler.url, GURL("http://example.com/handle=%s"));
 }
 
+TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifest_EmptyName) {
+  WebApplicationInfo web_app_info;
+
+  blink::Manifest manifest;
+  manifest.name =
+      base::NullableString16(base::ASCIIToUTF16(""), /*is_null=*/false);
+  manifest.short_name = base::NullableString16(
+      base::ASCIIToUTF16(kAppShortName), /*is_null=*/false);
+
+  UpdateWebAppInfoFromManifest(manifest, &web_app_info);
+  EXPECT_EQ(base::UTF8ToUTF16(kAppShortName), web_app_info.title);
+}
+
 // Tests that WebAppInfo is correctly updated when Manifest contains Shortcuts.
 TEST_F(WebAppInstallUtilsWithShortcutsMenu,
        UpdateWebAppInfoFromManifestWithShortcuts) {
diff --git a/chrome/browser/web_applications/system_web_app_manager_browsertest.cc b/chrome/browser/web_applications/system_web_app_manager_browsertest.cc
index 853910b..1b4b7e5 100644
--- a/chrome/browser/web_applications/system_web_app_manager_browsertest.cc
+++ b/chrome/browser/web_applications/system_web_app_manager_browsertest.cc
@@ -79,7 +79,7 @@
 }
 
 SystemAppType SystemWebAppManagerBrowserTestBase::GetMockAppType() {
-  DCHECK(maybe_installation_);
+  CHECK(maybe_installation_);
   return maybe_installation_->GetType();
 }
 
@@ -102,8 +102,13 @@
     SystemAppType system_app_type) {
   WaitForTestSystemAppInstall();
   apps::AppLaunchParams params = LaunchParamsForApp(system_app_type);
+
+  content::TestNavigationObserver navigation_observer(
+      GetLaunchURL(system_app_type));
+  navigation_observer.StartWatchingNewWebContents();
   content::WebContents* web_contents = LaunchApp(params);
-  EXPECT_TRUE(WaitForLoadStop(web_contents));
+  navigation_observer.Wait();
+
   return web_contents;
 }
 
@@ -122,7 +127,7 @@
     SystemAppType system_app_type) {
   base::Optional<AppId> app_id =
       GetManager().GetAppIdForSystemApp(system_app_type);
-  DCHECK(app_id.has_value());
+  CHECK(app_id.has_value());
   return apps::AppLaunchParams(
       *app_id, apps::mojom::LaunchContainer::kLaunchContainerWindow,
       WindowOpenDisposition::CURRENT_TAB,
@@ -136,6 +141,16 @@
       ->LaunchAppWithParams(params);
 }
 
+const GURL& SystemWebAppManagerBrowserTestBase::GetLaunchURL(
+    SystemAppType system_app_type) {
+  base::Optional<AppId> app_id =
+      GetManager().GetAppIdForSystemApp(system_app_type).value();
+  CHECK(app_id.has_value());
+  return WebAppProvider::Get(browser()->profile())
+      ->registrar()
+      .GetAppLaunchURL(app_id.value());
+}
+
 SystemWebAppManagerBrowserTest::SystemWebAppManagerBrowserTest(
     bool install_mock)
     : SystemWebAppManagerBrowserTestBase(install_mock) {
@@ -220,18 +235,23 @@
 
 class SystemWebAppManagerFileHandlingBrowserTestBase
     : public SystemWebAppManagerBrowserTestBase,
-      public testing::WithParamInterface<bool> {
+      public ::testing::WithParamInterface<web_app::ProviderType> {
  public:
   using IncludeLaunchDirectory =
       TestSystemWebAppInstallation::IncludeLaunchDirectory;
+
   explicit SystemWebAppManagerFileHandlingBrowserTestBase(
       IncludeLaunchDirectory include_launch_directory)
       : SystemWebAppManagerBrowserTestBase(/*install_mock=*/false) {
-    bool enable_desktop_pwas_without_extensions = GetParam();
+    web_app::ProviderType provider_type = GetParam();
+    if (provider_type == ProviderType::kWebApps) {
+      scoped_feature_web_app_provider_type_.InitAndEnableFeature(
+          features::kDesktopPWAsWithoutExtensions);
+    } else if (provider_type == ProviderType::kBookmarkApps) {
+      scoped_feature_web_app_provider_type_.InitAndDisableFeature(
+          features::kDesktopPWAsWithoutExtensions);
+    }
 
-    scoped_feature_web_app_provider_type_.InitWithFeatureState(
-        features::kDesktopPWAsWithoutExtensions,
-        enable_desktop_pwas_without_extensions);
     scoped_feature_blink_api_.InitWithFeatures(
         {blink::features::kNativeFileSystemAPI,
          blink::features::kFileHandlingAPI},
@@ -242,6 +262,72 @@
             include_launch_directory);
   }
 
+  content::WebContents* LaunchApp(
+      const std::vector<base::FilePath> launch_files,
+      bool wait_for_load = true) {
+    apps::AppLaunchParams params = LaunchParamsForApp(GetMockAppType());
+    params.source = apps::mojom::AppLaunchSource::kSourceChromeInternal;
+    params.launch_files = launch_files;
+
+    content::TestNavigationObserver navigation_observer(
+        GetLaunchURL(GetMockAppType()));
+    navigation_observer.StartWatchingNewWebContents();
+
+    content::WebContents* web_contents =
+        SystemWebAppManagerBrowserTestBase::LaunchApp(params);
+
+    if (wait_for_load)
+      navigation_observer.Wait();
+
+    return web_contents;
+  }
+
+  content::WebContents* LaunchAppWithoutWaiting(
+      const std::vector<base::FilePath> launch_files) {
+    return LaunchApp(launch_files, /* wait_for_load */ false);
+  }
+
+  // Must be called before WaitAndExposeLaunchParamsToWindow. This sets up the
+  // promise used to wait for launchParam callback.
+  bool PrepareToReceiveLaunchParams(content::WebContents* web_contents) {
+    return content::ExecuteScript(
+        web_contents,
+        "window.launchParamsPromise = new Promise(resolve => {"
+        "  window.resolveLaunchParamsPromise = resolve;"
+        "});"
+        "launchQueue.setConsumer(launchParams => {"
+        "  window.resolveLaunchParamsPromise(launchParams);"
+        "  window.resolveLaunchParamsPromise = null;"
+        "});");
+  }
+
+  // Must be called after PrepareToReceiveLaunchParams. This method waits for
+  // launchParams being received, the stores it to a |js_property_name| on JS
+  // window object.
+  bool WaitAndExposeLaunchParamsToWindow(
+      content::WebContents* web_contents,
+      const std::string js_property_name = "launchParams") {
+    bool launch_params_received;
+    EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
+        web_contents,
+        content::JsReplace("window.launchParamsPromise.then(launchParams => {"
+                           "  window[$1] = launchParams;"
+                           "  domAutomationController.send(true);"
+                           "});",
+                           js_property_name),
+        &launch_params_received));
+    return launch_params_received;
+  }
+
+  std::string GetJsStatementValueAsString(content::WebContents* web_contents,
+                                          std::string js_statement) {
+    std::string str;
+    EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+        web_contents, "domAutomationController.send( " + js_statement + ");",
+        &str));
+    return str;
+  }
+
  private:
   base::test::ScopedFeatureList scoped_feature_web_app_provider_type_;
   base::test::ScopedFeatureList scoped_feature_blink_api_;
@@ -261,8 +347,6 @@
 IN_PROC_BROWSER_TEST_P(SystemWebAppManagerLaunchFilesBrowserTest,
                        LaunchFilesForSystemWebApp) {
   WaitForTestSystemAppInstall();
-  apps::AppLaunchParams params = LaunchParamsForApp(GetMockAppType());
-  params.source = apps::mojom::AppLaunchSource::kSourceChromeInternal;
 
   base::ScopedAllowBlockingForTesting allow_blocking;
   base::ScopedTempDir temp_directory;
@@ -271,70 +355,31 @@
   ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_directory.GetPath(),
                                              &temp_file_path));
 
-  const GURL& launch_url = WebAppProvider::Get(browser()->profile())
-                               ->registrar()
-                               .GetAppLaunchURL(params.app_id);
-
   // First launch.
-  params.launch_files = {temp_file_path};
-  content::TestNavigationObserver navigation_observer(launch_url);
-  navigation_observer.StartWatchingNewWebContents();
-  content::WebContents* web_contents =
-      apps::AppServiceProxyFactory::GetForProfile(browser()->profile())
-          ->BrowserAppLauncher()
-          ->LaunchAppWithParams(params);
-  navigation_observer.Wait();
+  content::WebContents* web_contents = LaunchApp({temp_file_path});
 
-  // Set up a Promise that resolves to launchParams, when launchQueue's consumer
-  // callback is called.
-  EXPECT_TRUE(content::ExecuteScript(
-      web_contents,
-      "window.launchParamsPromise = new Promise(resolve => {"
-      "  window.resolveLaunchParamsPromise = resolve;"
-      "});"
-      "launchQueue.setConsumer(launchParams => {"
-      "  window.resolveLaunchParamsPromise(launchParams);"
-      "});"));
+  // Check the App is launched with the correct launch file.
+  EXPECT_TRUE(PrepareToReceiveLaunchParams(web_contents));
+  EXPECT_TRUE(WaitAndExposeLaunchParamsToWindow(web_contents, "launchParams1"));
+  EXPECT_EQ(temp_file_path.BaseName().AsUTF8Unsafe(),
+            GetJsStatementValueAsString(web_contents,
+                                        "window.launchParams1.files[0].name"));
 
-  // Check launch files are correct.
-  std::string file_name;
-  EXPECT_TRUE(content::ExecuteScriptAndExtractString(
-      web_contents,
-      "window.launchParamsPromise.then("
-      "  launchParams => "
-      "    domAutomationController.send(launchParams.files[0].name));",
-      &file_name));
-  EXPECT_EQ(temp_file_path.BaseName().AsUTF8Unsafe(), file_name);
-
-  // Reset the Promise to get second launchParams.
-  EXPECT_TRUE(content::ExecuteScript(
-      web_contents,
-      "window.launchParamsPromise = new Promise(resolve => {"
-      "  window.resolveLaunchParamsPromise = resolve;"
-      "});"));
-
-  // Second Launch.
+  // Second launch.
   base::FilePath temp_file_path2;
   ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_directory.GetPath(),
                                              &temp_file_path2));
-  params.launch_files = {temp_file_path2};
-  content::WebContents* web_contents2 =
-      apps::AppServiceProxyFactory::GetForProfile(browser()->profile())
-          ->BrowserAppLauncher()
-          ->LaunchAppWithParams(params);
 
-  // WebContents* should be the same because we are passing launchParams to the
-  // opened application.
-  EXPECT_EQ(web_contents, web_contents2);
+  // The second launch reuses the opened application. It should pass the
+  // launchParams to the opened page, and return the same content::WebContents*.
+  EXPECT_TRUE(PrepareToReceiveLaunchParams(web_contents));
+  EXPECT_EQ(web_contents, LaunchAppWithoutWaiting({temp_file_path2}));
+  EXPECT_TRUE(WaitAndExposeLaunchParamsToWindow(web_contents, "launchParams2"));
 
-  // Second launch_files are passed to the opened application.
-  EXPECT_TRUE(content::ExecuteScriptAndExtractString(
-      web_contents,
-      "window.launchParamsPromise.then("
-      "  launchParams => "
-      "    domAutomationController.send(launchParams.files[0].name))",
-      &file_name));
-  EXPECT_EQ(temp_file_path2.BaseName().AsUTF8Unsafe(), file_name);
+  // Second launch_files are correct.
+  EXPECT_EQ(temp_file_path2.BaseName().AsUTF8Unsafe(),
+            GetJsStatementValueAsString(web_contents,
+                                        "window.launchParams2.files[0].name"));
 }
 
 class SystemWebAppManagerLaunchDirectoryBrowserTest
@@ -411,9 +456,6 @@
   // files inside the launch directory; 3) read and write to the launch
   // directory (i.e. list and delete files).
   void TestPermissionsForLaunchDirectory(const base::FilePath& base_dir) {
-    apps::AppLaunchParams params = LaunchParamsForApp(GetMockAppType());
-    params.source = apps::mojom::AppLaunchSource::kSourceChromeInternal;
-
     base::ScopedAllowBlockingForTesting allow_blocking;
 
     // Create the launch file, which stores 4 characters "test".
@@ -422,17 +464,7 @@
     ASSERT_TRUE(base::WriteFile(launch_file_path, "test"));
 
     // Launch the App.
-    const GURL& launch_url = WebAppProvider::Get(browser()->profile())
-                                 ->registrar()
-                                 .GetAppLaunchURL(params.app_id);
-    params.launch_files = {launch_file_path};
-    content::TestNavigationObserver navigation_observer(launch_url);
-    navigation_observer.StartWatchingNewWebContents();
-    content::WebContents* web_contents =
-        apps::AppServiceProxyFactory::GetForProfile(browser()->profile())
-            ->BrowserAppLauncher()
-            ->LaunchAppWithParams(params);
-    navigation_observer.Wait();
+    content::WebContents* web_contents = LaunchApp({launch_file_path});
 
     // Launch directories and files passed to system web apps should
     // automatically be granted write permission. Users should not get
@@ -440,23 +472,9 @@
     NativeFileSystemPermissionRequestManager::FromWebContents(web_contents)
         ->set_auto_response_for_test(permissions::PermissionAction::DENIED);
 
-    // Set up a Promise to resolves to launchParams, when launchQueue's consumer
-    // callback is called.
-    EXPECT_TRUE(content::ExecuteScript(
-        web_contents,
-        "window.launchParamsPromise = new Promise(resolve => {"
-        "  window.resolveLaunchParamsPromise = resolve;"
-        "});"
-        "launchQueue.setConsumer(launchParams => {"
-        "  window.resolveLaunchParamsPromise(launchParams);"
-        "});"));
-
-    // Wait for launch. Set window.launchParams for manipulation.
-    EXPECT_TRUE(content::ExecuteScript(
-        web_contents,
-        "window.launchParamsPromise.then(launchParams => {"
-        "  window.launchParams = launchParams;"
-        "});"));
+    // Wait for launchParams.
+    EXPECT_TRUE(PrepareToReceiveLaunchParams(web_contents));
+    EXPECT_TRUE(WaitAndExposeLaunchParamsToWindow(web_contents));
 
     // Check we can read and write to the launch file.
     std::string launch_file_js_handle = "window.launchParams.files[1]";
@@ -498,7 +516,8 @@
 
     // Check a file can be created.
     std::string new_file_js_handle = content::JsReplace(
-        "window.launchParams.files[0].getFileHandle($1, {create:true})", "new_file");
+        "window.launchParams.files[0].getFileHandle($1, {create:true})",
+        "new_file");
     EXPECT_TRUE(WriteContentToJsFileHandle(web_contents, new_file_js_handle,
                                            "js_new_file"));
     EXPECT_EQ("js_new_file", ReadFileContent(base_dir.AppendASCII("new_file")));
@@ -512,8 +531,6 @@
 IN_PROC_BROWSER_TEST_P(SystemWebAppManagerLaunchDirectoryBrowserTest,
                        LaunchDirectoryForSystemWebApp) {
   WaitForTestSystemAppInstall();
-  apps::AppLaunchParams params = LaunchParamsForApp(GetMockAppType());
-  params.source = apps::mojom::AppLaunchSource::kSourceChromeInternal;
 
   base::ScopedAllowBlockingForTesting allow_blocking;
   base::ScopedTempDir temp_directory;
@@ -522,123 +539,49 @@
   ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_directory.GetPath(),
                                              &temp_file_path));
 
-  const GURL& launch_url = WebAppProvider::Get(browser()->profile())
-                               ->registrar()
-                               .GetAppLaunchURL(params.app_id);
-
   // First launch.
-  params.launch_files = {temp_file_path};
-  content::TestNavigationObserver navigation_observer(launch_url);
-  navigation_observer.StartWatchingNewWebContents();
-  content::WebContents* web_contents =
-      apps::AppServiceProxyFactory::GetForProfile(browser()->profile())
-          ->BrowserAppLauncher()
-          ->LaunchAppWithParams(params);
-  navigation_observer.Wait();
+  content::WebContents* web_contents = LaunchApp({temp_file_path});
+  EXPECT_TRUE(PrepareToReceiveLaunchParams(web_contents));
+  EXPECT_TRUE(WaitAndExposeLaunchParamsToWindow(web_contents, "launchParams1"));
 
-  // Set up a Promise that resolves to launchParams, when launchQueue's consumer
-  // callback is called.
-  EXPECT_TRUE(content::ExecuteScript(
-      web_contents,
-      "window.launchParamsPromise = new Promise(resolve => {"
-      "  window.resolveLaunchParamsPromise = resolve;"
-      "});"
-      "launchQueue.setConsumer(launchParams => {"
-      "  window.resolveLaunchParamsPromise(launchParams);"
-      "});"));
+  // Check launch directory and launch files are correct.
+  EXPECT_EQ("directory",
+            GetJsStatementValueAsString(web_contents,
+                                        "window.launchParams1.files[0].kind"));
+  EXPECT_EQ(temp_directory.GetPath().BaseName().AsUTF8Unsafe(),
+            GetJsStatementValueAsString(web_contents,
+                                        "window.launchParams1.files[0].name"));
+  EXPECT_EQ("file", GetJsStatementValueAsString(
+                        web_contents, "window.launchParams1.files[1].kind"));
+  EXPECT_EQ(temp_file_path.BaseName().AsUTF8Unsafe(),
+            GetJsStatementValueAsString(web_contents,
+                                        "window.launchParams1.files[1].name"));
 
-  // Wait for launch. Set window.firstLaunchParams for inspection.
-  EXPECT_TRUE(
-      content::ExecuteScript(web_contents,
-                             "window.launchParamsPromise.then(launchParams => {"
-                             "  window.firstLaunchParams = launchParams;"
-                             "});"));
-
-  // Check launch directory is correct.
-  std::string kind;
-  EXPECT_TRUE(content::ExecuteScriptAndExtractString(
-      web_contents,
-      "domAutomationController.send(window.firstLaunchParams."
-      "files[0].kind)",
-      &kind));
-  EXPECT_EQ("directory", kind);
-
-  std::string file_name;
-  EXPECT_TRUE(content::ExecuteScriptAndExtractString(
-      web_contents,
-      "domAutomationController.send(window.firstLaunchParams.files[0].name)",
-      &file_name));
-  EXPECT_EQ(temp_directory.GetPath().BaseName().AsUTF8Unsafe(), file_name);
-
-  // Check launch files are correct.
-  EXPECT_TRUE(content::ExecuteScriptAndExtractString(
-      web_contents,
-      "domAutomationController.send(window.firstLaunchParams.files[1].kind)",
-      &kind));
-  EXPECT_EQ("file", kind);
-
-  EXPECT_TRUE(content::ExecuteScriptAndExtractString(
-      web_contents,
-      "domAutomationController.send(window.firstLaunchParams.files[1].name)",
-      &file_name));
-  EXPECT_EQ(temp_file_path.BaseName().AsUTF8Unsafe(), file_name);
-
-  // Reset the Promise to get second launchParams.
-  EXPECT_TRUE(content::ExecuteScript(
-      web_contents,
-      "window.launchParamsPromise = new Promise(resolve => {"
-      "  window.resolveLaunchParamsPromise = resolve;"
-      "});"));
-
-  // Second Launch.
+  // Second launch.
   base::ScopedTempDir temp_directory2;
   ASSERT_TRUE(temp_directory2.CreateUniqueTempDir());
   base::FilePath temp_file_path2;
   ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_directory2.GetPath(),
                                              &temp_file_path2));
-  params.launch_files = {temp_file_path2};
-  content::WebContents* web_contents2 =
-      apps::AppServiceProxyFactory::GetForProfile(browser()->profile())
-          ->BrowserAppLauncher()
-          ->LaunchAppWithParams(params);
 
-  // WebContents* should be the same because we are passing launchParams to the
-  // opened application.
-  EXPECT_EQ(web_contents, web_contents2);
+  // The second launch reuses the opened application. It should pass the
+  // launchParams to the opened page, and return the same content::WebContents*.
+  EXPECT_TRUE(PrepareToReceiveLaunchParams(web_contents));
+  EXPECT_EQ(web_contents, LaunchAppWithoutWaiting({temp_file_path2}));
+  EXPECT_TRUE(WaitAndExposeLaunchParamsToWindow(web_contents, "launchParams2"));
 
-  // Wait for launch. Sets window.secondLaunchParams for inspection.
-  EXPECT_TRUE(
-      content::ExecuteScript(web_contents,
-                             "window.launchParamsPromise.then(launchParams => {"
-                             "  window.secondLaunchParams = launchParams;"
-                             "});"));
-
-  // Second launch_dir and launch_files are passed to the opened application.
-  EXPECT_TRUE(content::ExecuteScriptAndExtractString(
-      web_contents,
-      "domAutomationController.send(window.secondLaunchParams.files[0]."
-      "kind)",
-      &kind));
-  EXPECT_EQ("directory", kind);
-
-  EXPECT_TRUE(content::ExecuteScriptAndExtractString(
-      web_contents,
-      "domAutomationController.send(window.secondLaunchParams.files[0].name)",
-      &file_name));
-  EXPECT_EQ(temp_directory2.GetPath().BaseName().AsUTF8Unsafe(), file_name);
-
-  EXPECT_TRUE(content::ExecuteScriptAndExtractString(
-      web_contents,
-      "domAutomationController.send(window.secondLaunchParams.files[1]."
-      "kind)",
-      &kind));
-  EXPECT_EQ("file", kind);
-
-  EXPECT_TRUE(content::ExecuteScriptAndExtractString(
-      web_contents,
-      "domAutomationController.send(window.secondLaunchParams.files[1].name)",
-      &file_name));
-  EXPECT_EQ(temp_file_path2.BaseName().AsUTF8Unsafe(), file_name);
+  // Check the second launch directory and launch files are correct.
+  EXPECT_EQ("directory",
+            GetJsStatementValueAsString(web_contents,
+                                        "window.launchParams2.files[0].kind"));
+  EXPECT_EQ(temp_directory2.GetPath().BaseName().AsUTF8Unsafe(),
+            GetJsStatementValueAsString(web_contents,
+                                        "window.launchParams2.files[0].name"));
+  EXPECT_EQ("file", GetJsStatementValueAsString(
+                        web_contents, "window.launchParams2.files[1].kind"));
+  EXPECT_EQ(temp_file_path2.BaseName().AsUTF8Unsafe(),
+            GetJsStatementValueAsString(web_contents,
+                                        "window.launchParams2.files[1].name"));
 }
 
 IN_PROC_BROWSER_TEST_P(SystemWebAppManagerLaunchDirectoryBrowserTest,
@@ -742,36 +685,6 @@
     return volume_->mount_path().AppendASCII(file_name);
   }
 
-  // Wait for System App Launch.
-  content::WebContents* WaitForApplicationLaunch(
-      Profile* profile,
-      const base::FilePath& launch_file) {
-    apps::AppLaunchParams params = LaunchParamsForApp(GetMockAppType());
-    params.source = apps::mojom::AppLaunchSource::kSourceChromeInternal;
-    params.launch_files = {launch_file};
-
-    content::WebContents* web_contents = LaunchApp(params);
-    EXPECT_TRUE(WaitForLoadStop(web_contents));
-    return web_contents;
-  }
-
-  // Install a File Handling API launchQueue consumer, which copies the provided
-  // launch params to a JavaScript global named |js_property_name|.
-  bool WaitAndExposeLaunchParamsToWindow(
-      content::WebContents* web_contents,
-      const std::string js_property_name = "launchParams") {
-    bool launch_params_received;
-    EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
-        web_contents,
-        content::JsReplace("launchQueue.setConsumer(launchParams => {"
-                           "  window[$1] = launchParams;"
-                           "  domAutomationController.send(true);"
-                           "});",
-                           js_property_name),
-        &launch_params_received));
-    return launch_params_received;
-  }
-
  private:
   base::WeakPtr<file_manager::Volume> volume_;
 };
@@ -790,20 +703,14 @@
   const base::FilePath launch_file =
       GetFileSystemProviderFilePath(kTestGifFile);
 
-  content::WebContents* web_contents =
-      WaitForApplicationLaunch(profile, launch_file);
-
+  content::WebContents* web_contents = LaunchApp({launch_file});
+  EXPECT_TRUE(PrepareToReceiveLaunchParams(web_contents));
   EXPECT_TRUE(WaitAndExposeLaunchParamsToWindow(web_contents, "launchParams"));
 
-  // Check the launch file is the one we expect.
-  std::string file_name;
-  EXPECT_TRUE(content::ExecuteScriptAndExtractString(
-      web_contents,
-      "domAutomationController.send(window.launchParams.files[1].name);",
-      &file_name));
-  EXPECT_EQ(kTestGifFile, file_name);
-
-  // Check we can read the file by looking for GIF file signature.
+  // Check the launch file is the one we expect, and we can read the file.
+  EXPECT_EQ(kTestGifFile,
+            GetJsStatementValueAsString(web_contents,
+                                        "window.launchParams.files[1].name"));
   EXPECT_TRUE(
       CheckFileIsGif(web_contents, "window.launchParams.files[1].getFile()"));
 
@@ -838,9 +745,11 @@
 
   WaitForTestSystemAppInstall();
   InstallTestFileSystemProvider(profile);
-  content::WebContents* web_contents = WaitForApplicationLaunch(
-      profile, GetFileSystemProviderFilePath("readonly.png"));
 
+  content::WebContents* web_contents =
+      LaunchApp({GetFileSystemProviderFilePath("readonly.png")});
+
+  EXPECT_TRUE(PrepareToReceiveLaunchParams(web_contents));
   EXPECT_TRUE(WaitAndExposeLaunchParamsToWindow(web_contents, "launchParams"));
 
   // Try to write the file.
@@ -860,9 +769,11 @@
 
   WaitForTestSystemAppInstall();
   InstallTestFileSystemProvider(profile);
-  content::WebContents* web_contents = WaitForApplicationLaunch(
-      profile, GetFileSystemProviderFilePath("readonly.png"));
 
+  content::WebContents* web_contents =
+      LaunchApp({GetFileSystemProviderFilePath("readonly.png")});
+
+  EXPECT_TRUE(PrepareToReceiveLaunchParams(web_contents));
   EXPECT_TRUE(WaitAndExposeLaunchParamsToWindow(web_contents, "launchParams"));
 
   // Try to delete the file.
@@ -897,6 +808,40 @@
 
   ~SystemWebAppManagerFileHandlingOriginTrialsBrowserTest() override = default;
 
+  content::WebContents* LaunchWithTestFiles() {
+    // Create temporary directory and files.
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    base::ScopedTempDir temp_directory;
+    CHECK(temp_directory.CreateUniqueTempDir());
+    base::FilePath temp_file_path;
+    CHECK(base::CreateTemporaryFileInDir(temp_directory.GetPath(),
+                                         &temp_file_path));
+
+    // Launch the App.
+    apps::AppLaunchParams params = LaunchParamsForApp(GetMockAppType());
+    params.source = apps::mojom::AppLaunchSource::kSourceChromeInternal;
+    params.launch_files = {temp_file_path};
+
+    content::TestNavigationObserver navigation_observer(
+        GetLaunchURL(GetMockAppType()));
+    navigation_observer.StartWatchingNewWebContents();
+    content::WebContents* web_contents = LaunchApp(params);
+    navigation_observer.Wait();
+
+    return web_contents;
+  }
+
+  bool WaitForLaunchParam(content::WebContents* web_contents) {
+    bool promise_resolved = false;
+    EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
+        web_contents,
+        "launchQueue.setConsumer(launchParams => {"
+        "  domAutomationController.send(true);"
+        "});",
+        &promise_resolved));
+    return promise_resolved;
+  }
+
  private:
   url::Origin GetOrigin(const GURL& url) { return url::Origin::Create(url); }
 };
@@ -904,38 +849,9 @@
 IN_PROC_BROWSER_TEST_P(SystemWebAppManagerFileHandlingOriginTrialsBrowserTest,
                        FileHandlingWorks) {
   WaitForTestSystemAppInstall();
-  apps::AppLaunchParams params = LaunchParamsForApp(GetMockAppType());
-  params.source = apps::mojom::AppLaunchSource::kSourceChromeInternal;
 
-  base::ScopedAllowBlockingForTesting allow_blocking;
-  base::ScopedTempDir temp_directory;
-  ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
-  base::FilePath temp_file_path;
-  ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_directory.GetPath(),
-                                             &temp_file_path));
-
-  const GURL& launch_url = WebAppProvider::Get(browser()->profile())
-                               ->registrar()
-                               .GetAppLaunchURL(params.app_id);
-
-  params.launch_files = {temp_file_path};
-  content::TestNavigationObserver navigation_observer(launch_url);
-  navigation_observer.StartWatchingNewWebContents();
-  content::WebContents* web_contents =
-      apps::AppServiceProxyFactory::GetForProfile(browser()->profile())
-          ->BrowserAppLauncher()
-          ->LaunchAppWithParams(params);
-  navigation_observer.Wait();
-
-  // Wait for the Promise to resolve.
-  bool promise_resolved = false;
-  EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
-      web_contents,
-      "launchQueue.setConsumer(launchParams => {"
-      "  domAutomationController.send(true);"
-      "});",
-      &promise_resolved));
-  EXPECT_TRUE(promise_resolved);
+  content::WebContents* web_contents = LaunchWithTestFiles();
+  EXPECT_TRUE(WaitForLaunchParam(web_contents));
 }
 
 class SystemWebAppManagerNotShownInLauncherTest
@@ -1439,17 +1355,22 @@
 
 INSTANTIATE_TEST_SUITE_P(All,
                          SystemWebAppManagerLaunchFilesBrowserTest,
-                         testing::Bool());
+                         ::testing::Values(ProviderType::kBookmarkApps,
+                                           ProviderType::kWebApps),
+                         ProviderTypeParamToString);
 
 INSTANTIATE_TEST_SUITE_P(All,
                          SystemWebAppManagerLaunchDirectoryBrowserTest,
-                         testing::Bool());
+                         ::testing::Values(ProviderType::kBookmarkApps,
+                                           ProviderType::kWebApps),
+                         ProviderTypeParamToString);
 
 #if defined(OS_CHROMEOS)
 INSTANTIATE_TEST_SUITE_P(
     All,
     SystemWebAppManagerLaunchDirectoryFileSystemProviderBrowserTest,
-    testing::Bool());
+    ::testing::Values(ProviderType::kBookmarkApps, ProviderType::kWebApps),
+    ProviderTypeParamToString);
 #endif
 
 INSTANTIATE_TEST_SUITE_P(All,
diff --git a/chrome/browser/web_applications/system_web_app_manager_browsertest.h b/chrome/browser/web_applications/system_web_app_manager_browsertest.h
index f2b19a9..41f4562 100644
--- a/chrome/browser/web_applications/system_web_app_manager_browsertest.h
+++ b/chrome/browser/web_applications/system_web_app_manager_browsertest.h
@@ -45,18 +45,21 @@
   // TestSystemWebAppManager if initialized with |install_mock| true.
   SystemWebAppManager& GetManager();
 
-  // Return SystemAppType of mocked app, only valid if |install_mock| is true.
+  // Returns SystemAppType of mocked app, only valid if |install_mock| is true.
   SystemAppType GetMockAppType();
 
+  // Returns the the launch URL for an installed |system_app_type|.
+  const GURL& GetLaunchURL(SystemAppType system_app_type);
+
   void WaitForTestSystemAppInstall();
 
-  // Wait for system apps to install, then launch one. Waits for launched app
+  // Waits for system apps to install, then launches one. Waits for launched app
   // to load.
   content::WebContents* WaitForSystemAppInstallAndLoad(
       SystemAppType system_app_type);
 
-  // Wait for system apps to install, then launch one. Returns the browser that
-  // contains it.
+  // Waits for system apps to install, then launches one. Returns the browser
+  // that contains it.
   Browser* WaitForSystemAppInstallAndLaunch(SystemAppType system_app_type);
 
   // Creates a default AppLaunchParams for |system_app_type|. Launches a window.
diff --git a/chrome/browser/web_applications/system_web_app_manager_unittest.cc b/chrome/browser/web_applications/system_web_app_manager_unittest.cc
index ae50b8a..2cf7b15 100644
--- a/chrome/browser/web_applications/system_web_app_manager_unittest.cc
+++ b/chrome/browser/web_applications/system_web_app_manager_unittest.cc
@@ -9,6 +9,7 @@
 
 #include "base/callback.h"
 #include "base/run_loop.h"
+#include "base/strings/utf_string_conversions.h"
 #include "base/test/bind_test_util.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/web_applications/components/externally_installed_web_app_prefs.h"
@@ -95,6 +96,9 @@
     auto manifest = std::make_unique<blink::Manifest>();
     manifest->start_url = GetSystemAppDataForTask(task_index).url;
     manifest->scope = GetSystemAppDataForTask(task_index).url;
+    manifest->short_name =
+        base::NullableString16(base::ASCIIToUTF16("Manifest SWA Name"), false);
+
     blink::Manifest::ImageResource icon;
     icon.src = GetSystemAppDataForTask(task_index).icon_url;
     icon.purpose.push_back(blink::Manifest::ImageResource::Purpose::ANY);
@@ -271,6 +275,7 @@
 
     auto web_app = std::make_unique<WebApp>(app_id);
     web_app->SetLaunchUrl(launch_url);
+    web_app->SetName("App Name");
     web_app->AddSource(source_type);
     web_app->SetDisplayMode(DisplayMode::kStandalone);
     web_app->SetUserDisplayMode(DisplayMode::kStandalone);
diff --git a/chrome/browser/web_applications/test/test_data_retriever.cc b/chrome/browser/web_applications/test/test_data_retriever.cc
index 84b51c7..89cc8ea 100644
--- a/chrome/browser/web_applications/test/test_data_retriever.cc
+++ b/chrome/browser/web_applications/test/test_data_retriever.cc
@@ -8,6 +8,7 @@
 
 #include "base/bind.h"
 #include "base/check.h"
+#include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/web_applications/components/web_app_constants.h"
 #include "chrome/common/web_application_info.h"
 #include "third_party/blink/public/common/manifest/manifest.h"
@@ -100,6 +101,8 @@
   manifest->start_url = url;
   manifest->scope = scope;
   manifest->display = DisplayMode::kStandalone;
+  manifest->short_name =
+      base::NullableString16(base::ASCIIToUTF16("Manifest Name"), false);
 
   SetManifest(std::move(manifest), /*is_installable=*/true);
 
diff --git a/chrome/browser/web_applications/web_app_icon_manager_browsertest.cc b/chrome/browser/web_applications/web_app_icon_manager_browsertest.cc
index 880b4e7..106ec34 100644
--- a/chrome/browser/web_applications/web_app_icon_manager_browsertest.cc
+++ b/chrome/browser/web_applications/web_app_icon_manager_browsertest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "base/macros.h"
+#include "base/strings/utf_string_conversions.h"
 #include "base/test/bind_test_util.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/apps/app_service/app_launch_params.h"
@@ -68,6 +69,7 @@
         std::make_unique<WebApplicationInfo>();
     web_application_info->app_url = app_url;
     web_application_info->scope = app_url.GetWithoutFilename();
+    web_application_info->title = base::ASCIIToUTF16("App Name");
     web_application_info->open_as_window = true;
 
     {
diff --git a/chrome/browser/web_applications/web_app_install_finalizer.cc b/chrome/browser/web_applications/web_app_install_finalizer.cc
index 2ba2f65..64e4566 100644
--- a/chrome/browser/web_applications/web_app_install_finalizer.cc
+++ b/chrome/browser/web_applications/web_app_install_finalizer.cc
@@ -424,7 +424,9 @@
     const WebApplicationInfo& web_app_info,
     std::unique_ptr<WebApp> web_app,
     CommitCallback commit_callback) {
+  DCHECK(!web_app_info.title.empty());
   web_app->SetName(base::UTF16ToUTF8(web_app_info.title));
+
   web_app->SetDisplayMode(web_app_info.display_mode);
   web_app->SetDescription(base::UTF16ToUTF8(web_app_info.description));
   web_app->SetScope(web_app_info.scope);
diff --git a/chrome/browser/web_applications/web_app_install_manager_unittest.cc b/chrome/browser/web_applications/web_app_install_manager_unittest.cc
index 20ccb8c9..f22e2f22f 100644
--- a/chrome/browser/web_applications/web_app_install_manager_unittest.cc
+++ b/chrome/browser/web_applications/web_app_install_manager_unittest.cc
@@ -225,6 +225,7 @@
 
     web_app->AddSource(source);
     web_app->SetUserDisplayMode(user_display_mode);
+    web_app->SetName("Name");
     return web_app;
   }
 
@@ -342,6 +343,7 @@
     auto server_web_application_info = std::make_unique<WebApplicationInfo>();
     server_web_application_info->app_url = url;
     server_web_application_info->open_as_window = server_open_as_window;
+    server_web_application_info->title = base::ASCIIToUTF16("Server Name");
     InstallResult result = InstallBookmarkAppFromSync(
         bookmark_app_id, std::move(server_web_application_info));
 
@@ -1106,6 +1108,7 @@
 
   auto server_web_app_info = std::make_unique<WebApplicationInfo>();
   server_web_app_info->app_url = url;
+  server_web_app_info->title = base::ASCIIToUTF16("Server Name");
   {
     WebApplicationIconInfo server_icon1_info;
     server_icon1_info.url = icon1_url;
@@ -1187,6 +1190,7 @@
 
   auto server_web_app_info = std::make_unique<WebApplicationInfo>();
   server_web_app_info->app_url = url;
+  server_web_app_info->title = base::ASCIIToUTF16("Server Name");
   server_web_app_info->generated_icon_color = SK_ColorBLUE;
   {
     WebApplicationIconInfo server_icon1_info;
@@ -1240,6 +1244,7 @@
 
   auto web_app_info = std::make_unique<WebApplicationInfo>();
   web_app_info->app_url = url;
+  web_app_info->title = base::ASCIIToUTF16("Server Name");
   // All icons will get the E letter drawn into a rounded yellow background.
   web_app_info->generated_icon_color = SK_ColorYELLOW;
 
@@ -1276,6 +1281,7 @@
 
   auto server_web_app_info = std::make_unique<WebApplicationInfo>();
   server_web_app_info->app_url = old_url;
+  server_web_app_info->title = base::ASCIIToUTF16("Server Name");
 
   // WebAppInstallTask finishes with kExpectedAppIdCheckFailed but
   // WebAppInstallManager falls back to web application info, received from the
@@ -1311,6 +1317,7 @@
 
   auto server_web_application_info = std::make_unique<WebApplicationInfo>();
   server_web_application_info->app_url = url;
+  server_web_application_info->title = base::ASCIIToUTF16("Server Name");
 
   // Call InstallBookmarkAppFromSync while WebAppInstallManager is not yet
   // started.
@@ -1461,6 +1468,7 @@
 
   auto server_bookmark_app_info = std::make_unique<WebApplicationInfo>();
   server_bookmark_app_info->app_url = url;
+  server_bookmark_app_info->title = base::ASCIIToUTF16("Server Name");
 
   bool bookmark_app_installed = false;
   bool web_app_install_returns_early = false;
@@ -1524,6 +1532,7 @@
 
   auto server_bookmark_app_info = std::make_unique<WebApplicationInfo>();
   server_bookmark_app_info->app_url = url;
+  server_bookmark_app_info->title = base::ASCIIToUTF16("Server Name");
 
   bool bookmark_app_installed = false;
   bool web_app_install_returns_early = false;
diff --git a/chrome/browser/web_applications/web_app_install_task_unittest.cc b/chrome/browser/web_applications/web_app_install_task_unittest.cc
index 268407a9..90c20c26 100644
--- a/chrome/browser/web_applications/web_app_install_task_unittest.cc
+++ b/chrome/browser/web_applications/web_app_install_task_unittest.cc
@@ -211,6 +211,8 @@
 
     auto manifest = std::make_unique<blink::Manifest>();
     manifest->start_url = url;
+    manifest->short_name =
+        base::NullableString16(base::UTF8ToUTF16("Manifest Name"), false);
     data_retriever_->SetManifest(std::move(manifest), /*is_installable=*/true);
 
     data_retriever_->SetIcons(IconsMap{});
@@ -321,6 +323,10 @@
   WebAppRegistrar& registrar() { return controller().registrar(); }
   TestAppShortcutManager& test_shortcut_manager() { return *shortcut_manager_; }
   TestWebAppUrlLoader& url_loader() { return *url_loader_; }
+  TestDataRetriever& data_retriever() {
+    DCHECK(data_retriever_);
+    return *data_retriever_;
+  }
 
   std::unique_ptr<WebAppIconManager> icon_manager_;
   std::unique_ptr<WebAppInstallTask> install_task_;
@@ -382,7 +388,7 @@
   EXPECT_TRUE(AreWebAppsUserInstallable(profile()));
 
   const GURL url = GURL("https://example.com/scope/path");
-  const std::string name = "Name";
+  const std::string manifest_name = "Manifest Name";
   const std::string description = "Description";
   const GURL scope = GURL("https://example.com/scope");
   const base::Optional<SkColor> theme_color = 0xAABBCCDD;
@@ -390,9 +396,18 @@
 
   const AppId app_id = GenerateAppIdFromURL(url);
 
-  CreateDefaultDataToRetrieve(url, scope);
-  CreateRendererAppInfo(url, name, description, /*scope*/ GURL{}, theme_color,
+  CreateRendererAppInfo(url, "Renderer Name", description, /*scope*/ GURL{},
+                        theme_color,
                         /*open_as_window*/ true);
+  {
+    auto manifest = std::make_unique<blink::Manifest>();
+    manifest->start_url = url;
+    manifest->scope = scope;
+    manifest->short_name =
+        base::NullableString16(base::ASCIIToUTF16(manifest_name), false);
+
+    data_retriever().SetManifest(std::move(manifest), /*is_installable=*/true);
+  }
 
   base::RunLoop run_loop;
   bool callback_called = false;
@@ -416,7 +431,7 @@
   EXPECT_NE(nullptr, web_app);
 
   EXPECT_EQ(app_id, web_app->app_id());
-  EXPECT_EQ(name, web_app->name());
+  EXPECT_EQ(manifest_name, web_app->name());
   EXPECT_EQ(description, web_app->description());
   EXPECT_EQ(url, web_app->launch_url());
   EXPECT_EQ(scope, web_app->scope());
@@ -429,13 +444,22 @@
   const AppId app_id = GenerateAppIdFromURL(url);
 
   CreateDefaultDataToRetrieve(url);
-  CreateRendererAppInfo(url, "Name", "Description");
+  CreateRendererAppInfo(url, "Renderer Name", "Renderer Description");
 
   const AppId installed_web_app = InstallWebAppFromManifestWithFallback();
   EXPECT_EQ(app_id, installed_web_app);
 
   // Force reinstall:
-  CreateRendererAppInfo(url, "Name2", "Description2");
+  CreateRendererAppInfo(url, "Renderer Name2", "Renderer Description2");
+  {
+    auto manifest = std::make_unique<blink::Manifest>();
+    manifest->start_url = url;
+    manifest->scope = url;
+    manifest->short_name =
+        base::NullableString16(base::ASCIIToUTF16("Manifest Name2"), false);
+
+    data_retriever().SetManifest(std::move(manifest), /*is_installable=*/true);
+  }
 
   base::RunLoop run_loop;
   bool callback_called = false;
@@ -449,8 +473,8 @@
             EXPECT_EQ(InstallResultCode::kSuccessNewInstall, code);
             EXPECT_EQ(app_id, force_installed_app_id);
             const WebApp* web_app = registrar().GetAppById(app_id);
-            EXPECT_EQ(web_app->name(), "Name2");
-            EXPECT_EQ(web_app->description(), "Description2");
+            EXPECT_EQ(web_app->name(), "Manifest Name2");
+            EXPECT_EQ(web_app->description(), "Renderer Description2");
             callback_called = true;
             run_loop.Quit();
           }));
@@ -806,6 +830,8 @@
 
   auto manifest = std::make_unique<blink::Manifest>();
   manifest->start_url = url;
+  manifest->short_name =
+      base::NullableString16(base::UTF8ToUTF16("Server Name"), false);
 
   data_retriever_->SetManifest(std::move(manifest), /*is_installable=*/true);
 
@@ -1204,7 +1230,6 @@
   EXPECT_NE(nullptr, web_app);
 
   EXPECT_EQ(app_id, web_app->app_id());
-  EXPECT_EQ(name, web_app->name());
   EXPECT_EQ(description, web_app->description());
   EXPECT_EQ(url, web_app->launch_url());
   EXPECT_EQ(scope, web_app->scope());
@@ -1251,7 +1276,6 @@
   EXPECT_NE(nullptr, web_app);
 
   EXPECT_EQ(app_id, web_app->app_id());
-  EXPECT_EQ(name, web_app->name());
   EXPECT_EQ(description, web_app->description());
   EXPECT_EQ(url, web_app->launch_url());
   EXPECT_EQ(scope, web_app->scope());
diff --git a/chrome/browser/web_applications/web_app_registrar_unittest.cc b/chrome/browser/web_applications/web_app_registrar_unittest.cc
index be4c97fd..205640d 100644
--- a/chrome/browser/web_applications/web_app_registrar_unittest.cc
+++ b/chrome/browser/web_applications/web_app_registrar_unittest.cc
@@ -40,6 +40,7 @@
     auto web_app = std::make_unique<WebApp>(app_id);
     web_app->AddSource(Source::kSync);
     web_app->SetLaunchUrl(GURL(url));
+    web_app->SetName("Name" + base::NumberToString(i));
     web_app->SetDisplayMode(DisplayMode::kBrowser);
     web_app->SetUserDisplayMode(DisplayMode::kBrowser);
 
@@ -191,6 +192,7 @@
   web_app2->SetDisplayMode(DisplayMode::kBrowser);
   web_app2->SetUserDisplayMode(DisplayMode::kBrowser);
   web_app2->SetLaunchUrl(launch_url2);
+  web_app2->SetName(name);
 
   EXPECT_EQ(nullptr, registrar().GetAppById(app_id));
   EXPECT_EQ(nullptr, registrar().GetAppById(app_id2));
diff --git a/chrome/browser/web_applications/web_app_sync_bridge.cc b/chrome/browser/web_applications/web_app_sync_bridge.cc
index b86d979..f3c5d65f 100644
--- a/chrome/browser/web_applications/web_app_sync_bridge.cc
+++ b/chrome/browser/web_applications/web_app_sync_bridge.cc
@@ -46,8 +46,15 @@
 }
 
 std::unique_ptr<syncer::EntityData> CreateSyncEntityData(const WebApp& app) {
+  // The Sync System doesn't allow empty entity_data name.
+  DCHECK(!app.name().empty());
+
   auto entity_data = std::make_unique<syncer::EntityData>();
   entity_data->name = app.name();
+  // TODO(crbug.com/1103570): Remove this fallback later.
+  if (entity_data->name.empty())
+    entity_data->name = app.launch_url().spec();
+
   *(entity_data->specifics.mutable_web_app()) = WebAppToSyncProto(app);
   return entity_data;
 }
@@ -271,11 +278,15 @@
 void WebAppSyncBridge::CheckRegistryUpdateData(
     const RegistryUpdateData& update_data) const {
 #if DCHECK_IS_ON()
-  for (const std::unique_ptr<WebApp>& web_app : update_data.apps_to_create)
+  for (const std::unique_ptr<WebApp>& web_app : update_data.apps_to_create) {
     DCHECK(!registrar_->GetAppById(web_app->app_id()));
+    DCHECK(!web_app->name().empty());
+  }
 
-  for (const std::unique_ptr<WebApp>& web_app : update_data.apps_to_update)
+  for (const std::unique_ptr<WebApp>& web_app : update_data.apps_to_update) {
     DCHECK(registrar_->GetAppById(web_app->app_id()));
+    DCHECK(!web_app->name().empty());
+  }
 
   for (const AppId& app_id : update_data.apps_to_delete)
     DCHECK(registrar_->GetAppById(app_id));
@@ -460,6 +471,13 @@
     // full local data and all the icons.
     web_app->SetIsInSyncInstall(true);
 
+    // The sync system requires non-empty name, populate temp name from
+    // the fallback sync data name:
+    web_app->SetName(specifics.name());
+    // Or use syncer::EntityData::name as a last resort.
+    if (web_app->name().empty())
+      web_app->SetName(change.data().name);
+
     ApplySyncDataToApp(specifics, web_app.get());
 
     // For a new app, automatically choose if we want to install it locally.
diff --git a/chrome/browser/web_applications/web_app_sync_bridge_unittest.cc b/chrome/browser/web_applications/web_app_sync_bridge_unittest.cc
index a552c23..ba7952a 100644
--- a/chrome/browser/web_applications/web_app_sync_bridge_unittest.cc
+++ b/chrome/browser/web_applications/web_app_sync_bridge_unittest.cc
@@ -54,6 +54,7 @@
   // ApplySyncDataToApp enforces kSync source on |app_to_apply_sync_data|.
   ApplySyncDataToApp(entity_data.specifics.web_app(),
                      app_to_apply_sync_data.get());
+  app_to_apply_sync_data->SetName(entity_data.name);
   return expected_app == *app_to_apply_sync_data;
 }
 
@@ -102,6 +103,7 @@
   auto web_app = std::make_unique<WebApp>(app_id);
   web_app->AddSource(Source::kSync);
   web_app->SetLaunchUrl(launch_url);
+  web_app->SetName("Name");
   web_app->SetUserDisplayMode(DisplayMode::kStandalone);
   return web_app;
 }
diff --git a/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/SurfaceScope.java b/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/SurfaceScope.java
index 237afa9..0aa61d3f 100644
--- a/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/SurfaceScope.java
+++ b/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/SurfaceScope.java
@@ -22,4 +22,7 @@
     default SurfaceRenderer provideSurfaceRenderer() {
         return null;
     }
+
+    default void replaceDataStoreEntry(String key, byte[] data) {}
+    default void removeDataStoreEntry(String key) {}
 }
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 7b37b1f..51d19b4 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-master-1594662802-fdeeec39ffe5ca406713d5654e95a154ee25e0d8.profdata
+chrome-mac-master-1594749428-a9bf8891c97f732f274a0fa0ac7b0886b75624ed.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 84f82cc..7f8d90c 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-master-1594648594-97cb9ce3cb39a74bf15fa7f9f08c38efdf753597.profdata
+chrome-win32-master-1594670295-0a4f7f614ea3d9de6fcc3b138431b409b2b96371.profdata
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 028370d..e2f3897 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -50,8 +50,6 @@
 
 #if !defined(OS_ANDROID)
 // App Service related flags. See components/services/app_service/README.md.
-const base::Feature kAppServiceInstanceRegistry{
-    "AppServiceInstanceRegistry", base::FEATURE_ENABLED_BY_DEFAULT};
 const base::Feature kAppServiceIntentHandling{"AppServiceIntentHandling",
                                               base::FEATURE_ENABLED_BY_DEFAULT};
 const base::Feature kAppServiceAdaptiveIcon{"AppServiceAdaptiveIcon",
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 2b34090..c78f2c7 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -52,8 +52,6 @@
 
 #if !defined(OS_ANDROID)
 COMPONENT_EXPORT(CHROME_FEATURES)
-extern const base::Feature kAppServiceInstanceRegistry;
-COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kAppServiceIntentHandling;
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kAppServiceAdaptiveIcon;
diff --git a/chrome/common/extensions/api/autotest_private.idl b/chrome/common/extensions/api/autotest_private.idl
index 3eb6e5c..7c5eeb8 100644
--- a/chrome/common/extensions/api/autotest_private.idl
+++ b/chrome/common/extensions/api/autotest_private.idl
@@ -692,17 +692,6 @@
     // |callback|: Called when the operation has completed.
     static void importCrostini(DOMString path, VoidCallback callback);
 
-    // TODO(b/156566782): Remove this after M85 branches.
-    // Installs Plugin VM via the installer and then launches the VM.
-    // |imageUrl|: URL to the image to install.
-    // |imageHash|: Hash for the provided image.
-    // |licenseKey|: License key for Plugin VM.
-    // |callback|: Called when the operation has completed.
-    static void installPluginVM(DOMString imageUrl,
-                                DOMString imageHash,
-                                DOMString licenseKey,
-                                VoidCallback callback);
-
     // Sets mock Plugin VM policy.
     // |imageUrl|: URL to the image to install.
     // |imageHash|: Hash for the provided image.
@@ -1038,6 +1027,13 @@
         long numCompletions,
         long timeout,
         VoidCallback callback);
+
+    // Disables the automation feature. Note that the event handlers and caches
+    // of automation nodes still remain in the test extension and so the next
+    // automation.getDesktop will miss initialization. The caller should ensure
+    // invalidation of those information (i.e. reloading the entire background
+    // page).
+    static void disableAutomation(VoidCallback callback);
   };
 
   interface Events {
diff --git a/chrome/common/extensions/api/networking_cast_private.idl b/chrome/common/extensions/api/networking_cast_private.idl
index c4b45a3..cc45de35 100644
--- a/chrome/common/extensions/api/networking_cast_private.idl
+++ b/chrome/common/extensions/api/networking_cast_private.idl
@@ -4,7 +4,7 @@
 
 // The networking.castPrivate API is a private API that exposes networking
 // utilities needed by cast extension and setup app.
-[nodoc] namespace networking.castPrivate {
+[nodoc, deprecated] namespace networking.castPrivate {
   enum TDLSStatus {
     // TDLS is connected.
     CONNECTED,
@@ -78,20 +78,23 @@
                                      DOMString data,
                                      StringCallback callback);
 
-    // Enables TDLS for WiFi traffic with a specified peer if available.
-    // |ip_or_mac_address|: The IP or MAC address of the peer with which to
+    // Deprecated. Enables TDLS for WiFi traffic with a specified peer if
+    // available. |ip_or_mac_address|: The IP or MAC address of the peer with
+    // which to
     //     enable a TDLS connection.
     // |enabled|: If true, enable TDLS, otherwise disable TDLS.
     // |callback|: A callback function that receives a string with an error or
     //     the current TDLS status.
+    [nodoc, deprecated]
     static void setWifiTDLSEnabledState(DOMString ip_or_mac_address,
                                         boolean enabled,
                                         optional TDLSStatusCallback callback);
 
-    // Returns the current TDLS status for the specified peer.
+    // Deprecated. Returns the current TDLS status for the specified peer.
     // |ip_or_mac_address|: The IP or MAC address of the peer.
     // |callback|: A callback function that receives a string with the current
     //     TDLS status.
+    [nodoc, deprecated]
     static void getWifiTDLSStatus(DOMString ip_or_mac_address,
                                   TDLSStatusCallback callback);
   };
diff --git a/chrome/common/extensions/docs/server2/app.yaml b/chrome/common/extensions/docs/server2/app.yaml
index 64e3c7b..449f19e 100644
--- a/chrome/common/extensions/docs/server2/app.yaml
+++ b/chrome/common/extensions/docs/server2/app.yaml
@@ -1,5 +1,5 @@
 application: chrome-apps-doc
-version: 3-68-0
+version: 3-69-0
 runtime: python27
 api_version: 1
 threadsafe: false
diff --git a/chrome/common/extensions/manifest_handlers/settings_overrides_handler.cc b/chrome/common/extensions/manifest_handlers/settings_overrides_handler.cc
index e05224f..2eb8436c 100644
--- a/chrome/common/extensions/manifest_handlers/settings_overrides_handler.cc
+++ b/chrome/common/extensions/manifest_handlers/settings_overrides_handler.cc
@@ -115,12 +115,9 @@
 }
 
 std::string FormatUrlForDisplay(const GURL& url) {
-  base::StringPiece host = url.host_piece();
   // A www. prefix is not informative and thus not worth the limited real estate
   // in the permissions UI.
-  // TODO(catmullings): Ideally, we wouldn't be using custom code to format URLs
-  // here, since we have a number of methods that do that more universally.
-  return base::UTF16ToUTF8(url_formatter::StripWWW(base::UTF8ToUTF16(host)));
+  return url_formatter::StripWWW(url.host());
 }
 
 }  // namespace
diff --git a/chrome/common/profiler/stack_sampling_browsertest.cc b/chrome/common/profiler/stack_sampling_browsertest.cc
index 4aed013..36ac29b 100644
--- a/chrome/common/profiler/stack_sampling_browsertest.cc
+++ b/chrome/common/profiler/stack_sampling_browsertest.cc
@@ -8,6 +8,7 @@
 #include "base/no_destructor.h"
 #include "base/run_loop.h"
 #include "base/synchronization/lock.h"
+#include "base/test/scoped_run_loop_timeout.h"
 #include "base/thread_annotations.h"
 #include "base/threading/platform_thread.h"
 #include "chrome/common/channel_info.h"
@@ -25,6 +26,9 @@
 // thread while FetchProfiles() is invoked on the main thread.
 class ProfileInterceptor {
  public:
+  using Predicate =
+      base::RepeatingCallback<bool(const metrics::SampledProfile&)>;
+
   // Get the static object instance. This object must leak because there is no
   // synchronization between it and the profiler thread which can invoke
   // Intercept at any time.
@@ -33,23 +37,64 @@
     return *instance;
   }
 
-  void Intercept(metrics::SampledProfile profile) {
+  void SetFoundClosure(const base::RepeatingClosure& found_closure) {
     base::AutoLock lock(lock_);
-    profiles_.push_back(std::move(profile));
+    found_closure_ = found_closure;
   }
 
-  std::vector<metrics::SampledProfile> FetchProfiles() {
+  void SetPredicate(const Predicate& predicate) {
     base::AutoLock lock(lock_);
-    std::vector<metrics::SampledProfile> profiles;
-    profiles.swap(profiles_);
-    return profiles;
+    predicate_ = predicate;
+  }
+
+  bool ProfileWasFound() {
+    base::AutoLock lock(lock_);
+    return found_profile_;
+  }
+
+  void Intercept(metrics::SampledProfile profile) {
+    base::AutoLock lock(lock_);
+    if (predicate_.is_null()) {
+      pending_profiles_.push_back(profile);
+    } else {
+      CHECK(!found_closure_.is_null());
+      if (predicate_.Run(profile)) {
+        OnProfileFound();
+        return;
+      }
+      for (const auto& pending_profile : pending_profiles_) {
+        if (predicate_.Run(pending_profile)) {
+          OnProfileFound();
+          break;
+        }
+      }
+      pending_profiles_.clear();
+    }
   }
 
  private:
+  void OnProfileFound() EXCLUSIVE_LOCKS_REQUIRED(lock_) {
+    found_profile_ = true;
+    found_closure_.Run();
+  }
+
   base::Lock lock_;
-  std::vector<metrics::SampledProfile> profiles_ GUARDED_BY(lock_);
+  base::RepeatingClosure found_closure_ GUARDED_BY(lock_);
+  Predicate predicate_ GUARDED_BY(lock_);
+  std::vector<metrics::SampledProfile> pending_profiles_ GUARDED_BY(lock_);
+  bool found_profile_ GUARDED_BY(lock_) = false;
 };
 
+// Returns true if |profile| has the specified properties |trigger_event|,
+// |process| and |thread|. Returns false otherwise.
+bool MatchesProfile(metrics::SampledProfile::TriggerEvent trigger_event,
+                    metrics::Process process,
+                    metrics::Thread thread,
+                    const metrics::SampledProfile& profile) {
+  return profile.trigger_event() == trigger_event &&
+         profile.process() == process && profile.thread() == thread;
+}
+
 class StackSamplingBrowserTest : public InProcessBrowserTest {
  public:
   void SetUp() override {
@@ -69,8 +114,7 @@
   }
 };
 
-// Wait for a profile with the specified properties. Checks once per second
-// until the profile is seen or we time out.
+// Wait for a profile with the specified properties.
 bool WaitForProfile(metrics::SampledProfile::TriggerEvent trigger_event,
                     metrics::Process process,
                     metrics::Thread thread) {
@@ -85,33 +129,17 @@
     default:
       return true;
   }
+  auto predicate =
+      base::BindRepeating(&MatchesProfile, trigger_event, process, thread);
 
-  // The profiling duration is one second when enabling browser test mode via
-  // the kStartStackProfilerBrowserTest switch argument. We expect to see the
-  // profiles shortly thereafter, but wait up to 30 seconds to give ample time
-  // to avoid flaky failures.
-  int seconds_to_wait = 30;
-  do {
-    std::vector<metrics::SampledProfile> profiles =
-        ProfileInterceptor::GetInstance().FetchProfiles();
-    const bool was_received =
-        std::find_if(profiles.begin(), profiles.end(),
-                     [&](const metrics::SampledProfile& profile) {
-                       return profile.trigger_event() == trigger_event &&
-                              profile.process() == process &&
-                              profile.thread() == thread;
-                     }) != profiles.end();
-    if (was_received)
-      return true;
-    base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1));
-    // Manually spinning message loop is fine here because the main thread
-    // message loop will not be continuously busy at Chrome startup, and we will
-    // spin it enough over 30 seconds to ensure that any necessary processing is
-    // done.
-    base::RunLoop().RunUntilIdle();
-  } while (--seconds_to_wait > 0);
+  base::RunLoop run_loop;
+  ProfileInterceptor::GetInstance().SetFoundClosure(run_loop.QuitClosure());
+  ProfileInterceptor::GetInstance().SetPredicate(predicate);
 
-  return false;
+  base::test::ScopedRunLoopTimeout timeout(FROM_HERE,
+                                           base::TimeDelta::FromSeconds(30));
+  run_loop.Run();
+  return ProfileInterceptor::GetInstance().ProfileWasFound();
 }
 
 }  // namespace
diff --git a/chrome/installer/setup/install_worker.cc b/chrome/installer/setup/install_worker.cc
index c58bc8d9..7ad0077 100644
--- a/chrome/installer/setup/install_worker.cc
+++ b/chrome/installer/setup/install_worker.cc
@@ -562,10 +562,13 @@
                                google_update::kRegNameField,
                                InstallUtil::GetDisplayName(),
                                true);  // overwrite name also
-  list->AddSetRegValueWorkItem(root, clients_key, KEY_WOW64_32KEY,
-                               google_update::kRegOopcrashesField,
-                               static_cast<DWORD>(1),
-                               false);  // set during first install
+
+  // Clean up when updating from M85 and older installs.
+  // Can be removed after newer stable builds have been in the wild
+  // enough to have done a reasonable degree of clean up.
+  list->AddDeleteRegValueWorkItem(root, clients_key, KEY_WOW64_32KEY,
+                                  L"oopcrashes");
+
   if (add_language_identifier) {
     // Write the language identifier of the current translation.  Omaha's set of
     // languages is a superset of Chrome's set of translations with this one
diff --git a/chrome/installer/util/google_update_constants.cc b/chrome/installer/util/google_update_constants.cc
index ba074e1ac..e27a24a7 100644
--- a/chrome/installer/util/google_update_constants.cc
+++ b/chrome/installer/util/google_update_constants.cc
@@ -52,7 +52,6 @@
 const wchar_t kRegNameField[] = L"name";
 const wchar_t kRegOemInstallField[] = L"oeminstall";
 const wchar_t kRegOldVersionField[] = L"opv";
-const wchar_t kRegOopcrashesField[] = L"oopcrashes";
 const wchar_t kRegPathField[] = L"path";
 const wchar_t kRegRLZBrandField[] = L"brand";
 const wchar_t kRegRLZReactivationBrandField[] = L"reactivationbrand";
diff --git a/chrome/installer/util/google_update_constants.h b/chrome/installer/util/google_update_constants.h
index 38cec37..c13d0b81 100644
--- a/chrome/installer/util/google_update_constants.h
+++ b/chrome/installer/util/google_update_constants.h
@@ -67,7 +67,6 @@
 extern const wchar_t kRegNameField[];
 extern const wchar_t kRegOemInstallField[];
 extern const wchar_t kRegOldVersionField[];
-extern const wchar_t kRegOopcrashesField[];
 extern const wchar_t kRegPathField[];
 extern const wchar_t kRegProfilesActive[];
 extern const wchar_t kRegProfilesSignedIn[];
diff --git a/chrome/services/sharing/BUILD.gn b/chrome/services/sharing/BUILD.gn
index 297ccdb..a0d08b6a 100644
--- a/chrome/services/sharing/BUILD.gn
+++ b/chrome/services/sharing/BUILD.gn
@@ -30,8 +30,10 @@
   ]
 
   deps = [
+    "nearby/platform_v2",
     "nearby_decoder",
     "//jingle:webrtc_glue",
+    "//third_party/nearby:core",
     "//third_party/webrtc_overrides:webrtc_component",
   ]
 
@@ -47,8 +49,10 @@
   testonly = true
 
   sources = [
-    "nearby/test/mock_nearby_connections_host.cc",
-    "nearby/test/mock_nearby_connections_host.h",
+    "nearby/test/mock_bluetooth_adapter.cc",
+    "nearby/test/mock_bluetooth_adapter.h",
+    "nearby/test/mock_webrtc_signaling_messenger.cc",
+    "nearby/test/mock_webrtc_signaling_messenger.h",
     "sharing_impl_unittest.cc",
     "webrtc/sharing_webrtc_connection_integration_test.cc",
     "webrtc/sharing_webrtc_connection_unittest.cc",
diff --git a/chrome/services/sharing/nearby/DEPS b/chrome/services/sharing/nearby/DEPS
new file mode 100644
index 0000000..71260e5
--- /dev/null
+++ b/chrome/services/sharing/nearby/DEPS
@@ -0,0 +1,7 @@
+# Copyright (c) 2020 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_rules = [
+  "+third_party/nearby",
+]
diff --git a/chrome/services/sharing/nearby/nearby_connections.cc b/chrome/services/sharing/nearby/nearby_connections.cc
index 0d18664..13a1b76 100644
--- a/chrome/services/sharing/nearby/nearby_connections.cc
+++ b/chrome/services/sharing/nearby/nearby_connections.cc
@@ -4,27 +4,50 @@
 
 #include "chrome/services/sharing/nearby/nearby_connections.h"
 
+#include "base/run_loop.h"
+
 namespace location {
 namespace nearby {
 namespace connections {
 
+// Should only be accessed by objects within lifetime of NearbyConnections.
+NearbyConnections* g_instance = nullptr;
+
+// static
+NearbyConnections& NearbyConnections::GetInstance() {
+  DCHECK(g_instance);
+  return *g_instance;
+}
+
 NearbyConnections::NearbyConnections(
     mojo::PendingReceiver<mojom::NearbyConnections> nearby_connections,
-    mojo::PendingRemote<mojom::NearbyConnectionsHost> host,
+    mojom::NearbyConnectionsDependenciesPtr dependencies,
     base::OnceClosure on_disconnect)
     : nearby_connections_(this, std::move(nearby_connections)),
-      host_(std::move(host)),
       on_disconnect_(std::move(on_disconnect)) {
   nearby_connections_.set_disconnect_handler(base::BindOnce(
       &NearbyConnections::OnDisconnect, weak_ptr_factory_.GetWeakPtr()));
-  host_.set_disconnect_handler(base::BindOnce(&NearbyConnections::OnDisconnect,
-                                              weak_ptr_factory_.GetWeakPtr()));
-  host_->GetBluetoothAdapter(
-      base::BindOnce(&NearbyConnections::OnGetBluetoothAdapter,
-                     weak_ptr_factory_.GetWeakPtr()));
+
+  if (dependencies->bluetooth_adapter) {
+    bluetooth_adapter_.Bind(std::move(dependencies->bluetooth_adapter));
+    bluetooth_adapter_.set_disconnect_handler(base::BindOnce(
+        &NearbyConnections::OnDisconnect, weak_ptr_factory_.GetWeakPtr()));
+  }
+  webrtc_signaling_messenger_.Bind(
+      std::move(dependencies->webrtc_signaling_messenger));
+  webrtc_signaling_messenger_.set_disconnect_handler(base::BindOnce(
+      &NearbyConnections::OnDisconnect, weak_ptr_factory_.GetWeakPtr()));
+
+  // There should only be one instance of NearbyConnections in a process.
+  DCHECK(!g_instance);
+  g_instance = this;
+  // TODO(alexchau): Create Core here after g_instance is set.
 }
 
-NearbyConnections::~NearbyConnections() = default;
+NearbyConnections::~NearbyConnections() {
+  // TODO(alexhcau): Destroy Core here before g_instance is reset.
+  g_instance = nullptr;
+}
 
 void NearbyConnections::OnDisconnect() {
   if (on_disconnect_)
@@ -32,24 +55,19 @@
   // Note: |this| might be destroyed here.
 }
 
-void NearbyConnections::OnGetBluetoothAdapter(
-    mojo::PendingRemote<::bluetooth::mojom::Adapter> pending_remote_adapter) {
-  if (!pending_remote_adapter.is_valid()) {
-    VLOG(1) << __func__
-            << " Received invalid Bluetooh adapter in utility process";
-    return;
-  }
+bluetooth::mojom::Adapter* NearbyConnections::GetBluetoothAdapter() {
+  if (!bluetooth_adapter_.is_bound())
+    return nullptr;
 
-  VLOG(1) << __func__ << " Received Bluetooh adapter in utility process";
+  return bluetooth_adapter_.get();
+}
 
-  bluetooth_adapter_.Bind(std::move(pending_remote_adapter));
-  bluetooth_adapter_->GetInfo(
-      base::BindOnce([](bluetooth::mojom::AdapterInfoPtr info) {
-        VLOG(1) << __func__ << "Bluetooh AdapterInfo name: '" << info->name
-                << "' system_name: '" << info->system_name << "' address: '"
-                << info->address << "' present: " << info->present
-                << " powered: " << info->powered;
-      }));
+sharing::mojom::WebRtcSignalingMessenger*
+NearbyConnections::GetWebRtcSignalingMessenger() {
+  if (!webrtc_signaling_messenger_.is_bound())
+    return nullptr;
+
+  return webrtc_signaling_messenger_.get();
 }
 
 }  // namespace connections
diff --git a/chrome/services/sharing/nearby/nearby_connections.h b/chrome/services/sharing/nearby/nearby_connections.h
index df4d665..e610775 100644
--- a/chrome/services/sharing/nearby/nearby_connections.h
+++ b/chrome/services/sharing/nearby/nearby_connections.h
@@ -7,7 +7,9 @@
 
 #include "base/callback_forward.h"
 #include "base/memory/weak_ptr.h"
+#include "base/run_loop.h"
 #include "chrome/services/sharing/public/mojom/nearby_connections.mojom.h"
+#include "chrome/services/sharing/public/mojom/webrtc_signaling_messenger.mojom.h"
 #include "device/bluetooth/public/mojom/adapter.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
@@ -33,22 +35,29 @@
   // destroy this instance.
   NearbyConnections(
       mojo::PendingReceiver<mojom::NearbyConnections> nearby_connections,
-      mojo::PendingRemote<mojom::NearbyConnectionsHost> host,
+      mojom::NearbyConnectionsDependenciesPtr dependencies,
       base::OnceClosure on_disconnect);
   NearbyConnections(const NearbyConnections&) = delete;
   NearbyConnections& operator=(const NearbyConnections&) = delete;
   ~NearbyConnections() override;
 
+  // Should only be used by objects within lifetime of NearbyConnections.
+  static NearbyConnections& GetInstance();
+
+  bluetooth::mojom::Adapter* GetBluetoothAdapter();
+  sharing::mojom::WebRtcSignalingMessenger* GetWebRtcSignalingMessenger();
+
  private:
   void OnDisconnect();
-  void OnGetBluetoothAdapter(
-      mojo::PendingRemote<::bluetooth::mojom::Adapter> pending_remote_adapter);
 
   mojo::Receiver<mojom::NearbyConnections> nearby_connections_;
-  mojo::Remote<mojom::NearbyConnectionsHost> host_;
-  mojo::Remote<bluetooth::mojom::Adapter> bluetooth_adapter_;
   base::OnceClosure on_disconnect_;
 
+  // Medium dependencies:
+  mojo::Remote<bluetooth::mojom::Adapter> bluetooth_adapter_;
+  mojo::Remote<sharing::mojom::WebRtcSignalingMessenger>
+      webrtc_signaling_messenger_;
+
   base::WeakPtrFactory<NearbyConnections> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/services/sharing/nearby/platform_v2/BUILD.gn b/chrome/services/sharing/nearby/platform_v2/BUILD.gn
new file mode 100644
index 0000000..c255814
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/BUILD.gn
@@ -0,0 +1,60 @@
+# Copyright 2020 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.
+
+source_set("platform_v2") {
+  sources = [
+    "atomic_boolean.cc",
+    "atomic_boolean.h",
+    "atomic_uint32.cc",
+    "atomic_uint32.h",
+    "condition_variable.cc",
+    "condition_variable.h",
+    "count_down_latch.cc",
+    "count_down_latch.h",
+    "crypto.cc",
+    "log_message.cc",
+    "log_message.h",
+    "mutex.cc",
+    "mutex.h",
+    "platform.cc",
+    "recursive_mutex.cc",
+    "recursive_mutex.h",
+    "scheduled_executor.cc",
+    "scheduled_executor.h",
+    "submittable_executor.cc",
+    "submittable_executor.h",
+    "system_clock.cc",
+  ]
+
+  public_deps = [
+    "//base",
+    "//third_party/nearby:platform_v2_api_platform",
+    "//third_party/nearby:platform_v2_impl_shared_file",
+  ]
+
+  deps = [ "//crypto" ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+
+  sources = [
+    "atomic_boolean_unittest.cc",
+    "atomic_uint32_unittest.cc",
+    "condition_variable_unittest.cc",
+    "count_down_latch_unittest.cc",
+    "crypto_unittest.cc",
+    "multi_thread_executor_unittest.cc",
+    "mutex_unittest.cc",
+    "recursive_mutex_unittest.cc",
+    "scheduled_executor_unittest.cc",
+    "single_thread_executor_unittest.cc",
+  ]
+
+  deps = [
+    ":platform_v2",
+    "//base/test:test_support",
+    "//testing/gtest",
+  ]
+}
diff --git a/chrome/services/sharing/nearby/platform_v2/atomic_boolean.cc b/chrome/services/sharing/nearby/platform_v2/atomic_boolean.cc
new file mode 100644
index 0000000..dd74618c
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/atomic_boolean.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/services/sharing/nearby/platform_v2/atomic_boolean.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+AtomicBoolean::AtomicBoolean(bool initial_value) : value_(initial_value) {}
+
+AtomicBoolean::~AtomicBoolean() = default;
+
+bool AtomicBoolean::Get() const {
+  return value_.load();
+}
+
+bool AtomicBoolean::Set(bool value) {
+  return value_.exchange(value);
+}
+
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
diff --git a/chrome/services/sharing/nearby/platform_v2/atomic_boolean.h b/chrome/services/sharing/nearby/platform_v2/atomic_boolean.h
new file mode 100644
index 0000000..7ba5a7e
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/atomic_boolean.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_ATOMIC_BOOLEAN_H_
+#define CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_ATOMIC_BOOLEAN_H_
+
+#include <atomic>
+
+#include "third_party/nearby/src/cpp/platform_v2/api/atomic_boolean.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+// Concrete AtomicBoolean implementation.
+class AtomicBoolean : public api::AtomicBoolean {
+ public:
+  explicit AtomicBoolean(bool initial_value);
+  ~AtomicBoolean() override;
+
+  AtomicBoolean(const AtomicBoolean&) = delete;
+  AtomicBoolean& operator=(const AtomicBoolean&) = delete;
+
+  // api::AtomicBoolean:
+  bool Get() const override;
+  bool Set(bool value) override;
+
+ private:
+  std::atomic_bool value_;
+};
+
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
+
+#endif  // CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_ATOMIC_BOOLEAN_H_
diff --git a/chrome/services/sharing/nearby/platform_v2/atomic_boolean_unittest.cc b/chrome/services/sharing/nearby/platform_v2/atomic_boolean_unittest.cc
new file mode 100644
index 0000000..b702d90
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/atomic_boolean_unittest.cc
@@ -0,0 +1,98 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/services/sharing/nearby/platform_v2/atomic_boolean.h"
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "base/task/post_task.h"
+#include "base/task_runner.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+class AtomicBooleanTest : public testing::Test {
+ protected:
+  void VerifyAtomicBooleanOnThread(bool value) {
+    base::RunLoop run_loop;
+    auto callback = base::BindLambdaForTesting([&, value]() {
+      VerifyAtomicBoolean(value);
+      run_loop.Quit();
+    });
+    task_runner_->PostTask(FROM_HERE, std::move(callback));
+
+    run_loop.Run();
+  }
+
+  void SetAtomicBooleanOnThread(bool value) {
+    base::RunLoop run_loop;
+    auto callback = base::BindLambdaForTesting([&, value]() {
+      SetAtomicBoolean(value);
+      run_loop.Quit();
+    });
+    task_runner_->PostTask(FROM_HERE, std::move(callback));
+
+    run_loop.Run();
+  }
+
+  void VerifyAtomicBoolean(bool value) { EXPECT_EQ(value, GetAtomicBoolean()); }
+
+  void SetAtomicBoolean(bool value) { atomic_boolean_.Set(value); }
+
+  bool GetAtomicBoolean() { return atomic_boolean_.Get(); }
+
+  base::test::TaskEnvironment task_environment_;
+
+ private:
+  AtomicBoolean atomic_boolean_{false};
+  scoped_refptr<base::TaskRunner> task_runner_ =
+      base::ThreadPool::CreateTaskRunner({base::MayBlock()});
+};
+
+TEST_F(AtomicBooleanTest, SetOnSameThread) {
+  VerifyAtomicBoolean(false);
+
+  SetAtomicBoolean(true);
+  VerifyAtomicBoolean(true);
+}
+
+TEST_F(AtomicBooleanTest, MultipleSetGetOnSameThread) {
+  VerifyAtomicBoolean(false);
+
+  SetAtomicBoolean(true);
+  VerifyAtomicBoolean(true);
+
+  SetAtomicBoolean(true);
+  VerifyAtomicBoolean(true);
+
+  SetAtomicBoolean(false);
+  VerifyAtomicBoolean(false);
+
+  SetAtomicBoolean(true);
+  VerifyAtomicBoolean(true);
+}
+
+TEST_F(AtomicBooleanTest, SetOnNewThread) {
+  VerifyAtomicBoolean(false);
+
+  SetAtomicBooleanOnThread(true);
+  VerifyAtomicBoolean(true);
+}
+
+TEST_F(AtomicBooleanTest, GetOnNewThread) {
+  VerifyAtomicBoolean(false);
+
+  SetAtomicBoolean(true);
+  VerifyAtomicBoolean(true);
+
+  VerifyAtomicBooleanOnThread(true);
+}
+
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
diff --git a/chrome/services/sharing/nearby/platform_v2/atomic_uint32.cc b/chrome/services/sharing/nearby/platform_v2/atomic_uint32.cc
new file mode 100644
index 0000000..ccfe50f
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/atomic_uint32.cc
@@ -0,0 +1,26 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/services/sharing/nearby/platform_v2/atomic_uint32.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+AtomicUint32::AtomicUint32(std::int32_t initial_value)
+    : value_(initial_value) {}
+
+AtomicUint32::~AtomicUint32() = default;
+
+std::uint32_t AtomicUint32::Get() const {
+  return value_;
+}
+
+void AtomicUint32::Set(std::uint32_t value) {
+  value_ = value;
+}
+
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
diff --git a/chrome/services/sharing/nearby/platform_v2/atomic_uint32.h b/chrome/services/sharing/nearby/platform_v2/atomic_uint32.h
new file mode 100644
index 0000000..ce189e6
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/atomic_uint32.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_ATOMIC_UINT32_H_
+#define CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_ATOMIC_UINT32_H_
+
+#include <atomic>
+
+#include "third_party/nearby/src/cpp/platform_v2/api/atomic_reference.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+// Concrete AtomicUint32 implementation.
+class AtomicUint32 : public api::AtomicUint32 {
+ public:
+  explicit AtomicUint32(std::int32_t initial_value);
+  ~AtomicUint32() override;
+
+  AtomicUint32(const AtomicUint32&) = delete;
+  AtomicUint32& operator=(const AtomicUint32&) = delete;
+
+  // api::AtomicUint32:
+  std::uint32_t Get() const override;
+  void Set(std::uint32_t value) override;
+
+ private:
+  std::atomic<std::uint32_t> value_;
+};
+
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
+
+#endif  // CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_ATOMIC_UINT32_H_
diff --git a/chrome/services/sharing/nearby/platform_v2/atomic_uint32_unittest.cc b/chrome/services/sharing/nearby/platform_v2/atomic_uint32_unittest.cc
new file mode 100644
index 0000000..6991596
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/atomic_uint32_unittest.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/services/sharing/nearby/platform_v2/atomic_uint32.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "base/task/post_task.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+class AtomicUint32Test : public testing::Test {
+ protected:
+  void VerifyValueInThread(std::uint32_t value) {
+    base::RunLoop run_loop;
+    auto callback = base::BindLambdaForTesting([&, value]() {
+      VerifyValue(value);
+      run_loop.Quit();
+    });
+    task_runner_->PostTask(FROM_HERE, std::move(callback));
+
+    run_loop.Run();
+  }
+
+  void SetValueInThread(std::uint32_t value) {
+    base::RunLoop run_loop;
+    auto callback = base::BindLambdaForTesting([&, value]() {
+      SetValue(value);
+      run_loop.Quit();
+    });
+    task_runner_->PostTask(FROM_HERE, std::move(callback));
+
+    run_loop.Run();
+  }
+
+  void VerifyValue(std::uint32_t value) { EXPECT_EQ(value, GetValue()); }
+
+  void SetValue(std::uint32_t value) { atomic_reference_.Set(value); }
+
+  std::uint32_t GetValue() { return atomic_reference_.Get(); }
+
+  std::uint32_t initial_value_ = 8964;
+  base::test::TaskEnvironment task_environment_;
+
+ private:
+  AtomicUint32 atomic_reference_{initial_value_};
+  scoped_refptr<base::TaskRunner> task_runner_ =
+      base::ThreadPool::CreateTaskRunner({base::MayBlock()});
+};
+
+TEST_F(AtomicUint32Test, GetOnSameThread) {
+  VerifyValue(initial_value_);
+}
+
+TEST_F(AtomicUint32Test, SetGetOnSameThread) {
+  std::uint32_t new_token = 51;
+  SetValue(new_token);
+  VerifyValue(new_token);
+}
+
+TEST_F(AtomicUint32Test, SetOnNewThread) {
+  std::uint32_t new_thread_token = 51;
+  SetValueInThread(new_thread_token);
+  VerifyValue(new_thread_token);
+}
+
+TEST_F(AtomicUint32Test, GetOnNewThread) {
+  std::uint32_t new_token = 51;
+  SetValue(new_token);
+  VerifyValueInThread(new_token);
+}
+
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
diff --git a/chrome/services/sharing/nearby/platform_v2/condition_variable.cc b/chrome/services/sharing/nearby/platform_v2/condition_variable.cc
new file mode 100644
index 0000000..37c14f4
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/condition_variable.cc
@@ -0,0 +1,36 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/services/sharing/nearby/platform_v2/condition_variable.h"
+
+#include "base/time/time.h"
+#include "chrome/services/sharing/nearby/platform_v2/mutex.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+ConditionVariable::ConditionVariable(Mutex* mutex)
+    : mutex_(mutex), condition_variable_(&mutex_->lock_) {}
+
+ConditionVariable::~ConditionVariable() = default;
+
+Exception ConditionVariable::Wait() {
+  condition_variable_.Wait();
+  return {Exception::kSuccess};
+}
+
+Exception ConditionVariable::Wait(absl::Duration timeout) {
+  condition_variable_.TimedWait(
+      base::TimeDelta::FromMicroseconds(absl::ToInt64Microseconds(timeout)));
+  return {Exception::kSuccess};
+}
+
+void ConditionVariable::Notify() {
+  condition_variable_.Broadcast();
+}
+
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
diff --git a/chrome/services/sharing/nearby/platform_v2/condition_variable.h b/chrome/services/sharing/nearby/platform_v2/condition_variable.h
new file mode 100644
index 0000000..4daefe78
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/condition_variable.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_CONDITION_VARIABLE_H_
+#define CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_CONDITION_VARIABLE_H_
+
+#include "base/synchronization/condition_variable.h"
+#include "third_party/nearby/src/cpp/platform_v2/api/condition_variable.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+class Mutex;
+
+// Concrete ConditionVariable implementation.
+class ConditionVariable : public api::ConditionVariable {
+ public:
+  explicit ConditionVariable(Mutex* mutex);
+  ~ConditionVariable() override;
+
+  ConditionVariable(const ConditionVariable&) = delete;
+  ConditionVariable& operator=(const ConditionVariable&) = delete;
+
+  // api::ConditionVariable:
+  Exception Wait() override;
+  Exception Wait(absl::Duration timeout) override;
+  void Notify() override;
+
+ private:
+  Mutex* mutex_;
+  base::ConditionVariable condition_variable_;
+};
+
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
+
+#endif  // CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_CONDITION_VARIABLE_H_
diff --git a/chrome/services/sharing/nearby/platform_v2/condition_variable_unittest.cc b/chrome/services/sharing/nearby/platform_v2/condition_variable_unittest.cc
new file mode 100644
index 0000000..12fe846
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/condition_variable_unittest.cc
@@ -0,0 +1,118 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/services/sharing/nearby/platform_v2/condition_variable.h"
+
+#include "base/bind.h"
+#include "base/containers/flat_set.h"
+#include "base/run_loop.h"
+#include "base/task/post_task.h"
+#include "base/task/thread_pool.h"
+#include "base/task_runner.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/gtest_util.h"
+#include "base/test/task_environment.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/unguessable_token.h"
+#include "chrome/services/sharing/nearby/platform_v2/mutex.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+class ConditionVariableTest : public testing::Test {
+ protected:
+  void WaitOnConditionVariableFromParallelSequence(
+      base::RunLoop& run_loop,
+      const base::UnguessableToken& attempt_id) {
+    base::RunLoop wait_run_loop;
+    auto callback = base::BindLambdaForTesting([&]() {
+      base::ScopedAllowBaseSyncPrimitivesForTesting allow_sync_primitives;
+
+      wait_run_loop.Quit();
+
+      mutex_.Lock();
+      condition_variable_.Wait();
+      mutex_.Unlock();
+
+      {
+        base::AutoLock al(coordination_lock_);
+        successful_run_attempts_.insert(attempt_id);
+      }
+
+      run_loop.Quit();
+    });
+    task_runner_->PostTask(FROM_HERE, std::move(callback));
+
+    // Wait until callback has started.
+    wait_run_loop.Run();
+  }
+
+  bool HasSuccessfullyRunWithAttemptId(
+      const base::UnguessableToken& attempt_id) {
+    base::AutoLock al(coordination_lock_);
+    return base::Contains(successful_run_attempts_, attempt_id);
+  }
+
+  void NotifyConditionVariable() {
+    mutex_.Lock();
+    condition_variable_.Notify();
+    mutex_.Unlock();
+  }
+
+  base::test::TaskEnvironment task_environment_;
+
+ private:
+  Mutex mutex_;
+  ConditionVariable condition_variable_{&mutex_};
+  scoped_refptr<base::TaskRunner> task_runner_ =
+      base::ThreadPool::CreateTaskRunner({base::MayBlock()});
+  base::Lock coordination_lock_;
+  base::flat_set<base::UnguessableToken> successful_run_attempts_;
+};
+
+TEST_F(ConditionVariableTest, SingleSequence_BlocksOnWaitAndUnblocksOnNotify) {
+  base::RunLoop run_loop;
+  base::UnguessableToken attempt_id = base::UnguessableToken::Create();
+  WaitOnConditionVariableFromParallelSequence(run_loop, attempt_id);
+  ASSERT_FALSE(HasSuccessfullyRunWithAttemptId(attempt_id));
+
+  // Should unblock after notify().
+  NotifyConditionVariable();
+
+  run_loop.Run();
+  EXPECT_TRUE(HasSuccessfullyRunWithAttemptId(attempt_id));
+}
+
+TEST_F(ConditionVariableTest,
+       MultipleSequences_BlocksOnWaitAndUnblocksOnNotify) {
+  base::RunLoop run_loop_1;
+  base::UnguessableToken attempt_id_1 = base::UnguessableToken::Create();
+  WaitOnConditionVariableFromParallelSequence(run_loop_1, attempt_id_1);
+  base::RunLoop run_loop_2;
+  base::UnguessableToken attempt_id_2 = base::UnguessableToken::Create();
+  WaitOnConditionVariableFromParallelSequence(run_loop_2, attempt_id_2);
+  base::RunLoop run_loop_3;
+  base::UnguessableToken attempt_id_3 = base::UnguessableToken::Create();
+  WaitOnConditionVariableFromParallelSequence(run_loop_3, attempt_id_3);
+
+  ASSERT_FALSE(HasSuccessfullyRunWithAttemptId(attempt_id_1));
+  ASSERT_FALSE(HasSuccessfullyRunWithAttemptId(attempt_id_2));
+  ASSERT_FALSE(HasSuccessfullyRunWithAttemptId(attempt_id_3));
+
+  // All should unblock after notify().
+  NotifyConditionVariable();
+
+  run_loop_1.Run();
+  run_loop_2.Run();
+  run_loop_3.Run();
+  EXPECT_TRUE(HasSuccessfullyRunWithAttemptId(attempt_id_1));
+  EXPECT_TRUE(HasSuccessfullyRunWithAttemptId(attempt_id_2));
+  EXPECT_TRUE(HasSuccessfullyRunWithAttemptId(attempt_id_3));
+}
+
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
diff --git a/chrome/services/sharing/nearby/platform_v2/count_down_latch.cc b/chrome/services/sharing/nearby/platform_v2/count_down_latch.cc
new file mode 100644
index 0000000..c75933b
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/count_down_latch.cc
@@ -0,0 +1,45 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/services/sharing/nearby/platform_v2/count_down_latch.h"
+
+#include "base/time/time.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+CountDownLatch::CountDownLatch(int32_t count)
+    : count_(count),
+      count_waitable_event_(
+          base::WaitableEvent::ResetPolicy::MANUAL,
+          count_.IsZero() ? base::WaitableEvent::InitialState::SIGNALED
+                          : base::WaitableEvent::InitialState::NOT_SIGNALED) {
+  DCHECK_GE(count, 0);
+}
+
+CountDownLatch::~CountDownLatch() = default;
+
+Exception CountDownLatch::Await() {
+  count_waitable_event_.Wait();
+  return {Exception::kSuccess};
+}
+
+ExceptionOr<bool> CountDownLatch::Await(absl::Duration timeout) {
+  // Return true if |count_waitable_event_| is signaled before TimedAwait()
+  // times out. Otherwise, this returns false due to timing out.
+  return ExceptionOr<bool>(count_waitable_event_.TimedWait(
+      base::TimeDelta::FromMicroseconds(absl::ToInt64Microseconds(timeout))));
+}
+
+void CountDownLatch::CountDown() {
+  // Signal |count_waitable_event_| when (and only the one exact time when)
+  // |count_| decrements to 0.
+  if (!count_.Decrement())
+    count_waitable_event_.Signal();
+}
+
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
diff --git a/chrome/services/sharing/nearby/platform_v2/count_down_latch.h b/chrome/services/sharing/nearby/platform_v2/count_down_latch.h
new file mode 100644
index 0000000..d85e6bb
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/count_down_latch.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_COUNT_DOWN_LATCH_H_
+#define CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_COUNT_DOWN_LATCH_H_
+
+#include "base/atomic_ref_count.h"
+#include "base/synchronization/waitable_event.h"
+#include "third_party/nearby/src/cpp/platform_v2/api/count_down_latch.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+// Concrete CountDownLatch implementation.
+class CountDownLatch : public api::CountDownLatch {
+ public:
+  explicit CountDownLatch(int32_t count);
+  ~CountDownLatch() override;
+
+  CountDownLatch(const CountDownLatch&) = delete;
+  CountDownLatch& operator=(const CountDownLatch&) = delete;
+
+  // api::CountDownLatch:
+  Exception Await() override;
+  ExceptionOr<bool> Await(absl::Duration timeout) override;
+  void CountDown() override;
+
+ private:
+  base::AtomicRefCount count_;
+  base::WaitableEvent count_waitable_event_;
+};
+
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
+
+#endif  // CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_COUNT_DOWN_LATCH_H_
diff --git a/chrome/services/sharing/nearby/platform_v2/count_down_latch_unittest.cc b/chrome/services/sharing/nearby/platform_v2/count_down_latch_unittest.cc
new file mode 100644
index 0000000..64339f89
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/count_down_latch_unittest.cc
@@ -0,0 +1,214 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/services/sharing/nearby/platform_v2/count_down_latch.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/containers/flat_map.h"
+#include "base/optional.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "base/synchronization/lock.h"
+#include "base/task/post_task.h"
+#include "base/task/thread_pool.h"
+#include "base/task_runner.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/task_environment.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/unguessable_token.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+class CountDownLatchTest : public testing::Test {
+ protected:
+  void InitializeCountDownLatch(int32_t count) {
+    count_down_latch_ = std::make_unique<CountDownLatch>(count);
+  }
+
+  void PostAwaitTask(base::RunLoop& run_loop,
+                     const base::UnguessableToken& attempt_id,
+                     base::Optional<base::TimeDelta> timeout) {
+    base::RunLoop wait_run_loop;
+    auto callback = base::BindLambdaForTesting([&, timeout]() {
+      base::ScopedAllowBaseSyncPrimitivesForTesting allow_base_sync_primitives;
+
+      wait_run_loop.Quit();
+
+      base::Optional<ExceptionOr<bool>> result;
+      if (timeout) {
+        result = count_down_latch_->Await(
+            absl::Microseconds(timeout->InMicroseconds()));
+      } else {
+        result = ExceptionOr<bool>(count_down_latch_->Await());
+      }
+
+      {
+        base::AutoLock al(map_lock_);
+        id_to_result_map_.emplace(attempt_id, *result);
+      }
+
+      run_loop.Quit();
+    });
+    task_runner_->PostTask(FROM_HERE, std::move(callback));
+
+    // Wait until callback has started.
+    wait_run_loop.Run();
+  }
+
+  size_t MapSize() {
+    base::AutoLock al(map_lock_);
+    return id_to_result_map_.size();
+  }
+
+  void VerifyExceptionResultForAttemptId(
+      const base::UnguessableToken& id,
+      const Exception::Value& expected_exception) {
+    base::AutoLock al(map_lock_);
+    ASSERT_TRUE(base::Contains(id_to_result_map_, id));
+    ASSERT_TRUE(id_to_result_map_[id]);
+    EXPECT_EQ(expected_exception, id_to_result_map_[id]->exception());
+  }
+
+  void VerifyBoolResultForAttemptId(const base::UnguessableToken& id,
+                                    bool expected_result) {
+    base::AutoLock al(map_lock_);
+    ASSERT_TRUE(base::Contains(id_to_result_map_, id));
+    ASSERT_TRUE(id_to_result_map_[id]);
+    EXPECT_TRUE(id_to_result_map_[id]->ok());
+    EXPECT_EQ(expected_result, id_to_result_map_[id]->result());
+  }
+
+  base::test::TaskEnvironment task_environment_;
+  base::Lock map_lock_;
+  base::flat_map<base::UnguessableToken, base::Optional<ExceptionOr<bool>>>
+      id_to_result_map_;
+  std::unique_ptr<CountDownLatch> count_down_latch_;
+
+ private:
+  scoped_refptr<base::TaskRunner> task_runner_ =
+      base::ThreadPool::CreateTaskRunner({base::MayBlock()});
+};
+
+TEST_F(CountDownLatchTest, InitializeCount0_AwaitTimed_DoesNotBlock) {
+  InitializeCountDownLatch(0);
+
+  base::RunLoop run_loop;
+  base::UnguessableToken attempt_id = base::UnguessableToken::Create();
+  PostAwaitTask(run_loop, attempt_id, base::TimeDelta::FromMilliseconds(1000));
+
+  run_loop.Run();
+  EXPECT_EQ(1u, MapSize());
+  VerifyBoolResultForAttemptId(attempt_id, true);
+}
+
+TEST_F(CountDownLatchTest, InitializeCount0_AwaitInf_DoesNotBlock) {
+  InitializeCountDownLatch(0);
+
+  base::RunLoop run_loop;
+  base::UnguessableToken attempt_id = base::UnguessableToken::Create();
+  PostAwaitTask(run_loop, attempt_id, base::nullopt /* timeout */);
+
+  run_loop.Run();
+  EXPECT_EQ(1u, MapSize());
+  VerifyExceptionResultForAttemptId(attempt_id, Exception::kSuccess);
+}
+
+TEST_F(CountDownLatchTest, InitializeCount2_BlocksUnlessCountIsZero) {
+  InitializeCountDownLatch(2);
+
+  base::RunLoop run_loop;
+  base::UnguessableToken attempt_id = base::UnguessableToken::Create();
+  PostAwaitTask(run_loop, attempt_id, base::nullopt /* timeout */);
+  ASSERT_EQ(0u, MapSize());
+
+  count_down_latch_->CountDown();
+  EXPECT_EQ(0u, MapSize());
+
+  count_down_latch_->CountDown();
+  run_loop.Run();
+  EXPECT_EQ(1u, MapSize());
+  VerifyExceptionResultForAttemptId(attempt_id, Exception::kSuccess);
+
+  // Further CountDown is ignored.
+  count_down_latch_->CountDown();
+  EXPECT_EQ(1u, MapSize());
+  VerifyExceptionResultForAttemptId(attempt_id, Exception::kSuccess);
+}
+
+TEST_F(CountDownLatchTest,
+       InitializeCount2_UnblocksAllBlockedThreadsWhenCountIsZero) {
+  InitializeCountDownLatch(2);
+
+  base::RunLoop run_loop_1;
+  base::UnguessableToken attempt_id_1 = base::UnguessableToken::Create();
+  PostAwaitTask(run_loop_1, attempt_id_1, base::nullopt /* timeout */);
+  base::RunLoop run_loop_2;
+  base::UnguessableToken attempt_id_2 = base::UnguessableToken::Create();
+  PostAwaitTask(run_loop_2, attempt_id_2, base::nullopt /* timeout */);
+  base::RunLoop run_loop_3;
+  base::UnguessableToken attempt_id_3 = base::UnguessableToken::Create();
+  PostAwaitTask(run_loop_3, attempt_id_3, base::nullopt /* timeout */);
+  ASSERT_EQ(0u, MapSize());
+
+  count_down_latch_->CountDown();
+  ASSERT_EQ(0u, MapSize());
+
+  count_down_latch_->CountDown();
+
+  run_loop_1.Run();
+  run_loop_2.Run();
+  run_loop_3.Run();
+  EXPECT_EQ(3u, MapSize());
+  VerifyExceptionResultForAttemptId(attempt_id_1, Exception::kSuccess);
+  VerifyExceptionResultForAttemptId(attempt_id_2, Exception::kSuccess);
+  VerifyExceptionResultForAttemptId(attempt_id_3, Exception::kSuccess);
+}
+
+TEST_F(CountDownLatchTest, InitializeCount2_TimedAwaitTimesOut) {
+  InitializeCountDownLatch(2);
+
+  base::RunLoop run_loop;
+  base::UnguessableToken attempt_id = base::UnguessableToken::Create();
+  PostAwaitTask(run_loop, attempt_id, base::TimeDelta::FromMilliseconds(1000));
+
+  run_loop.Run();
+  EXPECT_EQ(1u, MapSize());
+  VerifyBoolResultForAttemptId(attempt_id, false);
+}
+
+TEST_F(CountDownLatchTest, InitializeCount2_LongerTimedAwaitDoesNotTimeOut) {
+  InitializeCountDownLatch(2);
+
+  base::RunLoop run_loop_1;
+  base::UnguessableToken attempt_id_1 = base::UnguessableToken::Create();
+  PostAwaitTask(run_loop_1, attempt_id_1,
+                base::TimeDelta::FromMilliseconds(100));
+  base::RunLoop run_loop_2;
+  base::UnguessableToken attempt_id_2 = base::UnguessableToken::Create();
+  PostAwaitTask(run_loop_2, attempt_id_2,
+                base::TimeDelta::FromMilliseconds(1000));
+
+  run_loop_1.Run();
+  ASSERT_EQ(1u, MapSize());
+  VerifyBoolResultForAttemptId(attempt_id_1, false);
+
+  count_down_latch_->CountDown();
+  ASSERT_EQ(1u, MapSize());
+
+  count_down_latch_->CountDown();
+
+  run_loop_2.Run();
+  EXPECT_EQ(2u, MapSize());
+  VerifyBoolResultForAttemptId(attempt_id_2, true);
+}
+
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
diff --git a/chrome/services/sharing/nearby/platform_v2/crypto.cc b/chrome/services/sharing/nearby/platform_v2/crypto.cc
new file mode 100644
index 0000000..bd6a538b
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/crypto.cc
@@ -0,0 +1,35 @@
+// Copyright (c) 2020 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/nearby/src/cpp/platform_v2/api/crypto.h"
+
+#include "base/hash/md5.h"
+#include "base/memory/ptr_util.h"
+#include "crypto/sha2.h"
+
+#include <vector>
+
+namespace location {
+namespace nearby {
+
+void Crypto::Init() {}
+
+ByteArray Crypto::Md5(absl::string_view input) {
+  if (input.empty())
+    return ByteArray();
+
+  base::MD5Digest digest;
+  base::MD5Sum(input.data(), input.length(), &digest);
+  return ByteArray(std::string(std::begin(digest.a), std::end(digest.a)));
+}
+
+ByteArray Crypto::Sha256(absl::string_view input) {
+  if (input.empty())
+    return ByteArray();
+
+  return ByteArray(crypto::SHA256HashString(std::string(input)));
+}
+
+}  // namespace nearby
+}  // namespace location
diff --git a/chrome/services/sharing/nearby/platform_v2/crypto_unittest.cc b/chrome/services/sharing/nearby/platform_v2/crypto_unittest.cc
new file mode 100644
index 0000000..a17bd1e
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/crypto_unittest.cc
@@ -0,0 +1,36 @@
+// Copyright (c) 2020 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/nearby/src/cpp/platform_v2/api/crypto.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace location {
+namespace nearby {
+
+TEST(CryptoTest, Md5GeneratesHash) {
+  const ByteArray expected_md5(
+      "\xb4\x5c\xff\xe0\x84\xdd\x3d\x20\xd9\x28\xbe\xe8\x5e\x7b\x0f\x21");
+  ByteArray md5_hash = Crypto::Md5("string");
+  EXPECT_EQ(md5_hash, expected_md5);
+}
+
+TEST(CryptoTest, Md5ReturnsEmptyOnError) {
+  EXPECT_EQ(Crypto::Md5(""), ByteArray{});
+}
+
+TEST(CryptoTest, Sha256GeneratesHash) {
+  const ByteArray expected_sha256(
+      "\x47\x32\x87\xf8\x29\x8d\xba\x71\x63\xa8\x97\x90\x89\x58\xf7\xc0"
+      "\xea\xe7\x33\xe2\x5d\x2e\x02\x79\x92\xea\x2e\xdc\x9b\xed\x2f\xa8");
+  ByteArray sha256_hash = Crypto::Sha256("string");
+  EXPECT_EQ(sha256_hash, expected_sha256);
+}
+
+TEST(CryptoTest, Sha256ReturnsEmptyOnError) {
+  EXPECT_EQ(Crypto::Sha256(""), ByteArray{});
+}
+
+}  // namespace nearby
+}  // namespace location
diff --git a/chrome/services/sharing/nearby/platform_v2/log_message.cc b/chrome/services/sharing/nearby/platform_v2/log_message.cc
new file mode 100644
index 0000000..44c70dcf2
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/log_message.cc
@@ -0,0 +1,61 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/services/sharing/nearby/platform_v2/log_message.h"
+
+#include <algorithm>
+
+#include "base/strings/stringprintf.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+api::LogMessage::Severity g_min_log_severity = api::LogMessage::Severity::kInfo;
+
+logging::LogSeverity ConvertSeverity(api::LogMessage::Severity severity) {
+  switch (severity) {
+    case api::LogMessage::Severity::kInfo:
+      return logging::LOG_INFO;
+    case api::LogMessage::Severity::kWarning:
+      return logging::LOG_WARNING;
+    case api::LogMessage::Severity::kError:
+      return logging::LOG_ERROR;
+    case api::LogMessage::Severity::kFatal:
+      return logging::LOG_FATAL;
+  }
+}
+
+LogMessage::LogMessage(const char* file, int line, Severity severity)
+    : log_message_(file, line, ConvertSeverity(severity)) {}
+
+LogMessage::~LogMessage() = default;
+
+void LogMessage::Print(const char* format, ...) {
+  va_list ap;
+  va_start(ap, format);
+  log_message_.stream() << base::StringPrintV(format, ap);
+  va_end(ap);
+}
+
+std::ostream& LogMessage::Stream() {
+  return log_message_.stream();
+}
+
+}  // namespace chrome
+
+namespace api {
+
+void LogMessage::SetMinLogSeverity(Severity severity) {
+  chrome::g_min_log_severity = severity;
+}
+
+bool LogMessage::ShouldCreateLogMessage(Severity severity) {
+  return severity >= chrome::g_min_log_severity &&
+         logging::ShouldCreateLogMessage(chrome::ConvertSeverity(severity));
+}
+
+}  // namespace api
+}  // namespace nearby
+}  // namespace location
diff --git a/chrome/services/sharing/nearby/platform_v2/log_message.h b/chrome/services/sharing/nearby/platform_v2/log_message.h
new file mode 100644
index 0000000..aed38fb
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/log_message.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_LOG_MESSAGE_H_
+#define CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_LOG_MESSAGE_H_
+
+#include "base/logging.h"
+#include "third_party/nearby/src/cpp/platform_v2/api/log_message.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+// Concrete LogMessage implementation
+class LogMessage : public api::LogMessage {
+ public:
+  LogMessage(const char* file, int line, Severity severity);
+  ~LogMessage() override;
+
+  LogMessage(const LogMessage&) = delete;
+  LogMessage& operator=(const LogMessage&) = delete;
+
+  void Print(const char* format, ...) override;
+
+  std::ostream& Stream() override;
+
+ private:
+  logging::LogMessage log_message_;
+};
+
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
+
+#endif  // CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_LOG_MESSAGE_H_
diff --git a/chrome/services/sharing/nearby/platform_v2/multi_thread_executor_unittest.cc b/chrome/services/sharing/nearby/platform_v2/multi_thread_executor_unittest.cc
new file mode 100644
index 0000000..eee88b6
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/multi_thread_executor_unittest.cc
@@ -0,0 +1,170 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/services/sharing/nearby/platform_v2/submittable_executor.h"
+
+#include <map>
+#include <memory>
+#include <set>
+#include <utility>
+
+#include "base/callback_forward.h"
+#include "base/run_loop.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/task/thread_pool.h"
+#include "base/test/task_environment.h"
+#include "base/unguessable_token.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+// To test Execute(), which has no return value, each task is assigned a unique
+// ID. This ID is added to |executed_tasks_| when the task is Run(). Thus, the
+// presence of an ID within |executed_tasks_| means the associated task has
+// completed execution.
+class MultiThreadExecutorTest : public testing::Test {
+ protected:
+  void ExecuteRunnableWithId(base::RunLoop& run_loop,
+                             const base::UnguessableToken& task_id) {
+    base::RunLoop wait_run_loop;
+    multi_thread_executor_->Execute(
+        CreateTrackedRunnable(run_loop, task_id, wait_run_loop));
+
+    // Wait until runnable has started.
+    wait_run_loop.Run();
+  }
+
+  bool SubmitRunnableWithId(base::RunLoop& run_loop,
+                            const base::UnguessableToken& task_id) {
+    base::RunLoop wait_run_loop;
+    bool result = multi_thread_executor_->DoSubmit(
+        CreateTrackedRunnable(run_loop, task_id, wait_run_loop));
+
+    // Wait until runnable has started.
+    wait_run_loop.Run();
+    return result;
+  }
+
+  bool HasTaskStarted(const base::UnguessableToken& task_id) {
+    base::AutoLock al(started_tasks_lock_);
+    return started_tasks_.find(task_id) != started_tasks_.end();
+  }
+
+  bool HasTaskExecuted(const base::UnguessableToken& task_id) {
+    base::AutoLock al(executed_tasks_lock_);
+    return executed_tasks_.find(task_id) != executed_tasks_.end();
+  }
+
+  Runnable CreateTrackedRunnable(base::RunLoop& run_loop,
+                                 const base::UnguessableToken& task_id,
+                                 base::RunLoop& wait_run_loop) {
+    return [&] {
+      {
+        base::AutoLock al(started_tasks_lock_);
+        started_tasks_.insert(task_id);
+      }
+
+      // Notify SubmitParallelRunnableWithId thread is started
+      wait_run_loop.Quit();
+
+      thread_event_.Wait();
+
+      {
+        base::AutoLock al(executed_tasks_lock_);
+        executed_tasks_.insert(task_id);
+      }
+
+      run_loop.Quit();
+    };
+  }
+
+  void NotifyThreadWaitableEvent() { thread_event_.Signal(); }
+
+  base::test::TaskEnvironment task_environment_;
+  std::unique_ptr<SubmittableExecutor> multi_thread_executor_ =
+      std::make_unique<SubmittableExecutor>(
+          base::ThreadPool::CreateTaskRunner({base::MayBlock()}));
+
+ private:
+  base::Lock started_tasks_lock_;
+  std::set<base::UnguessableToken> started_tasks_;
+  base::Lock executed_tasks_lock_;
+  std::set<base::UnguessableToken> executed_tasks_;
+  base::WaitableEvent thread_event_;
+};
+
+TEST_F(MultiThreadExecutorTest, Submit) {
+  base::RunLoop run_loop_1;
+  base::UnguessableToken task_id_1 = base::UnguessableToken::Create();
+  EXPECT_TRUE(SubmitRunnableWithId(run_loop_1, task_id_1));
+  base::RunLoop run_loop_2;
+  base::UnguessableToken task_id_2 = base::UnguessableToken::Create();
+  EXPECT_TRUE(SubmitRunnableWithId(run_loop_2, task_id_2));
+  base::RunLoop run_loop_3;
+  base::UnguessableToken task_id_3 = base::UnguessableToken::Create();
+  EXPECT_TRUE(SubmitRunnableWithId(run_loop_3, task_id_3));
+
+  EXPECT_TRUE(HasTaskStarted(task_id_1));
+  EXPECT_TRUE(HasTaskStarted(task_id_2));
+  EXPECT_TRUE(HasTaskStarted(task_id_3));
+  EXPECT_FALSE(HasTaskExecuted(task_id_1));
+  EXPECT_FALSE(HasTaskExecuted(task_id_2));
+  EXPECT_FALSE(HasTaskExecuted(task_id_3));
+
+  NotifyThreadWaitableEvent();
+
+  run_loop_1.Run();
+  run_loop_2.Run();
+  run_loop_3.Run();
+  EXPECT_TRUE(HasTaskExecuted(task_id_1));
+  EXPECT_TRUE(HasTaskExecuted(task_id_2));
+  EXPECT_TRUE(HasTaskExecuted(task_id_3));
+}
+
+TEST_F(MultiThreadExecutorTest, Execute) {
+  base::RunLoop run_loop_1;
+  base::UnguessableToken task_id_1 = base::UnguessableToken::Create();
+  ExecuteRunnableWithId(run_loop_1, task_id_1);
+  base::RunLoop run_loop_2;
+  base::UnguessableToken task_id_2 = base::UnguessableToken::Create();
+  ExecuteRunnableWithId(run_loop_2, task_id_2);
+  base::RunLoop run_loop_3;
+  base::UnguessableToken task_id_3 = base::UnguessableToken::Create();
+  ExecuteRunnableWithId(run_loop_3, task_id_3);
+
+  EXPECT_TRUE(HasTaskStarted(task_id_1));
+  EXPECT_TRUE(HasTaskStarted(task_id_2));
+  EXPECT_TRUE(HasTaskStarted(task_id_3));
+  EXPECT_FALSE(HasTaskExecuted(task_id_1));
+  EXPECT_FALSE(HasTaskExecuted(task_id_2));
+  EXPECT_FALSE(HasTaskExecuted(task_id_3));
+
+  NotifyThreadWaitableEvent();
+
+  run_loop_1.Run();
+  run_loop_2.Run();
+  run_loop_3.Run();
+  EXPECT_TRUE(HasTaskExecuted(task_id_1));
+  EXPECT_TRUE(HasTaskExecuted(task_id_2));
+  EXPECT_TRUE(HasTaskExecuted(task_id_3));
+}
+
+TEST_F(MultiThreadExecutorTest, ShutdownPreventsFurtherTasks) {
+  multi_thread_executor_->Shutdown();
+  base::RunLoop run_loop;
+  base::UnguessableToken task_id = base::UnguessableToken::Create();
+  base::RunLoop wait_run_loop;
+  EXPECT_FALSE(multi_thread_executor_->DoSubmit(
+      CreateTrackedRunnable(run_loop, task_id, wait_run_loop)));
+
+  EXPECT_FALSE(HasTaskExecuted(task_id));
+}
+
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
diff --git a/chrome/services/sharing/nearby/platform_v2/mutex.cc b/chrome/services/sharing/nearby/platform_v2/mutex.cc
new file mode 100644
index 0000000..4f7aade
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/mutex.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/services/sharing/nearby/platform_v2/mutex.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+Mutex::Mutex() = default;
+
+Mutex::~Mutex() = default;
+
+void Mutex::Lock() EXCLUSIVE_LOCK_FUNCTION() {
+  lock_.Acquire();
+}
+
+void Mutex::Unlock() UNLOCK_FUNCTION() {
+  lock_.Release();
+}
+
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
diff --git a/chrome/services/sharing/nearby/platform_v2/mutex.h b/chrome/services/sharing/nearby/platform_v2/mutex.h
new file mode 100644
index 0000000..4d11a6f
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/mutex.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_MUTEX_H_
+#define CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_MUTEX_H_
+
+#include "base/synchronization/lock.h"
+#include "third_party/nearby/src/cpp/platform_v2/api/mutex.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+// Concrete Mutex implementation. Non-recursive lock.
+class Mutex : public api::Mutex {
+ public:
+  Mutex();
+  ~Mutex() override;
+
+  Mutex(const Mutex&) = delete;
+  Mutex& operator=(const Mutex&) = delete;
+
+  // api::Mutex:
+  void Lock() override;
+  void Unlock() override;
+
+ private:
+  friend class ConditionVariable;
+  base::Lock lock_;
+};
+
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
+
+#endif  // CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_MUTEX_H_
diff --git a/chrome/services/sharing/nearby/platform_v2/mutex_unittest.cc b/chrome/services/sharing/nearby/platform_v2/mutex_unittest.cc
new file mode 100644
index 0000000..5b3ecad
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/mutex_unittest.cc
@@ -0,0 +1,98 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/services/sharing/nearby/platform_v2/mutex.h"
+
+#include "base/bind.h"
+#include "base/containers/flat_set.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/stl_util.h"
+#include "base/task/post_task.h"
+#include "base/task/thread_pool.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/gtest_util.h"
+#include "base/test/task_environment.h"
+#include "base/unguessable_token.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+class MutexTest : public testing::Test {
+ protected:
+  void PostLockAndUnlockFromDifferentThread(
+      base::RunLoop& run_loop,
+      const base::UnguessableToken& attempt_id) {
+    base::RunLoop wait_run_loop;
+    auto callback = base::BindLambdaForTesting([&]() {
+      wait_run_loop.Quit();
+
+      mutex_.Lock();
+      // Insert |attempt_id| into |successful_mutex_attempts_|
+      // if it succeeds in acquiring |mutex_|, immediately Unlock()
+      // after doing so because Unlock() may only be called from the same thread
+      // that originally called mutex().
+      {
+        base::AutoLock al(lock_);
+        successful_mutex_attempts_.insert(attempt_id);
+      }
+      mutex_.Unlock();
+
+      run_loop.Quit();
+    });
+    task_runner_->PostTask(FROM_HERE, std::move(callback));
+
+    // Wait until callback has started.
+    wait_run_loop.Run();
+  }
+
+  bool HasSuccessfullyLockedWithAttemptId(
+      const base::UnguessableToken& attempt_id) {
+    base::AutoLock al(lock_);
+    return base::Contains(successful_mutex_attempts_, attempt_id);
+  }
+
+  Mutex& mutex() { return mutex_; }
+
+  base::test::TaskEnvironment task_environment_;
+
+ private:
+  Mutex mutex_;
+  base::Lock lock_;
+  scoped_refptr<base::SingleThreadTaskRunner> task_runner_ =
+      base::ThreadPool::CreateSingleThreadTaskRunner({base::MayBlock()});
+  base::flat_set<base::UnguessableToken> successful_mutex_attempts_;
+};
+
+TEST_F(MutexTest, LockOnce_UnlockOnce) {
+  mutex().Lock();
+  mutex().Unlock();
+}
+
+TEST_F(MutexTest,
+       LockOnce_DisallowRelockingFromDifferentThreadUntilCurrentThreadUnlocks) {
+  // Lock on current thread.
+  mutex().Lock();
+
+  // Try to lock again, but on different thread.
+  base::RunLoop run_loop;
+  base::UnguessableToken attempt_id = base::UnguessableToken::Create();
+  PostLockAndUnlockFromDifferentThread(run_loop, attempt_id);
+  ASSERT_FALSE(HasSuccessfullyLockedWithAttemptId(attempt_id));
+
+  // Outstanding lock attempt succeed after unlocking from current thread.
+  mutex().Unlock();
+  run_loop.Run();
+  EXPECT_TRUE(HasSuccessfullyLockedWithAttemptId(attempt_id));
+}
+
+TEST_F(MutexTest, CannotUnlockBeforeAnyLocks) {
+  EXPECT_DCHECK_DEATH(mutex().Unlock());
+}
+
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
diff --git a/chrome/services/sharing/nearby/platform_v2/platform.cc b/chrome/services/sharing/nearby/platform_v2/platform.cc
new file mode 100644
index 0000000..855424e
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/platform.cc
@@ -0,0 +1,161 @@
+// Copyright 2020 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/nearby/src/cpp/platform_v2/api/platform.h"
+
+#include "base/guid.h"
+#include "base/strings/string_number_conversions.h"
+#include "chrome/services/sharing/nearby/platform_v2/atomic_boolean.h"
+#include "chrome/services/sharing/nearby/platform_v2/atomic_uint32.h"
+#include "chrome/services/sharing/nearby/platform_v2/condition_variable.h"
+#include "chrome/services/sharing/nearby/platform_v2/count_down_latch.h"
+#include "chrome/services/sharing/nearby/platform_v2/log_message.h"
+#include "chrome/services/sharing/nearby/platform_v2/mutex.h"
+#include "chrome/services/sharing/nearby/platform_v2/recursive_mutex.h"
+#include "chrome/services/sharing/nearby/platform_v2/scheduled_executor.h"
+#include "chrome/services/sharing/nearby/platform_v2/submittable_executor.h"
+#include "third_party/nearby/src/cpp/platform_v2/api/atomic_boolean.h"
+#include "third_party/nearby/src/cpp/platform_v2/api/atomic_reference.h"
+#include "third_party/nearby/src/cpp/platform_v2/api/ble.h"
+#include "third_party/nearby/src/cpp/platform_v2/api/ble_v2.h"
+#include "third_party/nearby/src/cpp/platform_v2/api/bluetooth_adapter.h"
+#include "third_party/nearby/src/cpp/platform_v2/api/bluetooth_classic.h"
+#include "third_party/nearby/src/cpp/platform_v2/api/condition_variable.h"
+#include "third_party/nearby/src/cpp/platform_v2/api/count_down_latch.h"
+#include "third_party/nearby/src/cpp/platform_v2/api/log_message.h"
+#include "third_party/nearby/src/cpp/platform_v2/api/mutex.h"
+#include "third_party/nearby/src/cpp/platform_v2/api/scheduled_executor.h"
+#include "third_party/nearby/src/cpp/platform_v2/api/server_sync.h"
+#include "third_party/nearby/src/cpp/platform_v2/api/submittable_executor.h"
+#include "third_party/nearby/src/cpp/platform_v2/api/webrtc.h"
+#include "third_party/nearby/src/cpp/platform_v2/api/wifi.h"
+#include "third_party/nearby/src/cpp/platform_v2/impl/shared/file.h"
+
+namespace location {
+namespace nearby {
+namespace api {
+
+namespace {
+std::string GetPayloadPath(std::int64_t payload_id) {
+  // TODO(alexchau): Get file path mapping from connections::NearbyConnections.
+  return std::string();
+}
+}  // namespace
+
+int GetCurrentTid() {
+  // SubmittableExecutor and ScheduledExecutor does not own a thread pool
+  // directly nor manages threads, thus cannot support this debug feature.
+  return 0;
+}
+
+std::unique_ptr<SubmittableExecutor>
+ImplementationPlatform::CreateSingleThreadExecutor() {
+  return std::make_unique<chrome::SubmittableExecutor>(
+      base::ThreadPool::CreateSingleThreadTaskRunner({base::MayBlock()}));
+}
+
+std::unique_ptr<SubmittableExecutor>
+ImplementationPlatform::CreateMultiThreadExecutor(int max_concurrency) {
+  // Chrome task runner does not support max_concurrency.
+  return std::make_unique<chrome::SubmittableExecutor>(
+      base::ThreadPool::CreateTaskRunner({base::MayBlock()}));
+}
+
+std::unique_ptr<ScheduledExecutor>
+ImplementationPlatform::CreateScheduledExecutor() {
+  // TODO(crbug/1091190): Figure out if task runner needs to run in main thread.
+  return std::make_unique<chrome::ScheduledExecutor>(
+      base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()}));
+}
+
+std::unique_ptr<AtomicUint32> ImplementationPlatform::CreateAtomicUint32(
+    std::uint32_t initial_value) {
+  return std::make_unique<chrome::AtomicUint32>(initial_value);
+}
+
+std::unique_ptr<BluetoothAdapter>
+ImplementationPlatform::CreateBluetoothAdapter() {
+  return nullptr;
+}
+
+std::unique_ptr<CountDownLatch> ImplementationPlatform::CreateCountDownLatch(
+    std::int32_t count) {
+  return std::make_unique<chrome::CountDownLatch>(count);
+}
+
+std::unique_ptr<AtomicBoolean> ImplementationPlatform::CreateAtomicBoolean(
+    bool initial_value) {
+  return std::make_unique<chrome::AtomicBoolean>(initial_value);
+}
+
+std::unique_ptr<InputFile> ImplementationPlatform::CreateInputFile(
+    std::int64_t payload_id,
+    std::int64_t total_size) {
+  return std::make_unique<shared::InputFile>(GetPayloadPath(payload_id),
+                                             total_size);
+}
+
+std::unique_ptr<OutputFile> ImplementationPlatform::CreateOutputFile(
+    std::int64_t payload_id) {
+  return std::make_unique<shared::OutputFile>(GetPayloadPath(payload_id));
+}
+
+std::unique_ptr<LogMessage> ImplementationPlatform::CreateLogMessage(
+    const char* file,
+    int line,
+    LogMessage::Severity severity) {
+  return std::make_unique<chrome::LogMessage>(file, line, severity);
+}
+
+std::unique_ptr<BluetoothClassicMedium>
+ImplementationPlatform::CreateBluetoothClassicMedium(
+    api::BluetoothAdapter& adapter) {
+  return nullptr;
+}
+
+std::unique_ptr<BleMedium> ImplementationPlatform::CreateBleMedium(
+    api::BluetoothAdapter& adapter) {
+  return nullptr;
+}
+
+std::unique_ptr<ble_v2::BleMedium> ImplementationPlatform::CreateBleV2Medium(
+    api::BluetoothAdapter& adapter) {
+  return nullptr;
+}
+
+std::unique_ptr<ServerSyncMedium>
+ImplementationPlatform::CreateServerSyncMedium() {
+  return nullptr;
+}
+
+std::unique_ptr<WifiMedium> ImplementationPlatform::CreateWifiMedium() {
+  return nullptr;
+}
+
+std::unique_ptr<WifiLanMedium> ImplementationPlatform::CreateWifiLanMedium() {
+  return nullptr;
+}
+
+std::unique_ptr<WebRtcMedium> ImplementationPlatform::CreateWebRtcMedium() {
+  return nullptr;
+}
+
+std::unique_ptr<Mutex> ImplementationPlatform::CreateMutex(Mutex::Mode mode) {
+  // Chrome does not support unchecked Mutex in debug mode, therefore
+  // chrome::Mutex is used for both kRegular and kRegularNoCheck.
+  if (mode == Mutex::Mode::kRecursive)
+    return std::make_unique<chrome::RecursiveMutex>();
+  else
+    return std::make_unique<chrome::Mutex>();
+}
+
+std::unique_ptr<ConditionVariable>
+ImplementationPlatform::CreateConditionVariable(Mutex* mutex) {
+  return std::make_unique<chrome::ConditionVariable>(
+      static_cast<chrome::Mutex*>(mutex));
+}
+
+}  // namespace api
+}  // namespace nearby
+}  // namespace location
diff --git a/chrome/services/sharing/nearby/platform_v2/recursive_mutex.cc b/chrome/services/sharing/nearby/platform_v2/recursive_mutex.cc
new file mode 100644
index 0000000..f0788d7
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/recursive_mutex.cc
@@ -0,0 +1,64 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/services/sharing/nearby/platform_v2/recursive_mutex.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+RecursiveMutex::RecursiveMutex() = default;
+
+RecursiveMutex::~RecursiveMutex() {
+#if DCHECK_IS_ON()
+  base::AutoLock al(bookkeeping_lock_);
+  DCHECK_EQ(0u, num_acquisitions_);
+  DCHECK_EQ(base::kInvalidThreadId, owning_thread_id_);
+#endif  // DCHECK_IS_ON()
+}
+
+void RecursiveMutex::Lock() EXCLUSIVE_LOCK_FUNCTION() {
+  {
+    base::AutoLock al(bookkeeping_lock_);
+    if (num_acquisitions_ > 0u &&
+        owning_thread_id_ == base::PlatformThread::CurrentId()) {
+      real_lock_.AssertAcquired();
+      ++num_acquisitions_;
+      return;
+    }
+  }
+
+  // At this point, either no thread currently holds |real_lock_|, in which case
+  // the current thread should be able to immediately acquire it, or a different
+  // thread holds it, in which case Acquire() will block. It's necessary that
+  // Acquire() happens outside the critical sections of |bookkeeping_lock_|,
+  // otherwise any future calls to Unlock() will block on acquiring
+  // |bookkeeping_lock_|, which would prevent Release() from ever running on
+  // |real_lock_|, resulting in deadlock.
+  real_lock_.Acquire();
+
+  {
+    base::AutoLock al(bookkeeping_lock_);
+    DCHECK_EQ(0u, num_acquisitions_);
+    owning_thread_id_ = base::PlatformThread::CurrentId();
+    num_acquisitions_ = 1;
+  }
+}
+
+void RecursiveMutex::Unlock() UNLOCK_FUNCTION() {
+  base::AutoLock al(bookkeeping_lock_);
+  CHECK_GT(num_acquisitions_, 0u);
+  DCHECK_EQ(base::PlatformThread::CurrentId(), owning_thread_id_);
+  real_lock_.AssertAcquired();
+
+  --num_acquisitions_;
+  if (num_acquisitions_ == 0u) {
+    owning_thread_id_ = base::kInvalidThreadId;
+    real_lock_.Release();
+  }
+}
+
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
diff --git a/chrome/services/sharing/nearby/platform_v2/recursive_mutex.h b/chrome/services/sharing/nearby/platform_v2/recursive_mutex.h
new file mode 100644
index 0000000..f787f7cb
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/recursive_mutex.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_RECURSIVE_MUTEX_H_
+#define CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_RECURSIVE_MUTEX_H_
+
+#include "base/synchronization/lock.h"
+#include "base/thread_annotations.h"
+#include "base/threading/platform_thread.h"
+#include "third_party/nearby/src/cpp/platform_v2/api/mutex.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+// Concrete Mutex implementation with recursive lock.
+// As base::Lock does not support recursive locking, this class uses separate
+// variable to track number of times a thread has acquried this mutex, and only
+// acquire base::Lock on first acquisation per thread, and only release
+// on the last release base::Lock per thread.
+class RecursiveMutex : public api::Mutex {
+ public:
+  RecursiveMutex();
+  ~RecursiveMutex() override;
+
+  RecursiveMutex(const RecursiveMutex&) = delete;
+  RecursiveMutex& operator=(const RecursiveMutex&) = delete;
+
+  // Mutex:
+  void Lock() override;
+  void Unlock() override;
+
+ private:
+  friend class RecursiveMutexTest;
+
+  // The underlying lock that can only be acquried once per thread.
+  base::Lock real_lock_;
+  // The lock that guards book keeping variables.
+  base::Lock bookkeeping_lock_;
+  base::PlatformThreadId owning_thread_id_ GUARDED_BY(bookkeeping_lock_) =
+      base::kInvalidThreadId;
+  size_t num_acquisitions_ GUARDED_BY(bookkeeping_lock_) = 0;
+};
+
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
+
+#endif  // CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_RECURSIVE_MUTEX_H_
diff --git a/chrome/services/sharing/nearby/platform_v2/recursive_mutex_unittest.cc b/chrome/services/sharing/nearby/platform_v2/recursive_mutex_unittest.cc
new file mode 100644
index 0000000..2e3bf480
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/recursive_mutex_unittest.cc
@@ -0,0 +1,163 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/services/sharing/nearby/platform_v2/recursive_mutex.h"
+
+#include "base/bind.h"
+#include "base/containers/flat_set.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "base/task/post_task.h"
+#include "base/task/thread_pool.h"
+#include "base/task_runner.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/gtest_util.h"
+#include "base/test/task_environment.h"
+#include "base/unguessable_token.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+class RecursiveMutexTest : public testing::Test {
+ protected:
+  void PostLockAndUnlockFromDifferentThread(
+      base::RunLoop& run_loop,
+      const base::UnguessableToken& attempt_id) {
+    base::RunLoop wait_run_loop;
+    auto callback = base::BindLambdaForTesting([&]() {
+      wait_run_loop.Quit();
+
+      mutex_.Lock();
+      {
+        // insert |attempt_id| into |successful_mutex_attempts_|
+        // if it succeeds in acquiring |mutex_|, immediately Unlock()
+        // after doing so because Unlock() may only be called from the same
+        // thread that originally called mutex().
+        base::AutoLock al(lock_);
+        successful_mutex_attempts_.insert(attempt_id);
+      }
+      mutex_.Unlock();
+
+      run_loop.Quit();
+    });
+    task_runner_->PostTask(FROM_HERE, std::move(callback));
+
+    // Wait until callback has started.
+    wait_run_loop.Run();
+  }
+
+  bool HasSuccessfullyLockedWithAttemptId(
+      const base::UnguessableToken& attempt_id) {
+    base::AutoLock al(lock_);
+    return base::Contains(successful_mutex_attempts_, attempt_id);
+  }
+
+  RecursiveMutex& mutex() { return mutex_; }
+
+  base::test::TaskEnvironment task_environment_;
+
+ private:
+  RecursiveMutex mutex_;
+  base::Lock lock_;
+  scoped_refptr<base::TaskRunner> task_runner_ =
+      base::ThreadPool::CreateTaskRunner({base::MayBlock()});
+  base::flat_set<base::UnguessableToken> successful_mutex_attempts_;
+};
+
+TEST_F(RecursiveMutexTest, LockOnce_UnlockOnce) {
+  mutex().Lock();
+  mutex().Unlock();
+}
+
+TEST_F(RecursiveMutexTest, LockThrice_UnlockThrice) {
+  mutex().Lock();
+  mutex().Lock();
+  mutex().Lock();
+  mutex().Unlock();
+  mutex().Unlock();
+  mutex().Unlock();
+}
+
+TEST_F(RecursiveMutexTest,
+       LockOnce_DisallowRelockingFromDifferentThreadUntilCurrentThreadUnlocks) {
+  // Lock on current thread.
+  mutex().Lock();
+
+  // Try to lock again, but on different thread.
+  base::RunLoop run_loop;
+  base::UnguessableToken attempt_id = base::UnguessableToken::Create();
+  PostLockAndUnlockFromDifferentThread(run_loop, attempt_id);
+  ASSERT_FALSE(HasSuccessfullyLockedWithAttemptId(attempt_id));
+
+  // Outstanding lock attempt succeed after unlocking from current thread.
+  mutex().Unlock();
+  run_loop.Run();
+  EXPECT_TRUE(HasSuccessfullyLockedWithAttemptId(attempt_id));
+}
+
+TEST_F(
+    RecursiveMutexTest,
+    LockThrice_DisallowRelockingFromDifferentThreadUntilCurrentThreadUnlocks) {
+  // Lock on current thread.
+  mutex().Lock();
+  mutex().Lock();
+  mutex().Lock();
+
+  // Try to lock again, but on different thread.
+  base::RunLoop run_loop;
+  base::UnguessableToken attempt_id = base::UnguessableToken::Create();
+  PostLockAndUnlockFromDifferentThread(run_loop, attempt_id);
+  ASSERT_FALSE(HasSuccessfullyLockedWithAttemptId(attempt_id));
+
+  // Outstanding lock attempt succeed after unlocking from current thread.
+  mutex().Unlock();
+  mutex().Unlock();
+  mutex().Unlock();
+  run_loop.Run();
+  EXPECT_TRUE(HasSuccessfullyLockedWithAttemptId(attempt_id));
+}
+
+TEST_F(RecursiveMutexTest, InterweavedLocking) {
+  mutex().Lock();
+
+  base::RunLoop run_loop_1;
+  base::UnguessableToken attempt_id_1 = base::UnguessableToken::Create();
+  PostLockAndUnlockFromDifferentThread(run_loop_1, attempt_id_1);
+  ASSERT_FALSE(HasSuccessfullyLockedWithAttemptId(attempt_id_1));
+
+  mutex().Lock();
+
+  base::RunLoop run_loop_2;
+  base::UnguessableToken attempt_id_2 = base::UnguessableToken::Create();
+  PostLockAndUnlockFromDifferentThread(run_loop_2, attempt_id_2);
+  ASSERT_FALSE(HasSuccessfullyLockedWithAttemptId(attempt_id_2));
+
+  mutex().Lock();
+
+  base::RunLoop run_loop_3;
+  base::UnguessableToken attempt_id_3 = base::UnguessableToken::Create();
+  PostLockAndUnlockFromDifferentThread(run_loop_3, attempt_id_3);
+  ASSERT_FALSE(HasSuccessfullyLockedWithAttemptId(attempt_id_3));
+
+  mutex().Unlock();
+  mutex().Unlock();
+  mutex().Unlock();
+
+  run_loop_1.Run();
+  run_loop_2.Run();
+  run_loop_3.Run();
+  EXPECT_TRUE(HasSuccessfullyLockedWithAttemptId(attempt_id_1));
+  EXPECT_TRUE(HasSuccessfullyLockedWithAttemptId(attempt_id_2));
+  EXPECT_TRUE(HasSuccessfullyLockedWithAttemptId(attempt_id_3));
+}
+
+TEST_F(RecursiveMutexTest, CannotUnlockBeforeAnyLocks) {
+  EXPECT_DCHECK_DEATH(mutex().Unlock());
+}
+
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
diff --git a/chrome/services/sharing/nearby/platform_v2/scheduled_executor.cc b/chrome/services/sharing/nearby/platform_v2/scheduled_executor.cc
new file mode 100644
index 0000000..dfc711b4
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/scheduled_executor.cc
@@ -0,0 +1,193 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/services/sharing/nearby/platform_v2/scheduled_executor.h"
+
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/stl_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "chrome/services/sharing/nearby/platform_v2/atomic_boolean.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+namespace {
+
+class CancelableTask : public api::Cancelable {
+ public:
+  explicit CancelableTask(base::OnceCallback<bool()> cancel_callback)
+      : cancel_callback_(std::move(cancel_callback)) {}
+  CancelableTask() = default;
+  ~CancelableTask() override = default;
+
+  // api::Cancelable:
+  bool Cancel() override {
+    if (cancel_callback_.is_null())
+      return false;
+
+    return std::move(cancel_callback_).Run();
+  }
+
+ private:
+  base::OnceCallback<bool()> cancel_callback_;
+};
+
+}  // namespace
+
+ScheduledExecutor::PendingTaskWithTimer::PendingTaskWithTimer(
+    Runnable&& runnable)
+    : runnable(std::move(runnable)) {}
+
+ScheduledExecutor::PendingTaskWithTimer::~PendingTaskWithTimer() = default;
+
+ScheduledExecutor::ScheduledExecutor(
+    scoped_refptr<base::SequencedTaskRunner> timer_task_runner)
+    : timer_task_runner_(std::move(timer_task_runner)) {
+  DETACH_FROM_SEQUENCE(timer_sequence_checker_);
+}
+
+ScheduledExecutor::~ScheduledExecutor() {
+  // Move all runnables from id_to_task_map_ to pending_tasks to avoid blocking
+  // Schedule or Cancel while executing runnables.
+  std::map<base::UnguessableToken, std::unique_ptr<PendingTaskWithTimer>>
+      pending_tasks;
+  {
+    base::AutoLock al(lock_);
+    is_shut_down_ = true;
+    using std::swap;
+    swap(pending_tasks, id_to_task_map_);
+  }
+
+  // Run all tasks prematurely, order does not matter.
+  {
+    // base::ScopedAllowBaseSyncPrimitives is required as code inside the
+    // runnable uses blocking primitive, which lives outside Chrome.
+    base::ScopedAllowBaseSyncPrimitives allow_wait;
+    for (auto& it : pending_tasks)
+      it.second->runnable();
+  }
+}
+
+bool ScheduledExecutor::TryCancelTask(base::WeakPtr<ScheduledExecutor> executor,
+                                      const base::UnguessableToken& id) {
+  if (!executor)
+    return false;
+
+  return executor->OnTaskCancelled(id);
+}
+
+void ScheduledExecutor::Execute(Runnable&& runnable) {
+  Schedule(std::move(runnable), absl::ZeroDuration());
+}
+
+void ScheduledExecutor::Shutdown() {
+  base::AutoLock al(lock_);
+  is_shut_down_ = true;
+}
+
+int ScheduledExecutor::GetTid(int index) const {
+  // ScheduledExecutor does not own a thread pool directly nor manages threads,
+  // thus cannot support this debug feature.
+  return 0;
+}
+
+std::shared_ptr<api::Cancelable> ScheduledExecutor::Schedule(
+    Runnable&& runnable,
+    absl::Duration duration) {
+  base::UnguessableToken id = base::UnguessableToken::Create();
+  {
+    base::AutoLock al(lock_);
+    if (is_shut_down_)
+      return std::make_shared<CancelableTask>();
+
+    id_to_task_map_.emplace(
+        id, std::make_unique<PendingTaskWithTimer>(std::move(runnable)));
+  }
+
+  timer_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&ScheduledExecutor::StartTimerWithId,
+                                base::Unretained(this), id,
+                                base::TimeDelta::FromMicroseconds(
+                                    absl::ToInt64Microseconds(duration))));
+
+  return std::make_shared<CancelableTask>(
+      base::BindOnce(&TryCancelTask, weak_factory_.GetWeakPtr(), id));
+}
+
+void ScheduledExecutor::StartTimerWithId(const base::UnguessableToken& id,
+                                         base::TimeDelta delay) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(timer_sequence_checker_);
+  base::AutoLock al(lock_);
+
+  // If the id no longer exists, it means the task has already been cancelled.
+  auto it = id_to_task_map_.find(id);
+  if (it == id_to_task_map_.end())
+    return;
+
+  it->second->timer.SetTaskRunner(timer_task_runner_);
+  it->second->timer.Start(FROM_HERE, delay,
+                          base::BindOnce(&ScheduledExecutor::RunTaskWithId,
+                                         base::Unretained(this), id));
+}
+
+void ScheduledExecutor::StopTimerWithIdAndDeleteTaskEntry(
+    const base::UnguessableToken& id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(timer_sequence_checker_);
+  base::AutoLock al(lock_);
+
+  // If the id no longer exists, it means the task has either already been run,
+  // or the task has already been cancelled.
+  auto it = id_to_task_map_.find(id);
+  if (it == id_to_task_map_.end())
+    return;
+
+  it->second->timer.Stop();
+  id_to_task_map_.erase(id);
+}
+
+void ScheduledExecutor::RunTaskWithId(const base::UnguessableToken& id) {
+  Runnable runnable;
+  {
+    base::AutoLock al(lock_);
+
+    auto it = id_to_task_map_.find(id);
+    if (it == id_to_task_map_.end())
+      return;
+
+    runnable = std::move(it->second->runnable);
+    id_to_task_map_.erase(id);
+  }
+
+  {
+    // base::ScopedAllowBaseSyncPrimitives is required as code inside the
+    // runnable uses blocking primitive, which lives outside Chrome.
+    base::ScopedAllowBaseSyncPrimitives allow_wait;
+    runnable();
+  }
+}
+
+bool ScheduledExecutor::OnTaskCancelled(const base::UnguessableToken& id) {
+  {
+    base::AutoLock al(lock_);
+    auto it = id_to_task_map_.find(id);
+    if (it == id_to_task_map_.end())
+      return false;
+  }
+
+  timer_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&ScheduledExecutor::StopTimerWithIdAndDeleteTaskEntry,
+                     base::Unretained(this), id));
+  return true;
+}
+
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
diff --git a/chrome/services/sharing/nearby/platform_v2/scheduled_executor.h b/chrome/services/sharing/nearby/platform_v2/scheduled_executor.h
new file mode 100644
index 0000000..e651321
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/scheduled_executor.h
@@ -0,0 +1,88 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_SCHEDULED_EXECUTOR_H_
+#define CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_SCHEDULED_EXECUTOR_H_
+
+#include <map>
+#include <memory>
+
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "base/synchronization/lock.h"
+#include "base/task/post_task.h"
+#include "base/thread_annotations.h"
+#include "base/timer/timer.h"
+#include "base/unguessable_token.h"
+#include "third_party/nearby/src/cpp/platform_v2/api/scheduled_executor.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+// Concrete ScheduledExecutor implementation.
+class ScheduledExecutor : public api::ScheduledExecutor {
+ public:
+  explicit ScheduledExecutor(
+      scoped_refptr<base::SequencedTaskRunner> timer_task_runner);
+  ~ScheduledExecutor() override;
+
+  ScheduledExecutor(const ScheduledExecutor&) = delete;
+  ScheduledExecutor& operator=(const ScheduledExecutor&) = delete;
+
+  // api::ScheduledExecutor:
+  std::shared_ptr<api::Cancelable> Schedule(Runnable&& runnable,
+                                            absl::Duration duration) override;
+  void Execute(Runnable&& runnable) override;
+  void Shutdown() override;
+  int GetTid(int index) const override;
+
+ private:
+  struct PendingTaskWithTimer {
+    explicit PendingTaskWithTimer(Runnable&& runnable);
+    ~PendingTaskWithTimer();
+
+    Runnable runnable;
+    base::OneShotTimer timer;
+  };
+
+  // Static wrapper that simply runs OnTaskCancelled() if the exector has not
+  // already been destroyed. This is necessary because
+  // base::WeakPtr<ScheduledExecutor> is not allowed to bind to
+  // OnTaskCancelled(), which is non-static and non-void.
+  static bool TryCancelTask(base::WeakPtr<ScheduledExecutor> executor,
+                            const base::UnguessableToken& id);
+
+  // To ensure thread-safety, these methods are only to be posted as tasks on
+  // |timer_api_task_runner_| so that they execute in the same sequence.
+  void StartTimerWithId(const base::UnguessableToken& id,
+                        base::TimeDelta delay);
+  void StopTimerWithIdAndDeleteTaskEntry(const base::UnguessableToken& id);
+
+  void RunTaskWithId(const base::UnguessableToken& id);
+  void RemoveTaskEntryWithId(const base::UnguessableToken& id);
+  bool OnTaskCancelled(const base::UnguessableToken& id);
+
+  // SequencedTaskRunner that all base::OneShotTimer method calls (e.g. Start()
+  // and Stop()) need to be run on, to ensure thread-safety. This is also where
+  // tasks posted to base::OneShotTimer will run.
+  scoped_refptr<base::SequencedTaskRunner> timer_task_runner_;
+
+  base::Lock lock_;
+  // Tracks if the executor has been shutdown. Accessed from different threads
+  // through public APIs and task_runner_.
+  bool is_shut_down_ GUARDED_BY(lock_) = false;
+  // Tracks all pending tasks. Accessed from different threads through public
+  // APIs and task_runner_.
+  std::map<base::UnguessableToken, std::unique_ptr<PendingTaskWithTimer>>
+      id_to_task_map_ GUARDED_BY(lock_);
+  SEQUENCE_CHECKER(timer_sequence_checker_);
+  base::WeakPtrFactory<ScheduledExecutor> weak_factory_{this};
+};
+
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
+
+#endif  // CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_SCHEDULED_EXECUTOR_H_
diff --git a/chrome/services/sharing/nearby/platform_v2/scheduled_executor_unittest.cc b/chrome/services/sharing/nearby/platform_v2/scheduled_executor_unittest.cc
new file mode 100644
index 0000000..03d47898
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/scheduled_executor_unittest.cc
@@ -0,0 +1,237 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/services/sharing/nearby/platform_v2/scheduled_executor.h"
+
+#include <memory>
+#include <set>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback_forward.h"
+#include "base/synchronization/lock.h"
+#include "base/test/task_environment.h"
+#include "base/unguessable_token.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+namespace {
+
+constexpr base::TimeDelta kDefaultDelayTimeDelta =
+    base::TimeDelta::FromMinutes(10);
+
+}  // namespace
+
+class ScheduledExecutorTest : public testing::Test {
+ protected:
+  std::shared_ptr<api::Cancelable> PostRunnableWithIdAndDelay(
+      base::RunLoop& run_loop,
+      const base::UnguessableToken& id,
+      base::TimeDelta delay) {
+    Runnable runnable = [&] {
+      base::AutoLock al(id_set_lock_);
+      id_set_.insert(id);
+
+      run_loop.Quit();
+    };
+
+    std::shared_ptr<api::Cancelable> cancelable = scheduled_executor_->Schedule(
+        std::move(runnable), absl::Microseconds(delay.InMicroseconds()));
+
+    // In order to make thread-safe calls to the API of base::OneShotTimer,
+    // schedule() will post a task to an internal base::SequencedTaskRunner that
+    // calls Start() on a base::OneShotTimer. Executing RunUntilIdle() simply
+    // ensures that the base::OneShotTimer associated with the Runnable has been
+    // Start()ed, but offers no guarantee on whether the Runnable has been run()
+    // or not.
+    task_environment_.RunUntilIdle();
+
+    return cancelable;
+  }
+
+  void CancelTaskAndVerifyState(std::shared_ptr<api::Cancelable> cancelable,
+                                bool should_expect_success) {
+    EXPECT_EQ(should_expect_success, cancelable->Cancel());
+
+    // Ensures that the base::OneShotTimer associated with the given Cancelable
+    // has been Stop()ped before this method returns.
+    task_environment_.RunUntilIdle();
+  }
+
+  void VerifySetContainsId(const base::UnguessableToken& id) {
+    base::AutoLock al(id_set_lock_);
+    EXPECT_NE(id_set_.end(), id_set_.find(id));
+  }
+
+  size_t GetSetSize() {
+    base::AutoLock al(id_set_lock_);
+    return id_set_.size();
+  }
+
+  base::test::TaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  std::unique_ptr<ScheduledExecutor> scheduled_executor_ =
+      std::make_unique<ScheduledExecutor>(
+          task_environment_.GetMainThreadTaskRunner());
+
+ private:
+  base::Lock id_set_lock_;
+  std::set<base::UnguessableToken> id_set_;
+};
+
+TEST_F(ScheduledExecutorTest, SingleTaskExecutes) {
+  base::RunLoop run_loop;
+  base::UnguessableToken id = base::UnguessableToken::Create();
+  PostRunnableWithIdAndDelay(run_loop, id, kDefaultDelayTimeDelta);
+
+  task_environment_.FastForwardBy(kDefaultDelayTimeDelta);
+  EXPECT_EQ(1u, GetSetSize());
+  VerifySetContainsId(id);
+}
+
+TEST_F(ScheduledExecutorTest, StaggeredTasksExecute) {
+  base::RunLoop run_loop_1;
+  base::UnguessableToken id_1 = base::UnguessableToken::Create();
+  PostRunnableWithIdAndDelay(run_loop_1, id_1, kDefaultDelayTimeDelta);
+  base::RunLoop run_loop_2;
+  base::UnguessableToken id_2 = base::UnguessableToken::Create();
+  PostRunnableWithIdAndDelay(run_loop_2, id_2, kDefaultDelayTimeDelta * 2);
+
+  // Only the first scheduled task should run at first.
+  task_environment_.FastForwardBy(kDefaultDelayTimeDelta);
+  EXPECT_EQ(1u, GetSetSize());
+  VerifySetContainsId(id_1);
+
+  task_environment_.FastForwardBy(kDefaultDelayTimeDelta);
+  EXPECT_EQ(2u, GetSetSize());
+  VerifySetContainsId(id_2);
+}
+
+TEST_F(ScheduledExecutorTest, SingleTaskCancels) {
+  base::RunLoop run_loop;
+  base::UnguessableToken id = base::UnguessableToken::Create();
+  auto cancelable =
+      PostRunnableWithIdAndDelay(run_loop, id, kDefaultDelayTimeDelta);
+
+  CancelTaskAndVerifyState(cancelable, true /* should_expect_success */);
+  task_environment_.FastForwardBy(kDefaultDelayTimeDelta * 2);
+  EXPECT_EQ(0u, GetSetSize());
+}
+
+TEST_F(ScheduledExecutorTest, FirstTaskCancelsAndSecondTaskExecutes) {
+  base::RunLoop run_loop_1;
+  base::UnguessableToken id_1 = base::UnguessableToken::Create();
+  auto cancelable_1 =
+      PostRunnableWithIdAndDelay(run_loop_1, id_1, kDefaultDelayTimeDelta * 2);
+
+  base::RunLoop run_loop_2;
+  base::UnguessableToken id_2 = base::UnguessableToken::Create();
+  PostRunnableWithIdAndDelay(run_loop_2, id_2, kDefaultDelayTimeDelta * 3);
+
+  CancelTaskAndVerifyState(cancelable_1, true /* should_expect_success */);
+  task_environment_.FastForwardBy(kDefaultDelayTimeDelta * 2);
+  EXPECT_EQ(0u, GetSetSize());
+
+  task_environment_.FastForwardBy(kDefaultDelayTimeDelta * 2);
+  EXPECT_EQ(1u, GetSetSize());
+  VerifySetContainsId(id_2);
+}
+
+TEST_F(ScheduledExecutorTest, FailToCancelAfterRun) {
+  base::RunLoop run_loop;
+  base::UnguessableToken id = base::UnguessableToken::Create();
+  auto cancelable =
+      PostRunnableWithIdAndDelay(run_loop, id, kDefaultDelayTimeDelta);
+
+  task_environment_.FastForwardBy(kDefaultDelayTimeDelta * 2);
+  CancelTaskAndVerifyState(cancelable, false /* should_expect_success */);
+}
+
+TEST_F(ScheduledExecutorTest, FailToRunAfterCancel) {
+  base::RunLoop run_loop;
+  base::UnguessableToken id = base::UnguessableToken::Create();
+  auto cancelable =
+      PostRunnableWithIdAndDelay(run_loop, id, kDefaultDelayTimeDelta);
+
+  CancelTaskAndVerifyState(cancelable, true /* should_expect_success */);
+  task_environment_.FastForwardBy(kDefaultDelayTimeDelta * 2);
+  EXPECT_EQ(0u, GetSetSize());
+}
+
+TEST_F(ScheduledExecutorTest, FailToCancelAfterCancel) {
+  base::RunLoop run_loop;
+  base::UnguessableToken id = base::UnguessableToken::Create();
+  auto cancelable =
+      PostRunnableWithIdAndDelay(run_loop, id, kDefaultDelayTimeDelta);
+
+  // The first call should successfully cancel the task. Subsequent invocations
+  // will return false by default, as CancelableTask uses a base::OnceClosure
+  // that will be consumed after the first call to cancel().
+  CancelTaskAndVerifyState(cancelable, true /* should_expect_success */);
+  CancelTaskAndVerifyState(cancelable, false /* should_expect_success */);
+  CancelTaskAndVerifyState(cancelable, false /* should_expect_success */);
+}
+
+TEST_F(ScheduledExecutorTest, FailToCancelAfterExecutorIsDestroyed) {
+  base::RunLoop run_loop;
+  base::UnguessableToken id = base::UnguessableToken::Create();
+  auto cancelable =
+      PostRunnableWithIdAndDelay(run_loop, id, kDefaultDelayTimeDelta);
+  scheduled_executor_.reset();
+
+  task_environment_.FastForwardBy(kDefaultDelayTimeDelta * 2);
+  CancelTaskAndVerifyState(cancelable, false /* should_expect_success */);
+}
+
+TEST_F(ScheduledExecutorTest, FailToScheduleAfterShutdown) {
+  scheduled_executor_->Shutdown();
+  base::RunLoop run_loop;
+  base::UnguessableToken id = base::UnguessableToken::Create();
+  auto cancelable =
+      PostRunnableWithIdAndDelay(run_loop, id, kDefaultDelayTimeDelta);
+
+  task_environment_.FastForwardBy(kDefaultDelayTimeDelta * 2);
+  EXPECT_EQ(0u, GetSetSize());
+}
+
+TEST_F(ScheduledExecutorTest, FailToCancelAfterShutdown) {
+  scheduled_executor_->Shutdown();
+  base::RunLoop run_loop;
+  base::UnguessableToken id = base::UnguessableToken::Create();
+  auto cancelable =
+      PostRunnableWithIdAndDelay(run_loop, id, kDefaultDelayTimeDelta);
+
+  task_environment_.FastForwardBy(kDefaultDelayTimeDelta * 2);
+  CancelTaskAndVerifyState(cancelable, false /* should_expect_success */);
+}
+
+TEST_F(ScheduledExecutorTest, ShutdownAllowsExistingTaskToComplete) {
+  base::RunLoop run_loop;
+  base::UnguessableToken id = base::UnguessableToken::Create();
+  auto cancelable =
+      PostRunnableWithIdAndDelay(run_loop, id, kDefaultDelayTimeDelta);
+  scheduled_executor_->Shutdown();
+
+  task_environment_.FastForwardBy(kDefaultDelayTimeDelta * 2);
+  EXPECT_EQ(1u, GetSetSize());
+  VerifySetContainsId(id);
+}
+
+TEST_F(ScheduledExecutorTest, DestroyAllowExistingTaskToCompleteImmediately) {
+  base::RunLoop run_loop;
+  base::UnguessableToken id = base::UnguessableToken::Create();
+  auto cancelable =
+      PostRunnableWithIdAndDelay(run_loop, id, kDefaultDelayTimeDelta);
+  scheduled_executor_.reset();
+
+  run_loop.Run();
+  EXPECT_EQ(1u, GetSetSize());
+  VerifySetContainsId(id);
+}
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
diff --git a/chrome/services/sharing/nearby/platform_v2/single_thread_executor_unittest.cc b/chrome/services/sharing/nearby/platform_v2/single_thread_executor_unittest.cc
new file mode 100644
index 0000000..4460abc3
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/single_thread_executor_unittest.cc
@@ -0,0 +1,96 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/services/sharing/nearby/platform_v2/submittable_executor.h"
+
+#include <map>
+#include <memory>
+#include <set>
+#include <utility>
+
+#include "base/callback_forward.h"
+#include "base/synchronization/lock.h"
+#include "base/task/thread_pool.h"
+#include "base/test/task_environment.h"
+#include "base/unguessable_token.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+// To test Execute(), which has no return value, each task is assigned a unique
+// ID. This ID is added to |executed_tasks_| when the task is Run(). Thus, the
+// presence of an ID within |executed_tasks_| means the associated task has
+// completed execution.
+class SingleThreadExecutorTest : public testing::Test {
+ protected:
+  Runnable CreateTrackedRunnable(base::RunLoop& run_loop,
+                                 const base::UnguessableToken& task_id) {
+    return [&] {
+      base::AutoLock al(executed_tasks_lock_);
+      executed_tasks_.insert(task_id);
+      run_loop.Quit();
+    };
+  }
+
+  bool HasTaskExecuted(const base::UnguessableToken& task_id) {
+    base::AutoLock al(executed_tasks_lock_);
+    return executed_tasks_.find(task_id) != executed_tasks_.end();
+  }
+
+  base::test::TaskEnvironment task_environment_;
+  std::unique_ptr<SubmittableExecutor> single_thread_executor_ =
+      std::make_unique<SubmittableExecutor>(
+          base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()}));
+
+ private:
+  base::Lock executed_tasks_lock_;
+  std::set<base::UnguessableToken> executed_tasks_;
+};
+
+TEST_F(SingleThreadExecutorTest, Submit) {
+  base::RunLoop run_loop;
+  base::UnguessableToken task_id = base::UnguessableToken::Create();
+  EXPECT_TRUE(single_thread_executor_->DoSubmit(
+      CreateTrackedRunnable(run_loop, task_id)));
+
+  run_loop.Run();
+  EXPECT_TRUE(HasTaskExecuted(task_id));
+}
+
+TEST_F(SingleThreadExecutorTest, Execute) {
+  base::RunLoop run_loop;
+  base::UnguessableToken task_id = base::UnguessableToken::Create();
+  single_thread_executor_->Execute(CreateTrackedRunnable(run_loop, task_id));
+
+  run_loop.Run();
+  EXPECT_TRUE(HasTaskExecuted(task_id));
+}
+
+TEST_F(SingleThreadExecutorTest, ShutdownPreventsFurtherTasks) {
+  single_thread_executor_->Shutdown();
+  base::RunLoop run_loop;
+  base::UnguessableToken task_id = base::UnguessableToken::Create();
+  EXPECT_FALSE(single_thread_executor_->DoSubmit(
+      CreateTrackedRunnable(run_loop, task_id)));
+
+  EXPECT_FALSE(HasTaskExecuted(task_id));
+}
+
+TEST_F(SingleThreadExecutorTest, DestroyAllowExistingTaskToComplete) {
+  base::RunLoop run_loop;
+  base::UnguessableToken task_id = base::UnguessableToken::Create();
+  EXPECT_TRUE(single_thread_executor_->DoSubmit(
+      CreateTrackedRunnable(run_loop, task_id)));
+  EXPECT_FALSE(HasTaskExecuted(task_id));
+  single_thread_executor_.reset();
+
+  run_loop.Run();
+  EXPECT_TRUE(HasTaskExecuted(task_id));
+}
+
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
diff --git a/chrome/services/sharing/nearby/platform_v2/submittable_executor.cc b/chrome/services/sharing/nearby/platform_v2/submittable_executor.cc
new file mode 100644
index 0000000..78f6051
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/submittable_executor.cc
@@ -0,0 +1,93 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/services/sharing/nearby/platform_v2/submittable_executor.h"
+
+#include "base/bind.h"
+#include "base/task/post_task.h"
+#include "base/task/thread_pool/thread_pool_instance.h"
+#include "base/threading/thread_restrictions.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+SubmittableExecutor::SubmittableExecutor(
+    scoped_refptr<base::TaskRunner> task_runner)
+    : task_runner_(std::move(task_runner)) {}
+
+SubmittableExecutor::~SubmittableExecutor() {
+  {
+    base::AutoLock al(lock_);
+    is_shut_down_ = true;
+    if (num_incomplete_tasks_ == 0)
+      last_task_completed_.Signal();
+  }
+
+  // Block until all pending tasks are finished.
+  last_task_completed_.Wait();
+
+#if DCHECK_IS_ON()
+  base::AutoLock al(lock_);
+  DCHECK_EQ(num_incomplete_tasks_, 0);
+#endif  // DCHECK_IS_ON()
+}  // namespace chrome
+
+// Once called, this method will prevent any future calls to Submit() or
+// Execute() from posting additional tasks. Previously posted asks will be
+// allowed to complete normally.
+void SubmittableExecutor::Shutdown() {
+  base::AutoLock al(lock_);
+  is_shut_down_ = true;
+}
+
+int SubmittableExecutor::GetTid(int index) const {
+  // SubmittableExecutor does not own a thread pool directly nor manages
+  // threads, thus cannot support this debug feature.
+  return 0;
+}
+
+// Posts the given |runnable| and returns true immediately. If Shutdown() has
+// been called, this method will return false.
+bool SubmittableExecutor::DoSubmit(Runnable&& runnable) {
+  base::AutoLock al(lock_);
+  if (is_shut_down_)
+    return false;
+
+  ++num_incomplete_tasks_;
+  return task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&SubmittableExecutor::RunTask,
+                                base::Unretained(this), std::move(runnable)));
+}
+
+// Posts the given |runnable| and returns immediately. If Shutdown() has been
+// called, this method will do nothing.
+void SubmittableExecutor::Execute(Runnable&& runnable) {
+  base::AutoLock al(lock_);
+  if (is_shut_down_)
+    return;
+
+  ++num_incomplete_tasks_;
+  task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&SubmittableExecutor::RunTask,
+                                base::Unretained(this), std::move(runnable)));
+}
+
+void SubmittableExecutor::RunTask(Runnable&& runnable) {
+  {
+    // base::ScopedAllowBaseSyncPrimitives is required as code inside the
+    // runnable uses blocking primitive, which lives outside Chrome.
+    base::ScopedAllowBaseSyncPrimitives allow_wait;
+    runnable();
+  }
+
+  base::AutoLock al(lock_);
+  DCHECK_GE(num_incomplete_tasks_, 1);
+  if (--num_incomplete_tasks_ == 0)
+    last_task_completed_.Signal();
+}
+
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
diff --git a/chrome/services/sharing/nearby/platform_v2/submittable_executor.h b/chrome/services/sharing/nearby/platform_v2/submittable_executor.h
new file mode 100644
index 0000000..cbacb794
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/submittable_executor.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_SUBMITTABLE_EXECUTOR_H_
+#define CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_SUBMITTABLE_EXECUTOR_H_
+
+#include <atomic>
+
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/task_runner.h"
+#include "base/thread_annotations.h"
+#include "chrome/services/sharing/nearby/platform_v2/atomic_boolean.h"
+#include "third_party/nearby/src/cpp/platform_v2/api/submittable_executor.h"
+
+namespace location {
+namespace nearby {
+namespace chrome {
+
+class SubmittableExecutor : public api::SubmittableExecutor {
+ public:
+  explicit SubmittableExecutor(scoped_refptr<base::TaskRunner> task_runner);
+  ~SubmittableExecutor() override;
+
+  SubmittableExecutor(const SubmittableExecutor&) = delete;
+  SubmittableExecutor& operator=(const SubmittableExecutor&) = delete;
+
+  // api::SubmittableExecutor:
+  bool DoSubmit(Runnable&& runnable) override;
+  void Execute(Runnable&& runnable) override;
+  void Shutdown() override;
+  int GetTid(int index) const override;
+
+ private:
+  // Directly calls run() on |runnable|. This is only meant to be posted as an
+  // asynchronous task within Execute().
+  void RunTask(Runnable&& runnable);
+
+  base::Lock lock_;
+  // Tracks number of incomplete tasks. Accessed from different threads through
+  // public APIs and task_runner_.
+  int num_incomplete_tasks_ GUARDED_BY(lock_) = 0;
+  // Tracks if the executor has been shutdown. Accessed from different threads
+  // through public APIs and task_runner_.
+  bool is_shut_down_ GUARDED_BY(lock_) = false;
+
+  // last_task_completed_ is used to notify destructor when the last task has
+  // completed.
+  base::WaitableEvent last_task_completed_;
+
+  scoped_refptr<base::TaskRunner> task_runner_;
+};
+
+}  // namespace chrome
+}  // namespace nearby
+}  // namespace location
+
+#endif  // CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_SUBMITTABLE_EXECUTOR_H_
diff --git a/chrome/services/sharing/nearby/platform_v2/system_clock.cc b/chrome/services/sharing/nearby/platform_v2/system_clock.cc
new file mode 100644
index 0000000..ffe39d7
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform_v2/system_clock.cc
@@ -0,0 +1,76 @@
+// Copyright (c) 2020 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/nearby/src/cpp/platform_v2/api/system_clock.h"
+
+#include "base/check_op.h"
+#include "base/threading/platform_thread.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+
+#if defined(OS_MACOSX)
+#include <mach/mach_time.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <time.h>
+
+#include "base/stl_util.h"
+#elif defined(OS_POSIX)
+#include <time.h>
+#else
+#include "base/system/sys_info.h"
+#endif  // defined(OS_MACOSX) || defined(OS_POSIX)
+
+namespace location {
+namespace nearby {
+
+void SystemClock::Init() {}
+
+absl::Time SystemClock::ElapsedRealtime() {
+#if defined(OS_MACOSX)
+  // Mac 10.12 supports mach_continuous_time, which is around 15 times faster
+  // than sysctl() call. Use it if possible; otherwise, fall back to sysctl().
+  if (__builtin_available(macOS 10.12, *)) {
+    return absl::FromUnixMicros(
+        base::TimeDelta::FromMachTime(mach_continuous_time()).InMicroseconds());
+  }
+
+  // On Mac mach_absolute_time stops while the device is sleeping. Instead use
+  // now - KERN_BOOTTIME to get a time difference that is not impacted by clock
+  // changes. KERN_BOOTTIME will be updated by the system whenever the system
+  // clock change.
+  struct timeval boottime;
+  int mib[2] = {CTL_KERN, KERN_BOOTTIME};
+  size_t size = sizeof(boottime);
+  int kr = sysctl(mib, base::size(mib), &boottime, &size, nullptr, 0);
+  DCHECK_EQ(KERN_SUCCESS, kr);
+  base::TimeDelta time_difference =
+      base::Time::FromCFAbsoluteTime(CFAbsoluteTimeGetCurrent()) -
+      (base::Time::FromTimeT(boottime.tv_sec) +
+       base::TimeDelta::FromMicroseconds(boottime.tv_usec));
+  return absl::FromUnixMicros(time_difference.InMicroseconds());
+#elif defined(OS_POSIX)
+  // SystemClock::ElapsedRealtime() must provide monotonically increasing time,
+  // but is not expected to be convertible to wall clock time. Unfortunately,
+  // the POSIX implementation of base::SysInfo::Uptime(), i.e. TimeTicks::Now(),
+  // does not increase when system is suspended. We instead use clock_gettime
+  // directly. See https://crbug.com/166153.
+  struct timespec ts = {};
+  const int ret = clock_gettime(CLOCK_BOOTTIME, &ts);
+  DCHECK_EQ(ret, 0);
+  return absl::TimeFromTimespec(ts);
+#else
+  return absl::FromUnixMicros(base::SysInfo::Uptime().InMicroseconds());
+#endif  // defined(OS_MACOSX) || defined(OS_POSIX)
+}
+
+Exception SystemClock::Sleep(absl::Duration duration) {
+  base::PlatformThread::Sleep(
+      base::TimeDelta::FromMicroseconds(absl::ToInt64Microseconds(duration)));
+  return {Exception::kSuccess};
+}
+
+}  // namespace nearby
+}  // namespace location
diff --git a/chrome/services/sharing/nearby/test/mock_bluetooth_adapter.cc b/chrome/services/sharing/nearby/test/mock_bluetooth_adapter.cc
new file mode 100644
index 0000000..7009435
--- /dev/null
+++ b/chrome/services/sharing/nearby/test/mock_bluetooth_adapter.cc
@@ -0,0 +1,30 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/services/sharing/nearby/test/mock_bluetooth_adapter.h"
+
+namespace bluetooth {
+
+MockBluetoothAdapater::MockBluetoothAdapater() = default;
+
+MockBluetoothAdapater::~MockBluetoothAdapater() = default;
+
+void MockBluetoothAdapater::ConnectToDevice(const std::string& address,
+                                            ConnectToDeviceCallback callback) {}
+
+void MockBluetoothAdapater::GetDevices(GetDevicesCallback callback) {}
+
+void MockBluetoothAdapater::GetInfo(GetInfoCallback callback) {
+  mojom::AdapterInfoPtr adapter_info = mojom::AdapterInfo::New();
+  adapter_info->present = present;
+  std::move(callback).Run(std::move(adapter_info));
+}
+
+void MockBluetoothAdapater::SetClient(
+    ::mojo::PendingRemote<mojom::AdapterClient> client) {}
+
+void MockBluetoothAdapater::StartDiscoverySession(
+    StartDiscoverySessionCallback callback) {}
+
+}  // namespace bluetooth
diff --git a/chrome/services/sharing/nearby/test/mock_bluetooth_adapter.h b/chrome/services/sharing/nearby/test/mock_bluetooth_adapter.h
new file mode 100644
index 0000000..655d35234
--- /dev/null
+++ b/chrome/services/sharing/nearby/test/mock_bluetooth_adapter.h
@@ -0,0 +1,34 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_SERVICES_SHARING_NEARBY_TEST_MOCK_BLUETOOTH_ADAPTER_H_
+#define CHROME_SERVICES_SHARING_NEARBY_TEST_MOCK_BLUETOOTH_ADAPTER_H_
+
+#include "device/bluetooth/public/mojom/adapter.mojom.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+
+namespace bluetooth {
+
+class MockBluetoothAdapater : public mojom::Adapter {
+ public:
+  MockBluetoothAdapater();
+  MockBluetoothAdapater(const MockBluetoothAdapater&) = delete;
+  MockBluetoothAdapater& operator=(const MockBluetoothAdapater&) = delete;
+  ~MockBluetoothAdapater() override;
+
+  // mojom::Adapter
+  void ConnectToDevice(const std::string& address,
+                       ConnectToDeviceCallback callback) override;
+  void GetDevices(GetDevicesCallback callback) override;
+  void GetInfo(GetInfoCallback callback) override;
+  void SetClient(::mojo::PendingRemote<mojom::AdapterClient> client) override;
+  void StartDiscoverySession(StartDiscoverySessionCallback callback) override;
+
+  mojo::Receiver<mojom::Adapter> adapter{this};
+  bool present = true;
+};
+
+}  // namespace bluetooth
+
+#endif  // CHROME_SERVICES_SHARING_NEARBY_TEST_MOCK_BLUETOOTH_ADAPTER_H_
diff --git a/chrome/services/sharing/nearby/test/mock_nearby_connections_host.cc b/chrome/services/sharing/nearby/test/mock_nearby_connections_host.cc
deleted file mode 100644
index de665b83..0000000
--- a/chrome/services/sharing/nearby/test/mock_nearby_connections_host.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/services/sharing/nearby/test/mock_nearby_connections_host.h"
-
-#include "chrome/services/sharing/public/mojom/webrtc.mojom.h"
-
-namespace location {
-namespace nearby {
-namespace connections {
-
-MockNearbyConnectionsHost::MockNearbyConnectionsHost() = default;
-
-MockNearbyConnectionsHost::~MockNearbyConnectionsHost() = default;
-
-void MockNearbyConnectionsHost::GetBluetoothAdapter(
-    GetBluetoothAdapterCallback callback) {
-  std::move(callback).Run(/*adapter=*/mojo::NullRemote());
-}
-
-void MockNearbyConnectionsHost::GetWebRtcSignalingMessenger(
-    GetWebRtcSignalingMessengerCallback callback) {
-  std::move(callback).Run(mojo::NullRemote());
-}
-
-}  // namespace connections
-}  // namespace nearby
-}  // namespace location
diff --git a/chrome/services/sharing/nearby/test/mock_nearby_connections_host.h b/chrome/services/sharing/nearby/test/mock_nearby_connections_host.h
deleted file mode 100644
index adea26c..0000000
--- a/chrome/services/sharing/nearby/test/mock_nearby_connections_host.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_SERVICES_SHARING_NEARBY_TEST_MOCK_NEARBY_CONNECTIONS_HOST_H_
-#define CHROME_SERVICES_SHARING_NEARBY_TEST_MOCK_NEARBY_CONNECTIONS_HOST_H_
-
-#include "chrome/services/sharing/public/mojom/nearby_connections.mojom.h"
-#include "mojo/public/cpp/bindings/receiver.h"
-
-namespace location {
-namespace nearby {
-namespace connections {
-
-// Mock NearbyConnectionsHost interface used in tests.
-class MockNearbyConnectionsHost : public mojom::NearbyConnectionsHost {
- public:
-  MockNearbyConnectionsHost();
-  MockNearbyConnectionsHost(const MockNearbyConnectionsHost&) = delete;
-  MockNearbyConnectionsHost& operator=(const MockNearbyConnectionsHost&) =
-      delete;
-  ~MockNearbyConnectionsHost() override;
-
-  // mojom::NearbyConnectionsHost
-  void GetBluetoothAdapter(GetBluetoothAdapterCallback callback) override;
-
-  void GetWebRtcSignalingMessenger(
-      GetWebRtcSignalingMessengerCallback callback) override;
-
-  mojo::Receiver<mojom::NearbyConnectionsHost> host{this};
-};
-
-}  // namespace connections
-}  // namespace nearby
-}  // namespace location
-
-#endif  // CHROME_SERVICES_SHARING_NEARBY_TEST_MOCK_NEARBY_CONNECTIONS_HOST_H_
diff --git a/chrome/services/sharing/nearby/test/mock_webrtc_signaling_messenger.cc b/chrome/services/sharing/nearby/test/mock_webrtc_signaling_messenger.cc
new file mode 100644
index 0000000..06aa37e
--- /dev/null
+++ b/chrome/services/sharing/nearby/test/mock_webrtc_signaling_messenger.cc
@@ -0,0 +1,26 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/services/sharing/nearby/test/mock_webrtc_signaling_messenger.h"
+
+namespace sharing {
+
+MockWebRtcSignalingMessenger::MockWebRtcSignalingMessenger() = default;
+
+MockWebRtcSignalingMessenger::~MockWebRtcSignalingMessenger() = default;
+
+void MockWebRtcSignalingMessenger::SendMessage(const std::string& self_id,
+                                               const std::string& peer_id,
+                                               const std::string& message,
+                                               SendMessageCallback callback) {}
+
+void MockWebRtcSignalingMessenger::StartReceivingMessages(
+    const std::string& self_id,
+    mojo::PendingRemote<sharing::mojom::IncomingMessagesListener>
+        incoming_messages_listener,
+    StartReceivingMessagesCallback callback) {}
+
+void MockWebRtcSignalingMessenger::StopReceivingMessages() {}
+
+}  // namespace sharing
diff --git a/chrome/services/sharing/nearby/test/mock_webrtc_signaling_messenger.h b/chrome/services/sharing/nearby/test/mock_webrtc_signaling_messenger.h
new file mode 100644
index 0000000..ed6eab29
--- /dev/null
+++ b/chrome/services/sharing/nearby/test/mock_webrtc_signaling_messenger.h
@@ -0,0 +1,35 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_SERVICES_SHARING_NEARBY_TEST_MOCK_WEBRTC_SIGNALING_MESSENGER_H_
+#define CHROME_SERVICES_SHARING_NEARBY_TEST_MOCK_WEBRTC_SIGNALING_MESSENGER_H_
+
+#include "chrome/services/sharing/public/mojom/webrtc_signaling_messenger.mojom.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+
+namespace sharing {
+
+class MockWebRtcSignalingMessenger : public mojom::WebRtcSignalingMessenger {
+ public:
+  MockWebRtcSignalingMessenger();
+  ~MockWebRtcSignalingMessenger() override;
+
+  // sharing::mojom::WebRtcSignalingMessenger:
+  void SendMessage(const std::string& self_id,
+                   const std::string& peer_id,
+                   const std::string& message,
+                   SendMessageCallback callback) override;
+  void StartReceivingMessages(
+      const std::string& self_id,
+      mojo::PendingRemote<sharing::mojom::IncomingMessagesListener>
+          incoming_messages_listener,
+      StartReceivingMessagesCallback callback) override;
+  void StopReceivingMessages() override;
+
+  mojo::Receiver<mojom::WebRtcSignalingMessenger> messenger{this};
+};
+
+}  // namespace sharing
+
+#endif  // CHROME_SERVICES_SHARING_NEARBY_TEST_MOCK_WEBRTC_SIGNALING_MESSENGER_H_
diff --git a/chrome/services/sharing/public/mojom/nearby_connections.mojom b/chrome/services/sharing/public/mojom/nearby_connections.mojom
index 271c776..017aff5 100644
--- a/chrome/services/sharing/public/mojom/nearby_connections.mojom
+++ b/chrome/services/sharing/public/mojom/nearby_connections.mojom
@@ -16,24 +16,10 @@
 interface NearbyConnections {
 };
 
-// Provides all runtime dependencies for the NearbyConnections library. Not all
-// methods need to return valid Mojo remotes if a particular interface is not
-// supported by the current hardware or OS version. Implemented in the browser
-// process and called from a sandboxed process typically at startup to get all
-// available mediums.
-interface NearbyConnectionsHost {
-  // Gets the current Bluetooth adapter from the host.
-  // There are three cases for the return value:
-  // 1) If Bluetooth is not supported by Chrome on the OS (see
-  //    BluetoothAdapterFactory::IsBluetoothSupported) we return a null remote.
-  // 2) If Bluetooth is supported by Chrome on the OS but there is not a valid
-  //    adapter, we return an adapter with the AdapterInfo.present=false because
-  //    this is the way BluetoothAdapterFactory currently behaves.
-  // 3) If Bluetooth is supported by Chrome on the OS and there is valid adapter
-  //    we return an adapter instance with the AdapterInfo.present=true.
-  GetBluetoothAdapter() => (pending_remote<bluetooth.mojom.Adapter>? adapter);
+// Provide all the dependencies that NearbyConnections library requires.
+struct NearbyConnectionsDependencies {
+  pending_remote<bluetooth.mojom.Adapter>? bluetooth_adapter;
 
-  // Gets the current webrtc signaling messenger from the host.
-  GetWebRtcSignalingMessenger()
-      => (pending_remote<sharing.mojom.WebRtcSignalingMessenger> messenger);
+  pending_remote<sharing.mojom.WebRtcSignalingMessenger>
+      webrtc_signaling_messenger;
 };
diff --git a/chrome/services/sharing/public/mojom/sharing.mojom b/chrome/services/sharing/public/mojom/sharing.mojom
index 300955eb..e704b64 100644
--- a/chrome/services/sharing/public/mojom/sharing.mojom
+++ b/chrome/services/sharing/public/mojom/sharing.mojom
@@ -31,17 +31,15 @@
       pending_remote<network.mojom.MdnsResponder> mdns_responder,
       array<IceServer> ice_servers);
 
-  // Creates a new Nearby Connections stack. All dependencies are provided via
-  // the |host| interface at runtime to allow dynamic capabilities like adding
-  // bluetooth adapters without restarting the service. Closing the |host| or
-  // the returned |connections| interface destroys the stack. Calling this
-  // method with an existing instance of the stack shuts down the previous one
-  // and creates a new one.
+  // Creates a new Nearby Connections stack. All dependencies should be provided
+  // via the |dependencies| struct. Closing the returned |connections| interface
+  // destroys the stack. Calling this method with an existing instance of the
+  // stack shuts down the previous one and creates a new one.
   CreateNearbyConnections(
-      pending_remote<location.nearby.connections.mojom.NearbyConnectionsHost>
-          host)
+      location.nearby.connections.mojom.NearbyConnectionsDependencies
+          dependencies)
       => (pending_remote<location.nearby.connections.mojom.NearbyConnections>
-              connections);
+             connections);
 
   // Creates a new Nearby Sharing Decoder.
   CreateNearbySharingDecoder()
diff --git a/chrome/services/sharing/sharing_impl.cc b/chrome/services/sharing/sharing_impl.cc
index 8b6260e..751bdedd 100644
--- a/chrome/services/sharing/sharing_impl.cc
+++ b/chrome/services/sharing/sharing_impl.cc
@@ -47,14 +47,14 @@
 }
 
 void SharingImpl::CreateNearbyConnections(
-    mojo::PendingRemote<NearbyConnectionsHostMojom> host,
+    NearbyConnectionsDependenciesPtr dependencies,
     CreateNearbyConnectionsCallback callback) {
   // Reset old instance of Nearby Connections stack.
   nearby_connections_.reset();
 
   mojo::PendingRemote<NearbyConnectionsMojom> remote;
   nearby_connections_ = std::make_unique<NearbyConnections>(
-      remote.InitWithNewPipeAndPassReceiver(), std::move(host),
+      remote.InitWithNewPipeAndPassReceiver(), std::move(dependencies),
       base::BindOnce(&SharingImpl::NearbyConnectionsDisconnected,
                      weak_ptr_factory_.GetWeakPtr()));
   std::move(callback).Run(std::move(remote));
diff --git a/chrome/services/sharing/sharing_impl.h b/chrome/services/sharing/sharing_impl.h
index 4fc6558c..e148347 100644
--- a/chrome/services/sharing/sharing_impl.h
+++ b/chrome/services/sharing/sharing_impl.h
@@ -40,11 +40,11 @@
 
 class SharingImpl : public mojom::Sharing {
  public:
-  using NearbyConnectionsHostMojom =
-      location::nearby::connections::mojom::NearbyConnectionsHost;
   using NearbyConnectionsMojom =
       location::nearby::connections::mojom::NearbyConnections;
   using NearbyConnections = location::nearby::connections::NearbyConnections;
+  using NearbyConnectionsDependenciesPtr =
+      location::nearby::connections::mojom::NearbyConnectionsDependenciesPtr;
 
   explicit SharingImpl(mojo::PendingReceiver<mojom::Sharing> receiver);
   SharingImpl(const SharingImpl&) = delete;
@@ -61,7 +61,7 @@
       mojo::PendingRemote<network::mojom::MdnsResponder> mdns_responder,
       std::vector<mojom::IceServerPtr> ice_servers) override;
   void CreateNearbyConnections(
-      mojo::PendingRemote<NearbyConnectionsHostMojom> host,
+      NearbyConnectionsDependenciesPtr dependencies,
       CreateNearbyConnectionsCallback callback) override;
   void CreateNearbySharingDecoder(
       CreateNearbySharingDecoderCallback callback) override;
diff --git a/chrome/services/sharing/sharing_impl_unittest.cc b/chrome/services/sharing/sharing_impl_unittest.cc
index 1d3c2bb0..73af9726 100644
--- a/chrome/services/sharing/sharing_impl_unittest.cc
+++ b/chrome/services/sharing/sharing_impl_unittest.cc
@@ -6,9 +6,12 @@
 
 #include <utility>
 
+#include "base/run_loop.h"
 #include "base/test/bind_test_util.h"
 #include "base/test/task_environment.h"
-#include "chrome/services/sharing/nearby/test/mock_nearby_connections_host.h"
+#include "chrome/services/sharing/nearby/nearby_connections.h"
+#include "chrome/services/sharing/nearby/test/mock_bluetooth_adapter.h"
+#include "chrome/services/sharing/nearby/test/mock_webrtc_signaling_messenger.h"
 #include "chrome/services/sharing/public/mojom/nearby_decoder.mojom.h"
 #include "chrome/services/sharing/webrtc/test/mock_sharing_connection_host.h"
 #include "mojo/public/cpp/bindings/remote.h"
@@ -18,11 +21,10 @@
 
 class SharingImplTest : public testing::Test {
  public:
-  using MockNearbyConnectionsHost =
-      location::nearby::connections::MockNearbyConnectionsHost;
   using NearbyConnectionsMojom =
       location::nearby::connections::mojom::NearbyConnections;
   using NearbySharingDecoderMojom = sharing::mojom::NearbySharingDecoder;
+  using NearbyConnections = location::nearby::connections::NearbyConnections;
 
   SharingImplTest() {
     service_ =
@@ -49,24 +51,39 @@
     return connection;
   }
 
-  std::unique_ptr<MockNearbyConnectionsHost> CreateNearbyConnections(
-      mojo::Remote<NearbyConnectionsMojom>* remote) {
-    auto host = std::make_unique<MockNearbyConnectionsHost>();
+  mojo::Remote<NearbyConnectionsMojom> CreateNearbyConnections(
+      mojo::PendingRemote<bluetooth::mojom::Adapter> bluetooth_adapter,
+      mojo::PendingRemote<sharing::mojom::WebRtcSignalingMessenger>
+          webrtc_signaling_messenger) {
+    mojo::Remote<NearbyConnectionsMojom> connections;
+    auto dependencies =
+        location::nearby::connections::mojom::NearbyConnectionsDependencies::
+            New(std::move(bluetooth_adapter),
+                std::move(webrtc_signaling_messenger));
+    base::RunLoop run_loop;
     service()->CreateNearbyConnections(
-        host->host.BindNewPipeAndPassRemote(),
+        std::move(dependencies),
         base::BindLambdaForTesting(
             [&](mojo::PendingRemote<NearbyConnectionsMojom> pending_remote) {
-              remote->Bind(std::move(pending_remote));
+              connections.Bind(std::move(pending_remote));
+              run_loop.Quit();
             }));
-    return host;
+    // Wait until NearbyConnectionsMojom is connected.
+    run_loop.Run();
+    return connections;
   }
 
-  void CreateNearbySharingDecoder(
-      mojo::Remote<NearbySharingDecoderMojom>* remote) {
+  mojo::Remote<NearbySharingDecoderMojom> CreateNearbySharingDecoder() {
+    mojo::Remote<NearbySharingDecoderMojom> remote;
+    base::RunLoop run_loop;
     service()->CreateNearbySharingDecoder(base::BindLambdaForTesting(
         [&](mojo::PendingRemote<NearbySharingDecoderMojom> pending_remote) {
-          remote->Bind(std::move(pending_remote));
+          remote.Bind(std::move(pending_remote));
+          run_loop.Quit();
         }));
+    // Wait until NearbySharingDecoderMojom is connected.
+    run_loop.Run();
+    return remote;
   }
 
  protected:
@@ -104,20 +121,29 @@
 }
 
 TEST_F(SharingImplTest, NearbyConnections_Create) {
-  mojo::Remote<NearbyConnectionsMojom> connections;
-  auto connections_host = CreateNearbyConnections(&connections);
+  bluetooth::MockBluetoothAdapater bluetooth_adapter;
+  sharing::MockWebRtcSignalingMessenger webrtc_signaling_messenger;
+  mojo::Remote<NearbyConnectionsMojom> connections = CreateNearbyConnections(
+      bluetooth_adapter.adapter.BindNewPipeAndPassRemote(),
+      webrtc_signaling_messenger.messenger.BindNewPipeAndPassRemote());
 
   EXPECT_TRUE(connections.is_connected());
 }
 
 TEST_F(SharingImplTest, NearbyConnections_CreateMultiple) {
-  mojo::Remote<NearbyConnectionsMojom> connections_1;
-  auto connections_host_1 = CreateNearbyConnections(&connections_1);
+  bluetooth::MockBluetoothAdapater bluetooth_adapter_1;
+  sharing::MockWebRtcSignalingMessenger webrtc_signaling_messenger_1;
+  mojo::Remote<NearbyConnectionsMojom> connections_1 = CreateNearbyConnections(
+      bluetooth_adapter_1.adapter.BindNewPipeAndPassRemote(),
+      webrtc_signaling_messenger_1.messenger.BindNewPipeAndPassRemote());
   EXPECT_TRUE(connections_1.is_connected());
 
   // Calling CreateNearbyConnections() again should disconnect the old instance.
-  mojo::Remote<NearbyConnectionsMojom> connections_2;
-  auto connections_host_2 = CreateNearbyConnections(&connections_2);
+  bluetooth::MockBluetoothAdapater bluetooth_adapter_2;
+  sharing::MockWebRtcSignalingMessenger webrtc_signaling_messenger_2;
+  mojo::Remote<NearbyConnectionsMojom> connections_2 = CreateNearbyConnections(
+      bluetooth_adapter_2.adapter.BindNewPipeAndPassRemote(),
+      webrtc_signaling_messenger_2.messenger.BindNewPipeAndPassRemote());
 
   // Run mojo disconnect handlers.
   base::RunLoop().RunUntilIdle();
@@ -126,14 +152,17 @@
   EXPECT_TRUE(connections_2.is_connected());
 }
 
-TEST_F(SharingImplTest, NearbyConnections_Disconnects) {
-  mojo::Remote<NearbyConnectionsMojom> connections;
-  auto connections_host = CreateNearbyConnections(&connections);
+TEST_F(SharingImplTest, NearbyConnections_BluetoothDisconnects) {
+  bluetooth::MockBluetoothAdapater bluetooth_adapter;
+  sharing::MockWebRtcSignalingMessenger webrtc_signaling_messenger;
+  mojo::Remote<NearbyConnectionsMojom> connections = CreateNearbyConnections(
+      bluetooth_adapter.adapter.BindNewPipeAndPassRemote(),
+      webrtc_signaling_messenger.messenger.BindNewPipeAndPassRemote());
   EXPECT_TRUE(connections.is_connected());
 
-  // Disconnecting the |connections_host| interface should also disconnect and
-  // destroy the |connections| interface.
-  connections_host.reset();
+  // Disconnecting the |bluetooth_adapter| interface should also
+  // disconnect and destroy the |connections| interface.
+  bluetooth_adapter.adapter.reset();
 
   // Run mojo disconnect handlers.
   base::RunLoop().RunUntilIdle();
@@ -141,21 +170,46 @@
   EXPECT_FALSE(connections.is_connected());
 }
 
+TEST_F(SharingImplTest, NearbyConnections_WebRtcSignalingMessengerDisconnects) {
+  bluetooth::MockBluetoothAdapater bluetooth_adapter;
+  sharing::MockWebRtcSignalingMessenger webrtc_signaling_messenger;
+  mojo::Remote<NearbyConnectionsMojom> connections = CreateNearbyConnections(
+      bluetooth_adapter.adapter.BindNewPipeAndPassRemote(),
+      webrtc_signaling_messenger.messenger.BindNewPipeAndPassRemote());
+  EXPECT_TRUE(connections.is_connected());
+
+  // Disconnecting the |webrtc_signaling_messenger| interface should also
+  // disconnect and destroy the |connections| interface.
+  webrtc_signaling_messenger.messenger.reset();
+
+  // Run mojo disconnect handlers.
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(connections.is_connected());
+}
+
+TEST_F(SharingImplTest, NearbyConnections_NullBluetoothAdapter) {
+  sharing::MockWebRtcSignalingMessenger webrtc_signaling_messenger;
+  mojo::Remote<NearbyConnectionsMojom> connections = CreateNearbyConnections(
+      mojo::NullRemote(),
+      webrtc_signaling_messenger.messenger.BindNewPipeAndPassRemote());
+  EXPECT_TRUE(connections.is_connected());
+}
+
 TEST_F(SharingImplTest, NearbySharingDecoder_Create) {
-  mojo::Remote<NearbySharingDecoderMojom> remote;
-  CreateNearbySharingDecoder(&remote);
+  mojo::Remote<NearbySharingDecoderMojom> remote = CreateNearbySharingDecoder();
   EXPECT_TRUE(remote.is_connected());
 }
 
 TEST_F(SharingImplTest, NearbySharingDecoder_CreateMultiple) {
-  mojo::Remote<NearbySharingDecoderMojom> remote_1;
-  CreateNearbySharingDecoder(&remote_1);
+  mojo::Remote<NearbySharingDecoderMojom> remote_1 =
+      CreateNearbySharingDecoder();
   EXPECT_TRUE(remote_1.is_connected());
 
   // Calling CreateNearbySharingDecoder() again should disconnect the old
   // instance.
-  mojo::Remote<NearbySharingDecoderMojom> remote_2;
-  CreateNearbySharingDecoder(&remote_2);
+  mojo::Remote<NearbySharingDecoderMojom> remote_2 =
+      CreateNearbySharingDecoder();
 
   // Run mojo disconnect handlers.
   base::RunLoop().RunUntilIdle();
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 6cb4212..46f685a6 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1038,6 +1038,7 @@
       "../browser/pdf/pdf_extension_test_util.h",
       "../browser/performance_manager/background_tab_loading_policy_browsertest.cc",
       "../browser/performance_manager/graph/page_node_impl_browsertest.cc",
+      "../browser/performance_manager/mechanisms/page_discarder_browsertest.cc",
       "../browser/performance_manager/page_load_tracker_decorator_browsertest.cc",
       "../browser/performance_manager/tab_properties_decorator_browsertest.cc",
       "../browser/permissions/permission_delegation_browsertest.cc",
@@ -1167,6 +1168,7 @@
       "../browser/ssl/crlset_browsertest.cc",
       "../browser/ssl/known_interception_disclosure_infobar_browsertest.cc",
       "../browser/ssl/known_interception_disclosure_ui_browsertest.cc",
+      "../browser/ssl/ocsp_browsertest.cc",
       "../browser/ssl/security_state_tab_helper_browsertest.cc",
       "../browser/ssl/ssl_browsertest.cc",
       "../browser/ssl/ssl_client_certificate_selector_test.cc",
@@ -4068,6 +4070,7 @@
       "../browser/memory/enterprise_memory_limit_evaluator_unittest.cc",
       "../browser/memory/swap_thrashing_monitor_delegate_win_unittest.cc",
       "../browser/metrics/desktop_session_duration/desktop_session_duration_tracker_unittest.cc",
+      "../browser/metrics/desktop_session_duration/touch_mode_stats_tracker_unittest.cc",
       "../browser/metrics/tab_stats_data_store_unittest.cc",
       "../browser/metrics/tab_stats_tracker_unittest.cc",
       "../browser/page_load_metrics/observers/session_restore_page_load_metrics_observer_unittest.cc",
@@ -4338,6 +4341,7 @@
       "//chrome/browser/resource_coordinator:tab_metrics_event_proto",
       "//chrome/browser/resource_coordinator/tab_ranker:tab_features_test_helper",
       "//chrome/services/sharing:unit_tests",
+      "//chrome/services/sharing/nearby/platform_v2:unit_tests",
       "//chrome/services/sharing/nearby_decoder:unit_tests",
       "//chrome/services/sharing/public/cpp:unit_tests",
       "//chrome/services/speech:unit_tests",
@@ -5998,11 +6002,15 @@
     ]
 
     if (include_js_tests) {
-      deps += [ "//chrome/test/data/webui:interactive_ui_tests_js_webui" ]
+      deps += [
+        "//chrome/test/data/webui:interactive_ui_tests_js_mojo_lite_webui",
+        "//chrome/test/data/webui:interactive_ui_tests_js_webui",
+      ]
     }
 
     # Runtime dependencies
     data_deps = [
+      "//chrome:browser_tests_pak",
       "//ppapi:ppapi_tests",
       "//testing/buildbot/filters:interactive_ui_tests_filters",
       "//third_party/mesa_headers",
@@ -6240,6 +6248,7 @@
     "//components/gwp_asan/buildflags",
     "//components/heap_profiling/in_process",
     "//components/safe_browsing:buildflags",
+    "//pdf:pdf_ppapi",
   ]
   if (!is_fuchsia) {
     # TODO(crbug.com/753619): Enable crash reporting on Fuchsia.
diff --git a/chrome/test/chromedriver/chrome/device_manager.cc b/chrome/test/chromedriver/chrome/device_manager.cc
index f4e32d7..6eb505ad 100644
--- a/chrome/test/chromedriver/chrome/device_manager.cc
+++ b/chrome/test/chromedriver/chrome/device_manager.cc
@@ -20,15 +20,15 @@
 
 const char kChromeCmdLineFile[] = "/data/local/tmp/chrome-command-line";
 
-Device::Device(
-    const std::string& device_serial, Adb* adb,
-    base::Callback<void()> release_callback)
+Device::Device(const std::string& device_serial,
+               Adb* adb,
+               base::OnceCallback<void()> release_callback)
     : serial_(device_serial),
       adb_(adb),
-      release_callback_(release_callback) {}
+      release_callback_(std::move(release_callback)) {}
 
 Device::~Device() {
-  release_callback_.Run();
+  std::move(release_callback_).Run();
 }
 
 // Only allow completely alpha exec names.
@@ -264,8 +264,8 @@
 Device* DeviceManager::LockDevice(const std::string& device_serial) {
   active_devices_.push_back(device_serial);
   return new Device(device_serial, adb_,
-      base::Bind(&DeviceManager::ReleaseDevice, base::Unretained(this),
-                 device_serial));
+                    base::BindOnce(&DeviceManager::ReleaseDevice,
+                                   base::Unretained(this), device_serial));
 }
 
 bool DeviceManager::IsDeviceLocked(const std::string& device_serial) {
diff --git a/chrome/test/chromedriver/chrome/device_manager.h b/chrome/test/chromedriver/chrome/device_manager.h
index 139b1f1f..18e2de12 100644
--- a/chrome/test/chromedriver/chrome/device_manager.h
+++ b/chrome/test/chromedriver/chrome/device_manager.h
@@ -39,7 +39,7 @@
 
   Device(const std::string& device_serial,
          Adb* adb,
-         base::Callback<void()> release_callback);
+         base::OnceCallback<void()> release_callback);
 
   Status ForwardDevtoolsPort(const std::string& package,
                              const std::string& process,
@@ -49,7 +49,7 @@
   const std::string serial_;
   std::string active_package_;
   Adb* adb_;
-  base::Callback<void()> release_callback_;
+  base::OnceCallback<void()> release_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(Device);
 };
diff --git a/chrome/test/chromedriver/chrome/devtools_client_impl.cc b/chrome/test/chromedriver/chrome/devtools_client_impl.cc
index e0c8f34..c4c0b25 100644
--- a/chrome/test/chromedriver/chrome/devtools_client_impl.cc
+++ b/chrome/test/chromedriver/chrome/devtools_client_impl.cc
@@ -84,8 +84,8 @@
       crashed_(false),
       detached_(false),
       id_(id),
-      frontend_closer_func_(base::Bind(&FakeCloseFrontends)),
-      parser_func_(base::Bind(&internal::ParseInspectorMessage)),
+      frontend_closer_func_(base::BindRepeating(&FakeCloseFrontends)),
+      parser_func_(base::BindRepeating(&internal::ParseInspectorMessage)),
       unnotified_event_(nullptr),
       next_id_(1),
       stack_count_(0) {
@@ -105,7 +105,7 @@
       detached_(false),
       id_(id),
       frontend_closer_func_(frontend_closer_func),
-      parser_func_(base::Bind(&internal::ParseInspectorMessage)),
+      parser_func_(base::BindRepeating(&internal::ParseInspectorMessage)),
       unnotified_event_(nullptr),
       next_id_(1),
       stack_count_(0) {
diff --git a/chrome/test/chromedriver/chrome/devtools_client_impl.h b/chrome/test/chromedriver/chrome/devtools_client_impl.h
index c57185a..1753bc12 100644
--- a/chrome/test/chromedriver/chrome/devtools_client_impl.h
+++ b/chrome/test/chromedriver/chrome/devtools_client_impl.h
@@ -60,7 +60,7 @@
                      const std::string& url,
                      const std::string& id);
 
-  typedef base::Callback<Status()> FrontendCloserFunc;
+  typedef base::RepeatingCallback<Status()> FrontendCloserFunc;
   DevToolsClientImpl(const SyncWebSocketFactory& factory,
                      const std::string& url,
                      const std::string& id,
@@ -68,12 +68,12 @@
 
   DevToolsClientImpl(DevToolsClientImpl* parent, const std::string& session_id);
 
-  typedef base::Callback<bool(const std::string&,
-                              int,
-                              std::string*,
-                              internal::InspectorMessageType*,
-                              internal::InspectorEvent*,
-                              internal::InspectorCommandResponse*)>
+  typedef base::RepeatingCallback<bool(const std::string&,
+                                       int,
+                                       std::string*,
+                                       internal::InspectorMessageType*,
+                                       internal::InspectorEvent*,
+                                       internal::InspectorCommandResponse*)>
       ParserFunc;
   DevToolsClientImpl(const SyncWebSocketFactory& factory,
                      const std::string& url,
diff --git a/chrome/test/chromedriver/chrome/devtools_client_impl_unittest.cc b/chrome/test/chromedriver/chrome/devtools_client_impl_unittest.cc
index c7d9e3b..c9fb5f0 100644
--- a/chrome/test/chromedriver/chrome/devtools_client_impl_unittest.cc
+++ b/chrome/test/chromedriver/chrome/devtools_client_impl_unittest.cc
@@ -155,9 +155,9 @@
 
 TEST_F(DevToolsClientImplTest, SendCommand) {
   SyncWebSocketFactory factory =
-      base::Bind(&CreateMockSyncWebSocket<MockSyncWebSocket>);
+      base::BindRepeating(&CreateMockSyncWebSocket<MockSyncWebSocket>);
   DevToolsClientImpl client(factory, "http://url", "id",
-                            base::Bind(&CloserFunc));
+                            base::BindRepeating(&CloserFunc));
   ASSERT_EQ(kOk, client.ConnectIfNecessary().code());
   base::DictionaryValue params;
   params.SetInteger("param", 1);
@@ -166,9 +166,9 @@
 
 TEST_F(DevToolsClientImplTest, SendCommandAndGetResult) {
   SyncWebSocketFactory factory =
-      base::Bind(&CreateMockSyncWebSocket<MockSyncWebSocket>);
+      base::BindRepeating(&CreateMockSyncWebSocket<MockSyncWebSocket>);
   DevToolsClientImpl client(factory, "http://url", "id",
-                            base::Bind(&CloserFunc));
+                            base::BindRepeating(&CloserFunc));
   ASSERT_EQ(kOk, client.ConnectIfNecessary().code());
   base::DictionaryValue params;
   params.SetInteger("param", 1);
@@ -210,9 +210,9 @@
 
 TEST_F(DevToolsClientImplTest, ConnectIfNecessaryConnectFails) {
   SyncWebSocketFactory factory =
-      base::Bind(&CreateMockSyncWebSocket<MockSyncWebSocket2>);
+      base::BindRepeating(&CreateMockSyncWebSocket<MockSyncWebSocket2>);
   DevToolsClientImpl client(factory, "http://url", "id",
-                            base::Bind(&CloserFunc));
+                            base::BindRepeating(&CloserFunc));
   ASSERT_EQ(kDisconnected, client.ConnectIfNecessary().code());
 }
 
@@ -266,10 +266,10 @@
 }  // namespace
 
 TEST_F(DevToolsClientImplTest, SendCommandSendFails) {
-  SyncWebSocketFactory factory =
-      base::Bind(&CreateMockSyncWebSocket_B<MockSyncWebSocket3>, false);
+  SyncWebSocketFactory factory = base::BindRepeating(
+      &CreateMockSyncWebSocket_B<MockSyncWebSocket3>, false);
   DevToolsClientImpl client(factory, "http://url", "id",
-                            base::Bind(&CloserFunc));
+                            base::BindRepeating(&CloserFunc));
   ASSERT_EQ(kOk, client.ConnectIfNecessary().code());
   base::DictionaryValue params;
   ASSERT_TRUE(client.SendCommand("method", params).IsError());
@@ -277,9 +277,9 @@
 
 TEST_F(DevToolsClientImplTest, SendCommandReceiveNextMessageFails) {
   SyncWebSocketFactory factory =
-      base::Bind(&CreateMockSyncWebSocket_B<MockSyncWebSocket3>, true);
+      base::BindRepeating(&CreateMockSyncWebSocket_B<MockSyncWebSocket3>, true);
   DevToolsClientImpl client(factory, "http://url", "id",
-                            base::Bind(&CloserFunc));
+                            base::BindRepeating(&CloserFunc));
   ASSERT_EQ(kOk, client.ConnectIfNecessary().code());
   base::DictionaryValue params;
   ASSERT_TRUE(client.SendCommand("method", params).IsError());
@@ -487,10 +487,10 @@
 
 TEST_F(DevToolsClientImplTest, SendCommandOnlyConnectsOnce) {
   SyncWebSocketFactory factory =
-      base::Bind(&CreateMockSyncWebSocket<FakeSyncWebSocket>);
+      base::BindRepeating(&CreateMockSyncWebSocket<FakeSyncWebSocket>);
   DevToolsClientImpl client(factory, "http://url", "id",
-                            base::Bind(&CloserFunc),
-                            base::Bind(&ReturnCommand));
+                            base::BindRepeating(&CloserFunc),
+                            base::BindRepeating(&ReturnCommand));
   ASSERT_EQ(kOk, client.ConnectIfNecessary().code());
   base::DictionaryValue params;
   ASSERT_TRUE(client.SendCommand("method", params).IsOk());
@@ -499,47 +499,48 @@
 
 TEST_F(DevToolsClientImplTest, SendCommandBadResponse) {
   SyncWebSocketFactory factory =
-      base::Bind(&CreateMockSyncWebSocket<FakeSyncWebSocket>);
+      base::BindRepeating(&CreateMockSyncWebSocket<FakeSyncWebSocket>);
   DevToolsClientImpl client(factory, "http://url", "id",
-                            base::Bind(&CloserFunc));
+                            base::BindRepeating(&CloserFunc));
   ASSERT_EQ(kOk, client.ConnectIfNecessary().code());
-  client.SetParserFuncForTesting(base::Bind(&ReturnBadResponse));
+  client.SetParserFuncForTesting(base::BindRepeating(&ReturnBadResponse));
   base::DictionaryValue params;
   ASSERT_TRUE(client.SendCommand("method", params).IsError());
 }
 
 TEST_F(DevToolsClientImplTest, SendCommandBadId) {
   SyncWebSocketFactory factory =
-      base::Bind(&CreateMockSyncWebSocket<FakeSyncWebSocket>);
+      base::BindRepeating(&CreateMockSyncWebSocket<FakeSyncWebSocket>);
   DevToolsClientImpl client(factory, "http://url", "id",
-                            base::Bind(&CloserFunc));
+                            base::BindRepeating(&CloserFunc));
   ASSERT_EQ(kOk, client.ConnectIfNecessary().code());
-  client.SetParserFuncForTesting(base::Bind(&ReturnCommandBadId));
+  client.SetParserFuncForTesting(base::BindRepeating(&ReturnCommandBadId));
   base::DictionaryValue params;
   ASSERT_TRUE(client.SendCommand("method", params).IsError());
 }
 
 TEST_F(DevToolsClientImplTest, SendCommandResponseError) {
   SyncWebSocketFactory factory =
-      base::Bind(&CreateMockSyncWebSocket<FakeSyncWebSocket>);
+      base::BindRepeating(&CreateMockSyncWebSocket<FakeSyncWebSocket>);
   DevToolsClientImpl client(factory, "http://url", "id",
-                            base::Bind(&CloserFunc));
+                            base::BindRepeating(&CloserFunc));
   ASSERT_EQ(kOk, client.ConnectIfNecessary().code());
-  client.SetParserFuncForTesting(base::Bind(&ReturnCommandError));
+  client.SetParserFuncForTesting(base::BindRepeating(&ReturnCommandError));
   base::DictionaryValue params;
   ASSERT_TRUE(client.SendCommand("method", params).IsError());
 }
 
 TEST_F(DevToolsClientImplTest, SendCommandEventBeforeResponse) {
   SyncWebSocketFactory factory =
-      base::Bind(&CreateMockSyncWebSocket<FakeSyncWebSocket>);
+      base::BindRepeating(&CreateMockSyncWebSocket<FakeSyncWebSocket>);
   MockListener listener;
   bool first = true;
   DevToolsClientImpl client(factory, "http://url", "id",
-                            base::Bind(&CloserFunc));
+                            base::BindRepeating(&CloserFunc));
   client.AddListener(&listener);
   ASSERT_EQ(kOk, client.ConnectIfNecessary().code());
-  client.SetParserFuncForTesting(base::Bind(&ReturnEventThenResponse, &first));
+  client.SetParserFuncForTesting(
+      base::BindRepeating(&ReturnEventThenResponse, &first));
   base::DictionaryValue params;
   std::unique_ptr<base::DictionaryValue> result;
   ASSERT_TRUE(client.SendCommandAndGetResult("method", params, &result).IsOk());
@@ -684,12 +685,12 @@
 TEST_F(DevToolsClientImplTest, HandleEventsUntil) {
   MockListener listener;
   SyncWebSocketFactory factory =
-      base::Bind(&CreateMockSyncWebSocket<MockSyncWebSocket>);
+      base::BindRepeating(&CreateMockSyncWebSocket<MockSyncWebSocket>);
   DevToolsClientImpl client(factory, "http://url", "id",
-                            base::Bind(&CloserFunc));
+                            base::BindRepeating(&CloserFunc));
   client.AddListener(&listener);
   ASSERT_EQ(kOk, client.ConnectIfNecessary().code());
-  client.SetParserFuncForTesting(base::Bind(&ReturnEvent));
+  client.SetParserFuncForTesting(base::BindRepeating(&ReturnEvent));
   Status status = client.HandleEventsUntil(base::BindRepeating(&AlwaysTrue),
                                            Timeout(long_timeout_));
   ASSERT_EQ(kOk, status.code());
@@ -697,11 +698,11 @@
 
 TEST_F(DevToolsClientImplTest, HandleEventsUntilTimeout) {
   SyncWebSocketFactory factory =
-      base::Bind(&CreateMockSyncWebSocket<MockSyncWebSocket>);
+      base::BindRepeating(&CreateMockSyncWebSocket<MockSyncWebSocket>);
   DevToolsClientImpl client(factory, "http://url", "id",
-                            base::Bind(&CloserFunc));
+                            base::BindRepeating(&CloserFunc));
   ASSERT_EQ(kOk, client.ConnectIfNecessary().code());
-  client.SetParserFuncForTesting(base::Bind(&ReturnEvent));
+  client.SetParserFuncForTesting(base::BindRepeating(&ReturnEvent));
   Status status = client.HandleEventsUntil(base::BindRepeating(&AlwaysTrue),
                                            Timeout(base::TimeDelta()));
   ASSERT_EQ(kTimeout, status.code());
@@ -709,10 +710,10 @@
 
 TEST_F(DevToolsClientImplTest, WaitForNextEventCommand) {
   SyncWebSocketFactory factory =
-      base::Bind(&CreateMockSyncWebSocket<MockSyncWebSocket>);
+      base::BindRepeating(&CreateMockSyncWebSocket<MockSyncWebSocket>);
   DevToolsClientImpl client(factory, "http://url", "id",
-                            base::Bind(&CloserFunc),
-                            base::Bind(&ReturnCommand));
+                            base::BindRepeating(&CloserFunc),
+                            base::BindRepeating(&ReturnCommand));
   ASSERT_EQ(kOk, client.ConnectIfNecessary().code());
   Status status = client.HandleEventsUntil(base::BindRepeating(&AlwaysTrue),
                                            Timeout(long_timeout_));
@@ -721,11 +722,11 @@
 
 TEST_F(DevToolsClientImplTest, WaitForNextEventError) {
   SyncWebSocketFactory factory =
-      base::Bind(&CreateMockSyncWebSocket<MockSyncWebSocket>);
+      base::BindRepeating(&CreateMockSyncWebSocket<MockSyncWebSocket>);
   DevToolsClientImpl client(factory, "http://url", "id",
-                            base::Bind(&CloserFunc));
+                            base::BindRepeating(&CloserFunc));
   ASSERT_EQ(kOk, client.ConnectIfNecessary().code());
-  client.SetParserFuncForTesting(base::Bind(&ReturnError));
+  client.SetParserFuncForTesting(base::BindRepeating(&ReturnError));
   Status status = client.HandleEventsUntil(base::BindRepeating(&AlwaysTrue),
                                            Timeout(long_timeout_));
   ASSERT_EQ(kUnknownError, status.code());
@@ -733,11 +734,11 @@
 
 TEST_F(DevToolsClientImplTest, WaitForNextEventConditionalFuncReturnsError) {
   SyncWebSocketFactory factory =
-      base::Bind(&CreateMockSyncWebSocket<MockSyncWebSocket>);
+      base::BindRepeating(&CreateMockSyncWebSocket<MockSyncWebSocket>);
   DevToolsClientImpl client(factory, "http://url", "id",
-                            base::Bind(&CloserFunc));
+                            base::BindRepeating(&CloserFunc));
   ASSERT_EQ(kOk, client.ConnectIfNecessary().code());
-  client.SetParserFuncForTesting(base::Bind(&ReturnEvent));
+  client.SetParserFuncForTesting(base::BindRepeating(&ReturnEvent));
   Status status = client.HandleEventsUntil(base::BindRepeating(&AlwaysError),
                                            Timeout(long_timeout_));
   ASSERT_EQ(kUnknownError, status.code());
@@ -745,13 +746,13 @@
 
 TEST_F(DevToolsClientImplTest, NestedCommandsWithOutOfOrderResults) {
   SyncWebSocketFactory factory =
-      base::Bind(&CreateMockSyncWebSocket<MockSyncWebSocket>);
+      base::BindRepeating(&CreateMockSyncWebSocket<MockSyncWebSocket>);
   int recurse_count = 0;
   DevToolsClientImpl client(factory, "http://url", "id",
-                            base::Bind(&CloserFunc));
+                            base::BindRepeating(&CloserFunc));
   ASSERT_EQ(kOk, client.ConnectIfNecessary().code());
   client.SetParserFuncForTesting(
-      base::Bind(&ReturnOutOfOrderResponses, &recurse_count, &client));
+      base::BindRepeating(&ReturnOutOfOrderResponses, &recurse_count, &client));
   base::DictionaryValue params;
   params.SetInteger("param", 1);
   std::unique_ptr<base::DictionaryValue> result;
@@ -865,9 +866,9 @@
 
 TEST_F(DevToolsClientImplTest, ProcessOnConnectedFirstOnCommand) {
   SyncWebSocketFactory factory =
-      base::Bind(&CreateMockSyncWebSocket<OnConnectedSyncWebSocket>);
+      base::BindRepeating(&CreateMockSyncWebSocket<OnConnectedSyncWebSocket>);
   DevToolsClientImpl client(factory, "http://url", "onconnected-id",
-                            base::Bind(&CloserFunc));
+                            base::BindRepeating(&CloserFunc));
   OnConnectedListener listener1("DOM.getDocument", &client);
   OnConnectedListener listener2("Runtime.enable", &client);
   OnConnectedListener listener3("Page.enable", &client);
@@ -881,9 +882,9 @@
 
 TEST_F(DevToolsClientImplTest, ProcessOnConnectedFirstOnHandleEventsUntil) {
   SyncWebSocketFactory factory =
-      base::Bind(&CreateMockSyncWebSocket<OnConnectedSyncWebSocket>);
+      base::BindRepeating(&CreateMockSyncWebSocket<OnConnectedSyncWebSocket>);
   DevToolsClientImpl client(factory, "http://url", "onconnected-id",
-                            base::Bind(&CloserFunc));
+                            base::BindRepeating(&CloserFunc));
   OnConnectedListener listener1("DOM.getDocument", &client);
   OnConnectedListener listener2("Runtime.enable", &client);
   OnConnectedListener listener3("Page.enable", &client);
@@ -973,9 +974,9 @@
 
 TEST_F(DevToolsClientImplTest, ProcessOnEventFirst) {
   SyncWebSocketFactory factory =
-      base::Bind(&CreateMockSyncWebSocket<MockSyncWebSocket5>);
+      base::BindRepeating(&CreateMockSyncWebSocket<MockSyncWebSocket5>);
   DevToolsClientImpl client(factory, "http://url", "id",
-                            base::Bind(&CloserFunc));
+                            base::BindRepeating(&CloserFunc));
   OtherEventListener listener2;
   OnEventListener listener1(&client, &listener2);
   client.AddListener(&listener1);
@@ -1028,12 +1029,11 @@
 
 TEST_F(DevToolsClientImplTest, Reconnect) {
   SyncWebSocketFactory factory =
-      base::Bind(&CreateMockSyncWebSocket<DisconnectedSyncWebSocket>);
+      base::BindRepeating(&CreateMockSyncWebSocket<DisconnectedSyncWebSocket>);
   bool is_called = false;
-  DevToolsClientImpl client(factory,
-                            "http://url",
-                            "id",
-                            base::Bind(&CheckCloserFuncCalled, &is_called));
+  DevToolsClientImpl client(
+      factory, "http://url", "id",
+      base::BindRepeating(&CheckCloserFuncCalled, &is_called));
   ASSERT_FALSE(is_called);
   ASSERT_EQ(kOk, client.ConnectIfNecessary().code());
   ASSERT_FALSE(is_called);
@@ -1115,9 +1115,10 @@
 
 TEST_F(DevToolsClientImplTest, BlockedByAlert) {
   std::list<std::string> msgs;
-  SyncWebSocketFactory factory = base::Bind(&CreateMockSyncWebSocket6, &msgs);
-  DevToolsClientImpl client(
-      factory, "http://url", "id", base::Bind(&CloserFunc));
+  SyncWebSocketFactory factory =
+      base::BindRepeating(&CreateMockSyncWebSocket6, &msgs);
+  DevToolsClientImpl client(factory, "http://url", "id",
+                            base::BindRepeating(&CloserFunc));
   msgs.push_back(
       "{\"method\": \"Page.javascriptDialogOpening\", \"params\": {}}");
   msgs.push_back("{\"id\": 2, \"result\": {}}");
@@ -1145,9 +1146,10 @@
   //                       response for 5
   //                       response for 6
   std::list<std::string> msgs;
-  SyncWebSocketFactory factory = base::Bind(&CreateMockSyncWebSocket6, &msgs);
-  DevToolsClientImpl client(
-      factory, "http://url", "id", base::Bind(&CloserFunc));
+  SyncWebSocketFactory factory =
+      base::BindRepeating(&CreateMockSyncWebSocket6, &msgs);
+  DevToolsClientImpl client(factory, "http://url", "id",
+                            base::BindRepeating(&CloserFunc));
   MockDevToolsEventListener listener;
   client.AddListener(&listener);
   msgs.push_back("{\"method\": \"FirstEvent\", \"params\": {}}");
@@ -1188,7 +1190,7 @@
     return Status(kOk);
   }
 
-  base::Callback<void(DevToolsClient*)> callback_;
+  base::RepeatingCallback<void(DevToolsClient*)> callback_;
   std::list<std::string> msgs_;
 };
 
@@ -1200,11 +1202,12 @@
 
 TEST_F(DevToolsClientImplTest, ReceivesCommandResponse) {
   std::list<std::string> msgs;
-  SyncWebSocketFactory factory = base::Bind(&CreateMockSyncWebSocket6, &msgs);
-  DevToolsClientImpl client(
-      factory, "http://url", "id", base::Bind(&CloserFunc));
+  SyncWebSocketFactory factory =
+      base::BindRepeating(&CreateMockSyncWebSocket6, &msgs);
+  DevToolsClientImpl client(factory, "http://url", "id",
+                            base::BindRepeating(&CloserFunc));
   MockCommandListener listener1;
-  listener1.callback_ = base::Bind(&HandleReceivedEvents);
+  listener1.callback_ = base::BindRepeating(&HandleReceivedEvents);
   MockCommandListener listener2;
   client.AddListener(&listener1);
   client.AddListener(&listener2);
@@ -1277,9 +1280,9 @@
 
 TEST_F(DevToolsClientImplTest, SendCommandAndIgnoreResponse) {
   SyncWebSocketFactory factory =
-      base::Bind(&CreateMockSyncWebSocket<MockSyncWebSocket7>);
+      base::BindRepeating(&CreateMockSyncWebSocket<MockSyncWebSocket7>);
   DevToolsClientImpl client(factory, "http://url", "id",
-                            base::Bind(&CloserFunc));
+                            base::BindRepeating(&CloserFunc));
   ASSERT_EQ(kOk, client.ConnectIfNecessary().code());
   base::DictionaryValue params;
   params.SetInteger("param", 1);
diff --git a/chrome/test/chromedriver/chrome/devtools_http_client.cc b/chrome/test/chromedriver/chrome/devtools_http_client.cc
index b5d20441..668fd6e 100644
--- a/chrome/test/chromedriver/chrome/devtools_http_client.cc
+++ b/chrome/test/chromedriver/chrome/devtools_http_client.cc
@@ -113,10 +113,10 @@
 
 std::unique_ptr<DevToolsClient> DevToolsHttpClient::CreateClient(
     const std::string& id) {
-  return std::unique_ptr<DevToolsClient>(
-      new DevToolsClientImpl(socket_factory_, endpoint_.GetDebuggerUrl(id), id,
-                             base::Bind(&DevToolsHttpClient::CloseFrontends,
-                                        base::Unretained(this), id)));
+  return std::unique_ptr<DevToolsClient>(new DevToolsClientImpl(
+      socket_factory_, endpoint_.GetDebuggerUrl(id), id,
+      base::BindRepeating(&DevToolsHttpClient::CloseFrontends,
+                          base::Unretained(this), id)));
 }
 
 Status DevToolsHttpClient::CloseWebView(const std::string& id) {
diff --git a/chrome/test/chromedriver/net/sync_websocket_factory.h b/chrome/test/chromedriver/net/sync_websocket_factory.h
index 041f72c..805d9eb 100644
--- a/chrome/test/chromedriver/net/sync_websocket_factory.h
+++ b/chrome/test/chromedriver/net/sync_websocket_factory.h
@@ -12,7 +12,8 @@
 class SyncWebSocket;
 class URLRequestContextGetter;
 
-typedef base::Callback<std::unique_ptr<SyncWebSocket>()> SyncWebSocketFactory;
+typedef base::RepeatingCallback<std::unique_ptr<SyncWebSocket>()>
+    SyncWebSocketFactory;
 
 SyncWebSocketFactory CreateSyncWebSocketFactory(
     URLRequestContextGetter* getter);
diff --git a/chrome/test/data/chromeos/enterprise/policies b/chrome/test/data/chromeos/enterprise/policies
index 06a729e..b60c95d 100644
--- a/chrome/test/data/chromeos/enterprise/policies
+++ b/chrome/test/data/chromeos/enterprise/policies
@@ -40,7 +40,7 @@
       u'DisableSpdy': True,
       u'DisabledPlugins': [u'*Viewer'],
       u'DnsPrefetchingEnabled': False,
-      u'ExtensionInstallBlacklist': [u'*'],
+      u'ExtensionInstallBlocklist': [u'*'],
       u'ExtensionInstallForcelist': [
         (u'kcnhkahnjcbndmmehfkdnkjomaanaooo;'
          'https://clients2.google.com/service/update2/crx'),
@@ -104,7 +104,7 @@
       u'Disable3DAPIs': True,
       u'DisabledPlugins': [u'Chrome PDF Viewer'],
       u'DnsPrefetchingEnabled': True,
-      u'ExtensionInstallBlacklist': [
+      u'ExtensionInstallBlocklist': [
         u'khgabmflimjjbclkmljlpmgaleanedem',
         u'boidnimkebefpfgbeekbjoponilnomle'],
       u'ExtensionInstallForcelist': [
@@ -184,7 +184,7 @@
       u'DefaultSearchProviderKeyword': u'mis',
       u'DnsPrefetchingEnabled': True,
       u'DisabledPlugins': [u'Shockwave Flash'],
-      u'ExtensionInstallBlacklist': [u'*'],
+      u'ExtensionInstallBlocklist': [u'*'],
       u'AlternateErrorPagesEnabled': True,
       u'HomepageLocation': u'http://finance.yahoo.com',
       u'SyncDisabled': False,
@@ -218,7 +218,7 @@
         u'O3D Plugin',
         u'Shockwave Flash'],
       u'DnsPrefetchingEnabled': True,
-      u'ExtensionInstallBlacklist': [u'*'],
+      u'ExtensionInstallBlocklist': [u'*'],
       u'ExtensionInstallForcelist': [
         (u'kcnhkahnjcbndmmehfkdnkjomaanaooo;'
          'https://clients2.google.com/service/update2/crx'),
@@ -291,7 +291,7 @@
       u'DisableSpdy': True,
       u'DisabledPlugins': [u'*Viewer'],
       u'DnsPrefetchingEnabled': False,
-      u'ExtensionInstallBlacklist': [u'*'],
+      u'ExtensionInstallBlocklist': [u'*'],
       u'ExtensionInstallForcelist': [
         (u'kcnhkahnjcbndmmehfkdnkjomaanaooo;'
          'https://clients2.google.com/service/update2/crx'),
@@ -355,7 +355,7 @@
       u'Disable3DAPIs': True,
       u'DisabledPlugins': [u'Chrome PDF Viewer'],
       u'DnsPrefetchingEnabled': True,
-      u'ExtensionInstallBlacklist': [
+      u'ExtensionInstallBlocklist': [
         u'khgabmflimjjbclkmljlpmgaleanedem',
         u'boidnimkebefpfgbeekbjoponilnomle'],
       u'ExtensionInstallForcelist': [
@@ -435,7 +435,7 @@
       u'DefaultSearchProviderKeyword': u'mis',
       u'DnsPrefetchingEnabled': True,
       u'DisabledPlugins': [u'Shockwave Flash'],
-      u'ExtensionInstallBlacklist': [u'*'],
+      u'ExtensionInstallBlocklist': [u'*'],
       u'AlternateErrorPagesEnabled': True,
       u'HomepageLocation': u'http://finance.yahoo.com',
       u'SyncDisabled': False,
@@ -469,7 +469,7 @@
         u'O3D Plugin',
         u'Shockwave Flash'],
       u'DnsPrefetchingEnabled': True,
-      u'ExtensionInstallBlacklist': [u'*'],
+      u'ExtensionInstallBlocklist': [u'*'],
       u'ExtensionInstallForcelist': [
         (u'kcnhkahnjcbndmmehfkdnkjomaanaooo;'
          'https://clients2.google.com/service/update2/crx'),
diff --git a/chrome/test/data/extensions/api_test/networking_cast_private/test.js b/chrome/test/data/extensions/api_test/networking_cast_private/test.js
index 3d31e8d..9b952da 100644
--- a/chrome/test/data/extensions/api_test/networking_cast_private/test.js
+++ b/chrome/test/data/extensions/api_test/networking_cast_private/test.js
@@ -19,9 +19,7 @@
   deviceBssid: '00:01:02:03:04:05'
 };
 
-chrome.test.getConfig(function(config) {
-  var args = JSON.parse(config.customArg);
-
+chrome.test.getConfig(function() {
   chrome.test.runTests([
     function verifyDestination() {
       chrome.networking.castPrivate.verifyDestination(
@@ -38,27 +36,5 @@
             assertEq('encrypted_data', result);
           }));
     },
-    function setWifiTDLSEnabledState() {
-      if (args.tdlsSupported) {
-        chrome.networking.castPrivate.setWifiTDLSEnabledState(
-            'aa:bb:cc:dd:ee:ff', true, callbackPass(function(result) {
-              assertEq('CONNECTED', result);
-            }));
-      } else {
-        chrome.networking.castPrivate.setWifiTDLSEnabledState(
-            'aa:bb:cc:dd:ee:ff', true, callbackFail('Not supported'));
-      }
-    },
-    function getWifiTDLSStatus() {
-      if (args.tdlsSupported) {
-        chrome.networking.castPrivate.getWifiTDLSStatus(
-            'aa:bb:cc:dd:ee:ff', callbackPass(function(result) {
-              assertEq('CONNECTED', result);
-            }));
-      } else {
-        chrome.networking.castPrivate.getWifiTDLSStatus(
-            'aa:bb:cc:dd:ee:ff', callbackFail('Not supported'));
-      }
-    },
   ]);
 });
diff --git a/chrome/test/data/extensions/api_test/networking_private/alias/test.js b/chrome/test/data/extensions/api_test/networking_private/alias/test.js
index ad850d1..4853418 100644
--- a/chrome/test/data/extensions/api_test/networking_private/alias/test.js
+++ b/chrome/test/data/extensions/api_test/networking_private/alias/test.js
@@ -99,9 +99,5 @@
       chrome.networking.onc.verifyAndEncryptData(
           stubVerificationProperties, '',
           chrome.test.callbackFail(expectedError));
-      chrome.networking.onc.setWifiTDLSEnabledState('', false,
-          chrome.test.callbackFail(expectedError));
-      chrome.networking.onc.getWifiTDLSStatus('',
-          chrome.test.callbackFail(expectedError));
     }
 ]);
diff --git a/chrome/test/data/extensions/api_test/networking_private/chromeos/test.js b/chrome/test/data/extensions/api_test/networking_private/chromeos/test.js
index 571b48f..dff9bd3 100644
--- a/chrome/test/data/extensions/api_test/networking_private/chromeos/test.js
+++ b/chrome/test/data/extensions/api_test/networking_private/chromeos/test.js
@@ -899,18 +899,6 @@
         assertEq('encrypted_data', result);
       }));
   },
-  function setWifiTDLSEnabledState() {
-    chrome.networkingPrivate.setWifiTDLSEnabledState(
-        'aa:bb:cc:dd:ee:ff', true, callbackPass(function(result) {
-          assertEq(ConnectionStateType.CONNECTED, result);
-        }));
-  },
-  function getWifiTDLSStatus() {
-    chrome.networkingPrivate.getWifiTDLSStatus(
-        'aa:bb:cc:dd:ee:ff', callbackPass(function(result) {
-          assertEq(ConnectionStateType.CONNECTED, result);
-        }));
-  },
   function getCaptivePortalStatus() {
     var networks = [['stub_ethernet_guid', 'Online'],
                     ['stub_wifi1_guid', 'Offline'],
diff --git a/chrome/test/data/extensions/api_test/networking_private/service_client/test.js b/chrome/test/data/extensions/api_test/networking_private/service_client/test.js
index 36f40185..cbd9f31 100644
--- a/chrome/test/data/extensions/api_test/networking_private/service_client/test.js
+++ b/chrome/test/data/extensions/api_test/networking_private/service_client/test.js
@@ -443,21 +443,6 @@
         assertEq("encrypted_data", result);
       }));
   },
-  function setWifiTDLSEnabledState() {
-    chrome.networkingPrivate.setWifiTDLSEnabledState(
-      "aa:bb:cc:dd:ee:ff",
-      true,
-      callbackPass(function(result) {
-        assertEq("Connected", result);
-      }));
-  },
-  function getWifiTDLSStatus() {
-    chrome.networkingPrivate.getWifiTDLSStatus(
-      "aa:bb:cc:dd:ee:ff",
-      callbackPass(function(result) {
-        assertEq("Connected", result);
-      }));
-  },
   function getCaptivePortalStatus() {
     var networks = [['stub_wifi1_guid', 'Offline'],
                     ['stub_wifi2_guid', 'Portal']];
diff --git a/chrome/test/data/extensions/api_test/networking_private/test.js b/chrome/test/data/extensions/api_test/networking_private/test.js
index cf5b327..9f86d4c2 100644
--- a/chrome/test/data/extensions/api_test/networking_private/test.js
+++ b/chrome/test/data/extensions/api_test/networking_private/test.js
@@ -103,16 +103,8 @@
     chrome.networkingPrivate.verifyAndEncryptData(
         verificationProperties, 'data', callbackPass(callbackResult));
   },
-  function setWifiTDLSEnabledState() {
-    chrome.networkingPrivate.setWifiTDLSEnabledState(
-        '', true, callbackPass(callbackResult));
-  },
-  function getWifiTDLSStatus() {
-    chrome.networkingPrivate.getWifiTDLSStatus(
-        '', callbackPass(callbackResult));
-  },
   function getCaptivePortalStatus() {
-    chrome.networkingPrivate.getWifiTDLSStatus(
+    chrome.networkingPrivate.getCaptivePortalStatus(
         kGuid, callbackPass(callbackResult));
   },
   function unlockCellularSim() {
diff --git a/chrome/test/data/extensions/api_test/tabs/on_removed/test.js b/chrome/test/data/extensions/api_test/tabs/on_removed/test.js
index a8dc753..ec6d782d 100644
--- a/chrome/test/data/extensions/api_test/tabs/on_removed/test.js
+++ b/chrome/test/data/extensions/api_test/tabs/on_removed/test.js
@@ -39,7 +39,7 @@
       chrome.tabs.getAllInWindow(firstWindowId, pass(function(tabs) {
         assertEq(pages.length, tabs.length);
         for (var i in tabs) {
-          assertEq(pages[i], tabs[i].url);
+          assertEq(pages[i], tabs[i].url || tabs[i].pendingUrl);
         }
       }));
     }));
@@ -60,7 +60,7 @@
     chrome.test.listenOnce(chrome.windows.onCreated, function(window) {
       windowEventsWindow = window;
       chrome.tabs.getAllInWindow(window.id, pass(function(tabs) {
-        assertEq(pageUrl("a"), tabs[0].url);
+        assertEq(pageUrl("a"), tabs[0].url || tabs[0].pendingUrl);
       }));
     });
 
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index c96267e..9c63a630 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -1402,6 +1402,16 @@
     ]
   },
 
+  "ExtensionInstallBlocklist": {
+    "os": ["win", "linux", "mac", "chromeos"],
+    "policy_pref_mapping_test": [
+      {
+        "policies": { "ExtensionInstallBlocklist": ["*"] },
+        "prefs": { "extensions.install.denylist": {} }
+      }
+    ]
+  },
+
   "ExtensionInstallWhitelist": {
     "os": ["win", "linux", "mac", "chromeos"],
     "policy_pref_mapping_test": [
@@ -7647,5 +7657,15 @@
         }
       }
     ]
+  },
+  "ShowFullUrlsInAddressBar": {
+    "os": ["win", "linux", "mac", "chromeos"],
+    "can_be_recommended": true,
+    "policy_pref_mapping_test": [
+      {
+        "policies": { "ShowFullUrlsInAddressBar": true },
+        "prefs": { "omnibox.prevent_url_elisions": {} }
+      }
+    ]
   }
 }
diff --git a/chrome/test/data/webui/BUILD.gn b/chrome/test/data/webui/BUILD.gn
index d2accf9..dcf6ef30 100644
--- a/chrome/test/data/webui/BUILD.gn
+++ b/chrome/test/data/webui/BUILD.gn
@@ -23,7 +23,6 @@
       "cr_focus_row_behavior_v3_interactive_test.js",
       "extensions/cr_extensions_interactive_ui_tests.js",
       "history/history_focus_test.js",
-      "new_tab_page/new_tab_page_interactive_test.js",
       "print_preview/print_preview_interactive_ui_tests.js",
       "settings/cr_settings_v3_interactive_ui_tests.js",
     ]
@@ -231,6 +230,7 @@
     if (is_chromeos) {
       data += [
         "$root_gen_dir/chrome/test/data/webui/cr_elements/cr_searchable_drop_down_tests.m.js",
+        "$root_gen_dir/chrome/test/data/webui/settings/chromeos/ambient_mode_page_test.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/bluetooth_page_tests.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/fake_bluetooth_private.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/fake_bluetooth.m.js",
@@ -242,9 +242,14 @@
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/multidevice_feature_toggle_tests.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/nearby_share_subpage_tests.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_reset_page_test.m.js",
+        "$root_gen_dir/chrome/test/data/webui/settings/chromeos/people_page_account_manager_test.m.js",
+        "$root_gen_dir/chrome/test/data/webui/settings/chromeos/people_page_change_picture_test.m.js",
+        "$root_gen_dir/chrome/test/data/webui/settings/chromeos/people_page_kerberos_accounts_test.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/test_os_reset_browser_proxy.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/test_os_lifetime_browser_proxy.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/test_multidevice_browser_proxy.m.js",
+        "$root_gen_dir/chrome/test/data/webui/settings/chromeos/personalization_page_test.m.js",
+        "$root_gen_dir/chrome/test/data/webui/settings/chromeos/test_wallpaper_browser_proxy.m.js",
       ]
     }
     defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
@@ -289,6 +294,30 @@
     defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
   }
 
+  js2gtest("interactive_ui_tests_js_mojo_lite_webui") {
+    test_type = "mojo_lite_webui"
+
+    sources = [ "new_tab_page/new_tab_page_interactive_test.js" ]
+
+    gen_include_files = [
+      "polymer_browser_test_base.js",
+      "polymer_interactive_ui_test.js",
+    ]
+
+    deps = [
+      ":modulize",
+      "//chrome/browser/ui",
+    ]
+
+    data = [
+      "$root_gen_dir/chrome/test/data/webui/test_browser_proxy.m.js",
+      "$root_gen_dir/chrome/test/data/webui/test_store.m.js",
+      "$root_gen_dir/chrome/test/data/webui/test_util.m.js",
+    ]
+
+    defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
+  }
+
   js2gtest("unit_tests_js") {
     test_type = "unit"
     sources = [
diff --git a/chrome/test/data/webui/new_tab_page/logo_test.js b/chrome/test/data/webui/new_tab_page/logo_test.js
index c302967f..c7a4faeb 100644
--- a/chrome/test/data/webui/new_tab_page/logo_test.js
+++ b/chrome/test/data/webui/new_tab_page/logo_test.js
@@ -354,6 +354,25 @@
     assertStyle($$(logo, '#logo'), 'background-image', 'none');
   });
 
+  test('logo aligned correctly', async () => {
+    // Act.
+    const logo = await createLogo();
+
+    // Assert.
+    const pos = getRelativePosition($$(logo, '#logo'), logo);
+    assertEquals(108, pos.top);
+    assertEquals(92, $$(logo, '#logo').offsetHeight);
+  });
+
+  test('doodle aligned correctly', async () => {
+    // Act.
+    const logo = await createLogo(createImageDoodle());
+
+    // Assert.
+    const pos = getRelativePosition($$(logo, '#doodle'), logo);
+    assertEquals(0, pos.top);
+  });
+
   // Disabled for flakiness, see https://crbug.com/1065812.
   test.skip('receiving resize message resizes doodle', async () => {
     // Arrange.
diff --git a/chrome/test/data/webui/settings/BUILD.gn b/chrome/test/data/webui/settings/BUILD.gn
index 6f630ec..451c390 100644
--- a/chrome/test/data/webui/settings/BUILD.gn
+++ b/chrome/test/data/webui/settings/BUILD.gn
@@ -193,7 +193,6 @@
     ":test_sync_browser_proxy.m",
     ":test_util",
 
-    #":test_wallpaper_browser_proxy",
     #":zoom_levels_tests",
   ]
 }
diff --git a/chrome/test/data/webui/settings/chromeos/BUILD.gn b/chrome/test/data/webui/settings/chromeos/BUILD.gn
index 84a0aff..858e538 100644
--- a/chrome/test/data/webui/settings/chromeos/BUILD.gn
+++ b/chrome/test/data/webui/settings/chromeos/BUILD.gn
@@ -31,6 +31,7 @@
 
 js_modulizer("modulize") {
   input_files = [
+    "ambient_mode_page_test.js",
     "bluetooth_page_tests.js",
     "fake_bluetooth.js",
     "fake_bluetooth_private.js",
@@ -44,9 +45,14 @@
     "multidevice_feature_toggle_tests.js",
     "nearby_share_subpage_tests.js",
     "os_reset_page_test.js",
+    "people_page_account_manager_test.js",
+    "people_page_change_picture_test.js",
+    "people_page_kerberos_accounts_test.js",
+    "personalization_page_test.js",
     "test_os_reset_browser_proxy.js",
     "test_os_lifetime_browser_proxy.js",
     "test_multidevice_browser_proxy.js",
+    "test_wallpaper_browser_proxy.js",
   ]
   namespace_rewrites =
       os_settings_namespace_rewrites + os_test_namespace_rewrites
diff --git a/chrome/test/data/webui/settings/chromeos/ambient_mode_page_test.js b/chrome/test/data/webui/settings/chromeos/ambient_mode_page_test.js
index d7918c6..0103c0b 100644
--- a/chrome/test/data/webui/settings/chromeos/ambient_mode_page_test.js
+++ b/chrome/test/data/webui/settings/chromeos/ambient_mode_page_test.js
@@ -2,6 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import 'chrome://os-settings/chromeos/os_settings.js';
+
+// #import {AmbientModeBrowserProxyImpl, CrSettingsPrefs} from 'chrome://os-settings/chromeos/os_settings.js';
+// #import {TestBrowserProxy} from '../../test_browser_proxy.m.js';
+// #import {assertEquals, assertFalse, assertTrue} from '../../chai_assert.js';
+// #import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+// clang-format on
+
 /**
  * @implements {settings.AmbientModeBrowserProxy}
  */
@@ -45,6 +54,11 @@
     return CrSettingsPrefs.initialized.then(function() {
       page = document.createElement('settings-ambient-mode-page');
       page.prefs = prefElement.prefs;
+
+      page.prefs.settings.ambient_mode = {
+        enabled: {value: true},
+      };
+
       document.body.appendChild(page);
       Polymer.dom.flush();
     });
diff --git a/chrome/test/data/webui/settings/chromeos/os_namespace_rewrites.gni b/chrome/test/data/webui/settings/chromeos/os_namespace_rewrites.gni
index 932e5c40..119ac95 100644
--- a/chrome/test/data/webui/settings/chromeos/os_namespace_rewrites.gni
+++ b/chrome/test/data/webui/settings/chromeos/os_namespace_rewrites.gni
@@ -1,14 +1,16 @@
 import("//chrome/browser/resources/settings/chromeos/os_settings.gni")
 
-os_test_namespace_rewrites = os_settings_namespace_rewrites + [
-  "multidevice.createFakePageContentData|createFakePageContentData",
-  "multidevice.TestMultideviceBrowserProxy|TestMultideviceBrowserProxy",
-  "multidevice.HOST_DEVICE|HOST_DEVICE",
-  "reset_page.TestOsResetBrowserProxy|TestOsResetBrowserProxy",
-  "settings.TestLifetimeBrowserProxy|TestLifetimeBrowserProxy",
-  "settings.FakeBluetooth|FakeBluetooth",
-  "settings.FakeBluetoothPrivate|FakeBluetoothPrivate",
-  "settings.MultiDeviceSettingsMode|MultiDeviceSettingsMode",
-  "test_util.flushTasks|flushTasks",
-  "test_util.eventToPromise|eventToPromise"
-]
+os_test_namespace_rewrites =
+    os_settings_namespace_rewrites + [
+      "multidevice.createFakePageContentData|createFakePageContentData",
+      "multidevice.TestMultideviceBrowserProxy|TestMultideviceBrowserProxy",
+      "multidevice.HOST_DEVICE|HOST_DEVICE",
+      "reset_page.TestOsResetBrowserProxy|TestOsResetBrowserProxy",
+      "settings.TestLifetimeBrowserProxy|TestLifetimeBrowserProxy",
+      "settings.FakeBluetooth|FakeBluetooth",
+      "settings.FakeBluetoothPrivate|FakeBluetoothPrivate",
+      "settings.MultiDeviceSettingsMode|MultiDeviceSettingsMode",
+      "test_util.flushTasks|flushTasks",
+      "test_util.eventToPromise|eventToPromise",
+      "settings.TestWallpaperBrowserProxy|TestWallpaperBrowserProxy",
+    ]
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
index 5ca3840..325cd64 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
@@ -1192,7 +1192,7 @@
     return super.extraLibraries.concat([
       '//ui/webui/resources/js/promise_resolver.js',
       BROWSER_SETTINGS_PATH + '../test_browser_proxy.js',
-      BROWSER_SETTINGS_PATH + 'test_wallpaper_browser_proxy.js',
+      BROWSER_SETTINGS_PATH + 'chromeos/test_wallpaper_browser_proxy.js',
       'personalization_page_test.js',
     ]);
   }
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js b/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js
index 328527c..15f05bb 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js
@@ -35,7 +35,8 @@
   }
 };
 
-[['ResetPage', 'os_reset_page_test.m.js'],
+[['AmbientModePpage', 'ambient_mode_page_test.m.js'],
+ ['ResetPage', 'os_reset_page_test.m.js'],
  ['LocalizedLink', 'localized_link_test.m.js'],
  ['BluetoothPage', 'bluetooth_page_tests.m.js'],
  ['NearbyShareSubPage', 'nearby_share_subpage_tests.m.js'],
@@ -44,6 +45,10 @@
  ['MultideviceSmartLockSubPage', 'multidevice_smartlock_subpage_test.m.js'],
  ['MultideviceFeatureItem', 'multidevice_feature_item_tests.m.js'],
  ['MultideviceFeatureToggle', 'multidevice_feature_toggle_tests.m.js'],
+ ['PersonalizationPage', 'personalization_page_test.m.js'],
+ ['PeoplePageAccountManager', 'people_page_account_manager_test.m.js'],
+ ['PeoplePageChangePicture', 'people_page_change_picture_test.m.js'],
+ ['PeoplePageKerberosAccounts', 'people_page_kerberos_accounts_test.m.js'],
 ].forEach(test => registerTest(...test));
 
 function registerTest(testName, module, caseName) {
diff --git a/chrome/test/data/webui/settings/chromeos/people_page_account_manager_test.js b/chrome/test/data/webui/settings/chromeos/people_page_account_manager_test.js
index f826f809..0732c11 100644
--- a/chrome/test/data/webui/settings/chromeos/people_page_account_manager_test.js
+++ b/chrome/test/data/webui/settings/chromeos/people_page_account_manager_test.js
@@ -2,6 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import 'chrome://os-settings/chromeos/os_settings.js';
+
+// #import {TestBrowserProxy} from '../../test_browser_proxy.m.js';
+// #import {Router, routes, AccountManagerBrowserProxyImpl} from 'chrome://os-settings/chromeos/os_settings.js';
+// #import {assertEquals, assertFalse, assertNotEquals, assertTrue} from '../../chai_assert.js';
+// #import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+// clang-format on
+
 cr.define('settings_people_page_account_manager', function() {
   /** @implements {settings.AccountManagerBrowserProxy} */
   class TestAccountManagerBrowserProxy extends TestBrowserProxy {
@@ -336,4 +345,7 @@
           accountManager.$$('#user-message-text').textContent.trim());
     });
   });
+
+  // #cr_define_end
+  return {};
 });
diff --git a/chrome/test/data/webui/settings/chromeos/people_page_change_picture_test.js b/chrome/test/data/webui/settings/chromeos/people_page_change_picture_test.js
index e810566..3424939b 100644
--- a/chrome/test/data/webui/settings/chromeos/people_page_change_picture_test.js
+++ b/chrome/test/data/webui/settings/chromeos/people_page_change_picture_test.js
@@ -2,6 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import 'chrome://os-settings/chromeos/os_settings.js';
+
+// #import {CrPicture} from 'chrome://resources/cr_elements/chromeos/cr_picture/cr_picture_types.m.js';
+// #import {down, up, pressAndReleaseKeyOn} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js'
+// #import {TestBrowserProxy} from '../../test_browser_proxy.m.js';
+// #import {Router, routes, AccountManagerBrowserProxyImpl, ChangePictureBrowserProxyImpl} from 'chrome://os-settings/chromeos/os_settings.js';
+// #import {assertEquals, assertFalse, assertNotEquals, assertTrue} from '../../chai_assert.js';
+// #import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+// #import {Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+// clang-format on
+
 cr.define('settings_people_page_change_picture', function() {
   /** @implements {settings.ChangePictureBrowserProxy} */
   class TestChangePictureBrowserProxy extends TestBrowserProxy {
@@ -410,4 +422,7 @@
       });
     });
   });
+
+  // #cr_define_end
+  return {};
 });
diff --git a/chrome/test/data/webui/settings/chromeos/people_page_kerberos_accounts_test.js b/chrome/test/data/webui/settings/chromeos/people_page_kerberos_accounts_test.js
index e4750269..048e59e 100644
--- a/chrome/test/data/webui/settings/chromeos/people_page_kerberos_accounts_test.js
+++ b/chrome/test/data/webui/settings/chromeos/people_page_kerberos_accounts_test.js
@@ -4,6 +4,16 @@
 
 'use strict';
 
+// clang-format off
+// #import 'chrome://os-settings/chromeos/os_settings.js';
+
+// #import {TestBrowserProxy} from '../../test_browser_proxy.m.js';
+// #import {Router, Route, routes, KerberosErrorType, KerberosConfigErrorCode, KerberosAccountsBrowserProxyImpl} from 'chrome://os-settings/chromeos/os_settings.js';
+// #import {assertEquals, assertFalse, assertNotEquals, assertTrue} from '../../chai_assert.js';
+// #import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+// #import {flushTasks} from 'chrome://test/test_util.m.js';
+// clang-format on
+
 cr.define('settings_people_page_kerberos_accounts', function() {
   // List of fake accounts.
   const testAccounts = [
@@ -114,14 +124,14 @@
     };
 
     setup(function() {
-      const routes = {
-        BASIC: new settings.Route('/'),
-      };
-      routes.PEOPLE = routes.BASIC.createSection('/people', 'people');
-      routes.KERBEROS_ACCOUNTS = routes.PEOPLE.createChild('/kerberosAccounts');
+      settings.routes.BASIC = new settings.Route('/'),
+      settings.routes.PEOPLE =
+          settings.routes.BASIC.createSection('/people', 'people');
+      settings.routes.KERBEROS_ACCOUNTS =
+          settings.routes.PEOPLE.createChild('/kerberosAccounts');
 
-      settings.Router.resetInstanceForTesting(new settings.Router(routes));
-      settings.routes = routes;
+      settings.Router.resetInstanceForTesting(
+          new settings.Router(settings.routes));
 
       browserProxy = new TestKerberosAccountsBrowserProxy();
       settings.KerberosAccountsBrowserProxyImpl.instance_ = browserProxy;
@@ -799,4 +809,7 @@
           settings.KerberosErrorType.kUnknown, generalError);
     });
   });
+
+  // #cr_define_end
+  return {};
 });
diff --git a/chrome/test/data/webui/settings/chromeos/personalization_page_test.js b/chrome/test/data/webui/settings/chromeos/personalization_page_test.js
index f4bcf07..9193e34 100644
--- a/chrome/test/data/webui/settings/chromeos/personalization_page_test.js
+++ b/chrome/test/data/webui/settings/chromeos/personalization_page_test.js
@@ -2,9 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import 'chrome://os-settings/chromeos/os_settings.js';
+
+// #import {WallpaperBrowserProxyImpl, routes, Router} from 'chrome://os-settings/chromeos/os_settings.js';
+// #import {assertEquals, assertFalse, assertTrue} from '../../chai_assert.js';
+// #import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+// #import {TestWallpaperBrowserProxy} from './test_wallpaper_browser_proxy.m.js';
+// clang-format on
+
 let personalizationPage = null;
 
-/** @type {?TestWallpaperBrowserProxy} */
+/** @type {?settings.TestWallpaperBrowserProxy} */
 let WallpaperBrowserProxy = null;
 
 function createPersonalizationPage() {
@@ -39,7 +48,7 @@
   });
 
   setup(function() {
-    WallpaperBrowserProxy = new TestWallpaperBrowserProxy();
+    WallpaperBrowserProxy = new settings.TestWallpaperBrowserProxy();
     settings.WallpaperBrowserProxyImpl.instance_ = WallpaperBrowserProxy;
     createPersonalizationPage();
   });
@@ -88,12 +97,16 @@
   });
 
   test('ambientMode', function() {
-    const row = personalizationPage.$$('#ambientModeRow');
-    assertTrue(!!row);
-    row.click();
-    assertEquals(
-        settings.routes.AMBIENT_MODE,
-        settings.Router.getInstance().getCurrentRoute());
-  });
+    const isGuest = loadTimeData.getBoolean('isGuest');
+    const isAmbientModeEnabled = loadTimeData.getBoolean('isAmbientModeEnabled');
 
+    if(!isGuest && isAmbientModeEnabled){
+      const row = personalizationPage.$$('#ambientModeRow');
+      assertTrue(!!row);
+      row.click();
+      assertEquals(
+          settings.routes.AMBIENT_MODE,
+          settings.Router.getInstance().getCurrentRoute());
+    }
+  });
 });
diff --git a/chrome/test/data/webui/settings/chromeos/test_wallpaper_browser_proxy.js b/chrome/test/data/webui/settings/chromeos/test_wallpaper_browser_proxy.js
new file mode 100644
index 0000000..fd93225
--- /dev/null
+++ b/chrome/test/data/webui/settings/chromeos/test_wallpaper_browser_proxy.js
@@ -0,0 +1,51 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// #import {TestBrowserProxy} from '../../test_browser_proxy.m.js';
+
+cr.define('settings', function() {
+  /** @implements {settings.WallpaperBrowserProxy} */
+  /* #export */ class TestWallpaperBrowserProxy extends TestBrowserProxy {
+    constructor() {
+      super([
+        'isWallpaperSettingVisible',
+        'isWallpaperPolicyControlled',
+        'openWallpaperManager',
+      ]);
+
+      /** @private */
+      this.isWallpaperSettingVisible_ = true;
+
+      /** @private */
+      this.isWallpaperPolicyControlled_ = false;
+    }
+
+    /** @override */
+    isWallpaperSettingVisible() {
+      this.methodCalled('isWallpaperSettingVisible');
+      return Promise.resolve(true);
+    }
+
+    /** @override */
+    isWallpaperPolicyControlled() {
+      this.methodCalled('isWallpaperPolicyControlled');
+      return Promise.resolve(this.isWallpaperPolicyControlled_);
+    }
+
+    /** @override */
+    openWallpaperManager() {
+      this.methodCalled('openWallpaperManager');
+    }
+
+    /** @param {boolean} Whether the wallpaper is policy controlled. */
+    setIsWallpaperPolicyControlled(isPolicyControlled) {
+      this.isWallpaperPolicyControlled_ = isPolicyControlled;
+    }
+  }
+
+  // #cr_define_end
+  return {
+    TestWallpaperBrowserProxy: TestWallpaperBrowserProxy,
+  };
+});
\ No newline at end of file
diff --git a/chrome/test/data/webui/settings/test_wallpaper_browser_proxy.js b/chrome/test/data/webui/settings/test_wallpaper_browser_proxy.js
deleted file mode 100644
index 204f33e1..0000000
--- a/chrome/test/data/webui/settings/test_wallpaper_browser_proxy.js
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/** @implements {settings.WallpaperBrowserProxy} */
-class TestWallpaperBrowserProxy extends TestBrowserProxy {
-  constructor() {
-    super([
-      'isWallpaperSettingVisible',
-      'isWallpaperPolicyControlled',
-      'openWallpaperManager',
-    ]);
-
-    /** @private */
-    this.isWallpaperSettingVisible_ = true;
-
-    /** @private */
-    this.isWallpaperPolicyControlled_ = false;
-  }
-
-  /** @override */
-  isWallpaperSettingVisible() {
-    this.methodCalled('isWallpaperSettingVisible');
-    return Promise.resolve(true);
-  }
-
-  /** @override */
-  isWallpaperPolicyControlled() {
-    this.methodCalled('isWallpaperPolicyControlled');
-    return Promise.resolve(this.isWallpaperPolicyControlled_);
-  }
-
-  /** @override */
-  openWallpaperManager() {
-    this.methodCalled('openWallpaperManager');
-  }
-
-  /** @param {boolean} Whether the wallpaper is policy controlled. */
-  setIsWallpaperPolicyControlled(isPolicyControlled) {
-    this.isWallpaperPolicyControlled_ = isPolicyControlled;
-  }
-}
diff --git a/chrome/updater/app/server/mac/server.mm b/chrome/updater/app/server/mac/server.mm
index c2d08df..2f8e198 100644
--- a/chrome/updater/app/server/mac/server.mm
+++ b/chrome/updater/app/server/mac/server.mm
@@ -19,6 +19,7 @@
 #import "chrome/updater/app/server/mac/app_server.h"
 #include "chrome/updater/app/server/mac/service_delegate.h"
 #include "chrome/updater/configurator.h"
+#include "chrome/updater/mac/setup/setup.h"
 #import "chrome/updater/mac/xpc_service_names.h"
 #include "chrome/updater/prefs.h"
 #include "chrome/updater/update_service_in_process.h"
@@ -71,12 +72,11 @@
 }
 
 void AppServerMac::UninstallSelf() {
-  // TODO(crbug.com/1098934): Uninstall this candidate version of the updater.
+  UninstallCandidate();
 }
 
 bool AppServerMac::SwapRPCInterfaces() {
-  // TODO(crbug.com/1098935): Update unversioned XPC interface.
-  return true;
+  return PromoteCandidate() == setup_exit_codes::kSuccess;
 }
 
 void AppServerMac::TaskStarted() {
diff --git a/chrome/updater/app/server/mac/service_delegate.mm b/chrome/updater/app/server/mac/service_delegate.mm
index 1f237e0..72e1ea86 100644
--- a/chrome/updater/app/server/mac/service_delegate.mm
+++ b/chrome/updater/app/server/mac/service_delegate.mm
@@ -26,14 +26,6 @@
 #include "chrome/updater/update_service.h"
 #include "chrome/updater/updater_version.h"
 
-const NSInteger kMaxRedialAttempts = 3;
-
-const base::Version& GetSelfVersion() {
-  static base::NoDestructor<base::Version> self_version(
-      base::Version(UPDATER_VERSION_STRING));
-  return *self_version;
-}
-
 @interface CRUUpdateCheckXPCServiceImpl
     : NSObject <CRUUpdateChecking, CRUAdministering>
 
@@ -235,151 +227,9 @@
                                 request, std::move(cb)));
 }
 
-- (void)getUpdaterVersionWithReply:
-    (void (^_Nonnull)(NSString* _Nullable version))reply {
-  if (reply)
-    reply(@UPDATER_VERSION_STRING);
-}
-
-- (BOOL)isUpdaterVersionLowerThanCandidateVersion:(NSString* _Nonnull)version {
-  const base::Version selfVersion = GetSelfVersion();
-  CHECK(selfVersion.IsValid());
-  const base::Version candidateVersion =
-      base::Version(base::SysNSStringToUTF8(version));
-  if (candidateVersion.IsValid()) {
-    return candidateVersion.CompareTo(selfVersion) > 0;
-  }
-  return NO;
-}
-
-- (void)haltForUpdateToVersion:(NSString* _Nonnull)candidateVersion
-                         reply:(void (^_Nonnull)(BOOL shouldUpdate))reply {
-  if (reply) {
-    if ([self isUpdaterVersionLowerThanCandidateVersion:candidateVersion]) {
-      // Halt the service for a long time, so that the update to the candidate
-      // version can be performed. Reply YES.
-      // TODO: crbug 1072061
-      // Halting not implemented yet. Change to reply(YES) when we can halt.
-      reply(NO);
-    } else {
-      reply(NO);
-    }
-  }
-}
-
 #pragma mark CRUAdministering
 
-- (void)promoteCandidate {
-  // TODO: crbug 1072061
-  // Actually do the self test. For now assume it passes.
-  BOOL selfTestPassed = YES;
-
-  if (!selfTestPassed) {
-    LOG(ERROR) << "Candidate versioned: '"
-               << UPDATER_VERSION_STRING "' failed self test.";
-    return;
-  }
-
-  auto haltErrorHandler = ^(NSError* haltError) {
-    LOG(ERROR) << "XPC connection failed: "
-               << base::SysNSStringToUTF8([haltError description]);
-    // Try to redial the connection
-    if (++_redialAttempts <= kMaxRedialAttempts) {
-      [self dialUpdateCheckXPCConnection];
-      [self promoteCandidate];
-      return;
-    } else {
-      LOG(ERROR) << "XPC connection redialed maximum number of times.";
-    }
-  };
-
-  auto haltReply = ^(BOOL halted) {
-    VLOG(0) << "Response from  haltForUpdateToVersion:reply: = " << halted;
-    if (halted) {
-      updater::PromoteCandidate();
-    } else {
-      LOG(ERROR) << "The active service refused to halt for update to version: "
-                 << UPDATER_VERSION_STRING;
-    }
-  };
-
-  [[_updateCheckXPCConnection.get()
-      remoteObjectProxyWithErrorHandler:haltErrorHandler]
-      haltForUpdateToVersion:@UPDATER_VERSION_STRING
-                       reply:haltReply];
-}
-
 - (void)performAdminTasks {
-  base::scoped_nsobject<NSError> versionError;
-
-  if (!_updateCheckXPCConnection) {
-    [self promoteCandidate];
-    return;
-  }
-
-  auto versionErrorHandler = ^(NSError* versionError) {
-    LOG(ERROR) << "XPC connection failed: "
-               << base::SysNSStringToUTF8([versionError description]);
-  };
-
-  auto versionReply = ^(NSString* _Nullable activeServiceVersionString) {
-    if (activeServiceVersionString) {
-      const base::Version activeServiceVersion =
-          base::Version(base::SysNSStringToUTF8(activeServiceVersionString));
-      const base::Version selfVersion = GetSelfVersion();
-
-      if (!selfVersion.IsValid()) {
-        updater::UninstallCandidate();
-      } else if (!activeServiceVersion.IsValid()) {
-        [self promoteCandidate];
-      } else {
-        int versionComparisonResult =
-            selfVersion.CompareTo(activeServiceVersion);
-
-        if (versionComparisonResult > 0) {
-          // If the versioned service is a higher version than the active
-          // service, run a self test and activate the versioned service.
-          [self promoteCandidate];
-        } else if (versionComparisonResult < 0) {
-          // If the versioned service is a lower version than the active
-          // service, remove the versioned service.
-          updater::UninstallCandidate();
-        } else {
-          // If the versioned service is the same version as the active
-          // service, check for updates.
-          base::scoped_nsobject<NSError> updateCheckError;
-
-          base::scoped_nsprotocol<id<CRUUpdateStateObserving>> stateObserver(
-              [[CRUUpdateStateObserver alloc]
-                  initWithRepeatingCallback:
-                      base::BindRepeating(
-                          [](updater::UpdateService::UpdateState) {})
-                             callbackRunner:_callbackRunner]);
-
-          auto updateCheckErrorHandler = ^(NSError* updateCheckError) {
-            LOG(ERROR) << "XPC connection failed: "
-                       << base::SysNSStringToUTF8(
-                              [updateCheckError description]);
-          };
-          auto updateCheckReply = ^(int error) {
-            VLOG(0) << "UpdateAll complete: exit_code = " << error;
-          };
-
-          [[_updateCheckXPCConnection.get()
-              remoteObjectProxyWithErrorHandler:updateCheckErrorHandler]
-              checkForUpdatesWithUpdateState:stateObserver
-                                       reply:updateCheckReply];
-        }
-      }
-    } else {
-      // Active service version is nil.
-      LOG(ERROR) << "Active service version is nil.";
-    }
-  };
-
-  [[_updateCheckXPCConnection.get()
-      remoteObjectProxyWithErrorHandler:versionErrorHandler]
-      getUpdaterVersionWithReply:versionReply];
 }
 
 @end
diff --git a/chrome/updater/app/server/mac/service_protocol.h b/chrome/updater/app/server/mac/service_protocol.h
index 77eb09f..3f0c4520 100644
--- a/chrome/updater/app/server/mac/service_protocol.h
+++ b/chrome/updater/app/server/mac/service_protocol.h
@@ -25,10 +25,6 @@
 // Protocol for the XPC update checking service.
 @protocol CRUUpdateChecking <NSObject>
 
-// Checks for the version of the Updater. Returns the result in the reply block.
-- (void)getUpdaterVersionWithReply:
-    (void (^_Nonnull)(NSString* _Nonnull version))reply;
-
 // Checks for updates and returns the result in the reply block.
 - (void)checkForUpdatesWithUpdateState:
             (CRUUpdateStateObserver* _Nonnull)updateState
@@ -49,10 +45,6 @@
                existenceCheckerPath:(NSString* _Nullable)existenceCheckerPath
                               reply:(void (^_Nonnull)(int rc))reply;
 
-// Checks if |version| is newer. Returns the result in the reply block.
-- (void)haltForUpdateToVersion:(NSString* _Nonnull)version
-                         reply:(void (^_Nonnull)(BOOL shouldUpdate))reply;
-
 @end
 
 // Protocol for the XPC administration tasks of the Updater.
diff --git a/chrome/updater/branding.gni b/chrome/updater/branding.gni
index 01379de..fff9a02 100644
--- a/chrome/updater/branding.gni
+++ b/chrome/updater/branding.gni
@@ -9,6 +9,7 @@
   updater_company_short_name = "Google"
   updater_product_full_name = "GoogleUpdater"
   browser_name = "Chrome"
+  browser_product_name = "Google Chrome"
   updater_copyright =
       "Copyright 2020 The Chromium Authors. All rights reserved."
   mac_updater_bundle_identifier = "com.google.GoogleUpdater"
@@ -18,6 +19,7 @@
   updater_company_short_name = "Chromium"
   updater_product_full_name = "ChromiumUpdater"
   browser_name = "Chromium"
+  browser_product_name = "Chromium"
   updater_copyright =
       "Copyright 2020 The Chromium Authors. All rights reserved."
   mac_updater_bundle_identifier = "org.chromium.ChromiumUpdater"
diff --git a/chrome/updater/mac/.install.sh b/chrome/updater/mac/.install.sh
new file mode 100755
index 0000000..f2e8545
--- /dev/null
+++ b/chrome/updater/mac/.install.sh
@@ -0,0 +1,693 @@
+#!/bin/bash -p
+# Copyright 2020 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.
+
+# usage: install.sh update_dmg_mount_point installed_app_path current_version
+#
+# Called by Omaha v4 to update the installed application with a new version from
+# the dmg.
+#
+# Exit codes:
+#   0   Success!
+#   1   Unknown Failure
+#   2   Could not locate installed app path.
+#   3   DMG mount point is not an absolute path.
+#   4   Path to new .app must be an absolute path.
+#   5   Update app is not using the new version folder layout.
+#   6   Installed app path must be an absolute path to a directory.
+#   7   Installed app's versioned directory is in old format.
+#   8   Installed app's versioned directory is in old format.
+#   9   Installed app's versioned directory is in old format.
+#   10  Making versioned directory for new version failed.
+#   11  Could not remove existing file where versioned directory should be.
+#   12  rsync of versioned directory failed.
+#   13  rsync of app directory failed.
+#   14  We could not determine the new app version.
+#   15  The new app version does not match the update version.
+#   16  This will return a usage message.
+
+set -eu
+
+# Set path to /bin, /usr/bin, /sbin, /usr/sbin
+export PATH="/bin:/usr/bin:/sbin:/usr/sbin"
+
+# Environment sanitization.  Clear environment variables that might impact the
+# interpreter's operation.  The |bash -p| invocation on the #! line takes the
+# bite out of BASH_ENV, ENV, and SHELLOPTS (among other features), but
+# clearing them here ensures that they won't impact any shell scripts used as
+# utility programs. SHELLOPTS is read-only and can't be unset, only
+# unexported.
+unset BASH_ENV CDPATH ENV GLOBIGNORE IFS POSIXLY_CORRECT
+export -n SHELLOPTS
+
+set -o pipefail
+shopt -s nullglob
+
+ME="$(basename "${0}")"
+readonly ME
+
+# We will populate this variable with the version of the application bundle
+# it'll be packaged with when the build is run. This allows us to not do a
+# defaults read on Info.plist to find the update_version.
+UPDATE_VERSION=
+readonly UPDATE_VERSION
+
+err() {
+  local error="${1}"
+  local id=": ${$} $(date "+%Y-%m-%d %H:%M:%S %z")"
+  echo "${ME}${id}: ${error}" >& 2
+}
+
+note() {
+  local message="${1}"
+  echo "${ME}: ${$} $(date "+%Y-%m-%d %H:%M:%S %z"): ${message}" >& 1
+}
+
+g_temp_dir=
+cleanup() {
+  local status=${?}
+
+  trap - EXIT
+  trap '' HUP INT QUIT TERM
+
+  if [[ ${status} -ge 128 ]]; then
+    err "Caught signal $((${status} - 128))"
+  fi
+
+  if [[ -n "${g_temp_dir}" ]]; then
+    rm -rf "${g_temp_dir}"
+  fi
+
+  exit ${status}
+}
+
+# Returns 0 (true) if |symlink| exists, is a symbolic link, and appears
+# writable on the basis of its POSIX permissions.  This is used to determine
+# writability like test's -w primary, but -w resolves symbolic links and this
+# function does not.
+is_writable_symlink() {
+  local symlink="${1}"
+
+  local link_mode="$(stat -f %Sp "${symlink}" 2> /dev/null || true)"
+  if [[ -z "${link_mode}" ]] || [[ "${link_mode:0:1}" != "l" ]]; then
+    return 1
+  fi
+
+  local link_user="$(stat -f %u "${symlink}" 2> /dev/null || true)"
+  local link_group="$(stat -f %g "${symlink}" 2> /dev/null || true)"
+  if [[ -z "${link_user}" ]] || [[ -z "${link_group}" ]]; then
+    return 1
+  fi
+
+  # If the users match, check the owner-write bit.
+  if [[ ${EUID} -eq "${link_user}" ]]; then
+    if [[ "${link_mode:2:1}" = "w" ]]; then
+      return 0
+    fi
+    return 1
+  fi
+
+  # If the file's group matches any of the groups that this process is a
+  # member of, check the group-write bit.
+  local group_match=
+  local group
+  for group in "${GROUPS[@]}"; do
+    if [[ "${group}" -eq "${link_group}" ]]; then
+      group_match="y"
+      break
+    fi
+  done
+  if [[ -n "${group_match}" ]]; then
+    if [[ "${link_mode:5:1}" = "w" ]]; then
+      return 0
+    fi
+    return 1
+  fi
+
+  # Check the other-write bit.
+  if [[ "${link_mode:8:1}" = "w" ]]; then
+    return 0
+  fi
+
+  return 1
+}
+
+# If |symlink| exists and is a symbolic link, but is not writable according to
+# is_writable_symlink, this function attempts to replace it with a new
+# writable symbolic link.  If |symlink| does not exist, is not a symbolic
+# link, or is already writable, this function does nothing.  This function
+# always returns 0 (true).
+ensure_writable_symlink() {
+  local symlink="${1}"
+
+  if [[ -L "${symlink}" ]] && ! is_writable_symlink "${symlink}"; then
+    # If ${symlink} refers to a directory, doing this naively might result in
+    # the new link being placed in that directory, instead of replacing the
+    # existing link.  ln -fhs is supposed to handle this case, but it does so
+    # by unlinking (removing) the existing symbolic link before creating a new
+    # one.  That leaves a small window during which the symbolic link is not
+    # present on disk at all.
+    #
+    # To avoid that possibility, a new symbolic link is created in a temporary
+    # location and then swapped into place with mv.  An extra temporary
+    # directory is used to convince mv to replace the symbolic link: again, if
+    # the existing link refers to a directory, "mv newlink oldlink" will
+    # actually leave oldlink alone and place newlink into the directory.
+    # "mv newlink dirname(oldlink)" works as expected, but in order to replace
+    # oldlink, newlink must have the same basename, hence the temporary
+    # directory.
+
+    local target="$(readlink "${symlink}" 2> /dev/null || true)"
+    if [[ -z "${target}" ]]; then
+      return 0
+    fi
+
+    # Error handling strategy: if anything fails, such as the mktemp, ln,
+    # chmod, or mv, ignore the failure and return 0 (success), leaving the
+    # existing state with the non-writable symbolic link intact.  Failures
+    # in this function will be difficult to understand and diagnose, and a
+    # non-writable symbolic link is not necessarily fatal.  If something else
+    # requires a writable symbolic link, allowing it to fail when a symbolic
+    # link is not writable is easier to understand than bailing out of the
+    # script on failure here.
+
+    local symlink_dir="$(dirname "${symlink}")"
+    local temp_link_dir=\
+      "$(mktemp -d "${symlink_dir}/.symlink_temp.XXXXXX" || true)"
+    if [[ -z "${temp_link_dir}" ]]; then
+      return 0
+    fi
+    local temp_link="${temp_link_dir}/$(basename "${symlink}")"
+
+    (ln -fhs "${target}" "${temp_link}" &&
+        chmod -h 755 "${temp_link}" &&
+        mv -f "${temp_link}" "${symlink_dir}/") || true
+    rm -rf "${temp_link_dir}"
+  fi
+
+  return 0
+}
+
+# ensure_writable_symlinks_recursive calls ensure_writable_symlink for every
+# symbolic link in |directory|, recursively.
+#
+# In some very weird and rare cases, it is possible to wind up with a user
+# installation that contains symbolic links that the user does not have write
+# permission over.  More on how that might happen later.
+#
+# If a weird and rare case like this is observed, rsync will exit with an
+# error when attempting to update the times on these symbolic links.  rsync
+# may not be intelligent enough to try creating a new symbolic link in these
+# cases, but this script can be.
+#
+# The problem occurs when an administrative user first drag-installs the
+# application to /Applications, resulting in the program's user being set to
+# the user's own ID.  If, subsequently, a .pkg package is installed over that,
+# the existing directory ownership will be preserved, but file ownership will
+# be changed to whatever is specified by the package, typically root.  This
+# applies to symbolic links as well.  On a subsequent update, rsync will be
+# able to copy the new files into place, because the user still has permission
+# to write to the directories.  If the symbolic link targets are not changing,
+# though, rsync will not replace them, and they will remain owned by root.
+# The user will not have permission to update the time on the symbolic links,
+# resulting in an rsync error.
+ensure_writable_symlinks_recursive() {
+  local directory="${1}"
+
+  # This fix-up is not necessary when running as root, because root will
+  # always be able to write everything needed.
+  if [[ ${EUID} -eq 0 ]]; then
+    return 0
+  fi
+
+  # This step isn't critical.
+  local set_e=
+  if [[ "${-}" =~ e ]]; then
+    set_e="y"
+    set +e
+  fi
+
+  # Use find -print0 with read -d $'\0' to handle even the weirdest paths.
+  local symlink
+  while IFS= read -r -d $'\0' symlink; do
+    ensure_writable_symlink "${symlink}"
+  done < <(find "${directory}" -type l -print0)
+
+  # Go back to how things were.
+  if [[ -n "${set_e}" ]]; then
+    set -e
+  fi
+}
+
+# Runs "defaults read" to obtain the value of a key in a property list. As
+# with "defaults read", an absolute path to a plist is supplied, without the
+# ".plist" extension.
+#
+# As of Mac OS X 10.8, defaults (and NSUserDefaults and CFPreferences)
+# normally communicates with cfprefsd to read and write plists. Changes to a
+# plist file aren't necessarily reflected immediately via this API family when
+# not made through this API family, because cfprefsd may return cached data
+# from a former on-disk version of a plist file instead of reading the current
+# version from disk. The old behavior can be restored by setting the
+# __CFPREFERENCES_AVOID_DAEMON environment variable, although extreme care
+# should be used because portions of the system that use this API family
+# normally and thus use cfprefsd and its cache will become unsynchronized with
+# the on-disk state.
+#
+# This function is provided to set __CFPREFERENCES_AVOID_DAEMON when calling
+# "defaults read" and thus avoid cfprefsd and its on-disk cache, and is
+# intended only to be used to read values from Info.plist files, which are not
+# preferences. The use of "defaults" for this purpose has always been
+# questionable, but there's no better option to interact with plists from
+# shell scripts. Definitely don't use infoplist_read to read preference
+# plists.
+#
+# This function exists because the update process delivers new copies of
+# Info.plist files to the disk behind cfprefsd's back, and if cfprefsd becomes
+# aware of the original version of the file for any reason (such as this
+# script reading values from it via "defaults read"), the new version of the
+# file will not be immediately effective or visible via cfprefsd after the
+# update is applied.
+infoplist_read() {
+  __CFPREFERENCES_AVOID_DAEMON=1 defaults read "${@}"
+}
+
+usage() {
+  echo "usage: ${ME} update_dmg_mount_point installed_app_path current_version"\
+  >& 2
+}
+
+main() {
+  local update_dmg_mount_point="${1}"
+  local installed_app_path="${2}"
+  local old_version_app="${3}"
+
+  # Early steps are critical.  Don't continue past any failure.
+  set -e
+
+  trap cleanup EXIT HUP INT QUIT TERM
+
+  # Figure out where to install.
+  if [[ ! -d "${installed_app_path}" ]]; then
+    err "couldn't locate installed_app_path"
+    exit 2
+  fi
+  note "installed_app_path = ${installed_app_path}"
+  note "old_version_app = ${old_version_app}"
+
+  # The app directory, product name, etc. can all be gotten from the
+  # installed_app_path.
+  readonly APP_DIR="$(basename "${installed_app_path}")"
+  readonly PRODUCT_NAME="${APP_DIR%.*}"
+  readonly FRAMEWORK_NAME="${PRODUCT_NAME} Framework"
+  readonly FRAMEWORK_DIR="${FRAMEWORK_NAME}.framework"
+  readonly CONTENTS_DIR="Contents"
+  readonly APP_PLIST="${CONTENTS_DIR}/Info"
+  readonly VERSIONS_DIR_NEW=\
+"${CONTENTS_DIR}/Frameworks/${FRAMEWORK_DIR}/Versions"
+  readonly APP_VERSION_KEY="CFBundleShortVersionString"
+
+  readonly QUARANTINE_ATTR="com.apple.quarantine"
+
+  # Don't use rsync --archive, because --archive includes --group and --owner,
+  # which copy groups and owners, respectively, from the source, and that is
+  # undesirable in this case (often, this script will have permission to set
+  # those attributes).  --archive also includes --devices and --specials, which
+  # copy files that should never occur in the transfer; --devices only works
+  # when running as root, so for consistency between privileged and unprivileged
+  # operation, this option is omitted as well.  --archive does not include
+  # --ignore-times, which is desirable, as it forces rsync to copy files even
+  # when their sizes and modification times are identical, as their content
+  # still may be different.
+  readonly RSYNC_FLAGS="--ignore-times --links --perms --recursive --times"
+
+  note "update_dmg_mount_point = ${update_dmg_mount_point}"
+
+  if [[ -z "${update_dmg_mount_point}" ]] ||
+     [[ "${update_dmg_mount_point:0:1}" != "/" ]] ||
+     ! [[ -d "${update_dmg_mount_point}" ]]; then
+    err "update_dmg_mount_point must be an absolute path to a directory"
+    usage
+    exit 3
+  fi
+
+  # The update to install.
+
+  # update_app is the path to the new version of the .app.
+  local update_app="${update_dmg_mount_point}/${APP_DIR}"
+  note "update_app = ${update_app}"
+
+  # Make sure that it's an absolute path.
+  if [[ "${update_app:0:1}" != "/" ]]; then
+    err "update_app must be an absolute path"
+    exit 4
+  fi
+
+  if [[ ! -d "${update_app}/${VERSIONS_DIR_NEW}" ]]; then
+    err "update app not using new layout"
+    exit 5
+  fi
+
+  if [[ "${installed_app_path:0:1}" != "/" ]] ||
+     ! [[ -d "${installed_app_path}" ]]; then
+    err "installed_app_path must be an absolute path to a directory"
+    exit 6
+  fi
+
+  # Figure out what the existing installed application is using for its
+  # versioned directory.  This will be used later, to avoid removing the
+  # existing installed version's versioned directory in case anything is still
+  # using it.
+  note "reading install values"
+
+  local installed_app_path_plist="${installed_app_path}/${APP_PLIST}"
+  note "installed_app_path_plist = ${installed_app_path_plist}"
+
+  local installed_versions_dir_new="${installed_app_path}/${VERSIONS_DIR_NEW}"
+  note "installed_versions_dir_new = ${installed_versions_dir_new}"
+
+  local installed_versions_dir="${installed_versions_dir_new}"
+  if [[ ! -d "${installed_versions_dir}" ]]; then
+    err "installed app does not have versioned dir. Might be an old version."
+    exit 7
+  fi
+  note "installed_versions_dir = ${installed_versions_dir}"
+
+  # If the installed application is incredibly old, or in a skeleton bootstrap
+  # installation, old_versioned_dir may not exist.
+  local old_versioned_dir
+  if [[ -n "${old_version_app}" ]]; then
+    if [[ -d "${installed_versions_dir_new}/${old_version_app}" ]]; then
+      old_versioned_dir="${installed_versions_dir_new}/${old_version_app}"
+    else
+      err "installed app does not have versioned dir. Might be an old version."
+      exit 8
+    fi
+  fi
+  note "old_versioned_dir = ${old_versioned_dir}"
+
+  local update_versioned_dir=\
+"${update_app}/${VERSIONS_DIR_NEW}/${UPDATE_VERSION}"
+  if [[ ! -d "${update_versioned_dir}" ]]; then
+    err "Update versioned dir does not have the new layout."
+    exit 9
+  fi
+
+  ensure_writable_symlinks_recursive "${installed_app_path}"
+
+  # By copying to ${installed_app_path}, the existing application name will be
+  # preserved, if the user has renamed the application on disk.  Respecting
+  # the user's changes is friendly.
+
+  # Make sure that ${installed_versions_dir} exists, so that it can receive
+  # the versioned directory.  It may not exist if updating from an older
+  # version that did not use the same versioned layout on disk.  Later, during
+  # the rsync to copy the application directory, the mode bits and timestamp on
+  # ${installed_versions_dir} will be set to conform to whatever is present in
+  # the update.
+  #
+  # ${installed_app_path} is guaranteed to exist at this point, but
+  # ${installed_app_path}/${CONTENTS_DIR} may not if things are severely broken
+  # or if this update is actually an initial installation from an updater
+  # skeleton bootstrap.  The mkdir creates ${installed_app_path}/${CONTENTS_DIR}
+  # if it doesn't exist; its mode bits will be fixed up in a subsequent rsync.
+  note "creating installed_versions_dir"
+  if ! mkdir -p "${installed_versions_dir}"; then
+    err "mkdir of installed_versions_dir failed"
+    exit 10
+  fi
+
+  local new_versioned_dir="${installed_versions_dir}/${UPDATE_VERSION}"
+  note "new_versioned_dir = ${new_versioned_dir}"
+
+  # If there's an entry at ${new_versioned_dir} but it's not a directory
+  # (or it's a symbolic link, whether or not it points to a directory), rsync
+  # won't get rid of it.  It's never correct to have a non-directory in place
+  # of the versioned directory, so toss out whatever's there.
+  if [[ -e "${new_versioned_dir}" ]] &&
+     ([[ -L "${new_versioned_dir}" ]] ||
+      ! [[ -d "${new_versioned_dir}" ]]); then
+    note "removing non-directory in place of versioned directory"
+    rm -f "${new_versioned_dir}" 2> /dev/null || true
+
+    # If the non-directory new_versioned_dir still exists after we attempted to
+    # remove it, just fail early here, before we get to the rsync.
+    if [[ -e "${new_versioned_dir}" ]]; then
+      err "could not remove existing file where versioned directory should be"
+      exit 11
+    fi
+  fi
+
+  # Copy the versioned directory.  The new versioned directory should have a
+  # different name than any existing one, so this won't harm anything already
+  # present in ${installed_versions_dir}, including the versioned directory
+  # being used by any running processes.  If this step is interrupted, there
+  # will be an incomplete versioned directory left behind, but it won't
+  # interfere with anything, and it will be replaced or removed during a future
+  # update attempt.
+  #
+  # In certain cases, same-version updates are distributed to move users
+  # between channels; when this happens, the contents of the versioned
+  # directories are identical and rsync will not render the versioned
+  # directory unusable even for an instant.
+  if [[ -n "${update_versioned_dir}" ]]; then
+    note "rsyncing versioned directory"
+    if ! rsync ${RSYNC_FLAGS} --delete-before "${update_versioned_dir}/" \
+                                              "${new_versioned_dir}"; then
+      err "rsync of versioned directory failed, status ${PIPESTATUS[0]}"
+
+      # If the rsync of a new-layout versioned directory failed, remove it.
+      # The incomplete version would break code signature validation.
+      note "cleaning up new_versioned_dir"
+      rm -rf "${new_versioned_dir}"
+      exit 12
+    fi
+  fi
+
+  # See if the timestamp of what's currently on disk is newer than the
+  # update's outer .app's timestamp.  rsync will copy the update's timestamp
+  # over, but if that timestamp isn't as recent as what's already on disk, the
+  # .app will need to be touched.
+  local needs_touch=
+  if [[ "${installed_app_path}" -nt "${update_app}" ]]; then
+    needs_touch="y"
+  fi
+
+  # Copy the unversioned files into place, leaving everything in
+  # ${installed_versions_dir} alone.  If this step is interrupted, the
+  # application will at least remain in a usable state, although it may not
+  # pass signature validation.  Depending on when this step is interrupted,
+  # the application will either launch the old or the new version.  The
+  # critical point is when the main executable is replaced.  There isn't very
+  # much to copy in this step, because most of the application is in the
+  # versioned directory.  This step only accounts for around 50 files, most of
+  # which are small localized InfoPlist.strings files.  Note that
+  # ${VERSIONS_DIR_NEW} are included to copy their mode bits and timestamps, but
+  # their contents are excluded, having already been installed above. The
+  # ${VERSIONS_DIR_NEW}/Current symbolic link is updated or created in this
+  # step, however.
+  note "rsyncing app directory"
+  if ! rsync ${RSYNC_FLAGS} --delete-after \
+       --include="/${VERSIONS_DIR_NEW}/Current" \
+       --exclude="/${VERSIONS_DIR_NEW}/*" "${update_app}/" \
+       "${installed_app_path}"; then
+    err "rsync of app directory failed, status ${PIPESTATUS[0]}"
+    exit 13
+  fi
+
+  note "rsyncs complete"
+
+  if [[ -n "${g_temp_dir}" ]]; then
+    # The temporary directory, if any, is no longer needed.
+    rm -rf "${g_temp_dir}" 2> /dev/null || true
+    g_temp_dir=
+    note "g_temp_dir = ${g_temp_dir}"
+  fi
+
+  # If necessary, touch the outermost .app so that it appears to the outside
+  # world that something was done to the bundle.  This will cause
+  # LaunchServices to invalidate the information it has cached about the
+  # bundle even if lsregister does not run.  This is not done if rsync already
+  # updated the timestamp to something newer than what had been on disk.  This
+  # is not considered a critical step, and if it fails, this script will not
+  # exit.
+  if [[ -n "${needs_touch}" ]]; then
+    touch -cf "${installed_app_path}" || true
+  fi
+
+  # Read the new values, such as the version.
+  note "reading new values"
+
+  local new_version_app
+  if ! new_version_app="$(infoplist_read "${installed_app_path_plist}" \
+                                         "${APP_VERSION_KEY}")" ||
+     [[ -z "${new_version_app}" ]]; then
+    err "couldn't determine new_version_app"
+    exit 14
+  fi
+
+  local new_versioned_dir="${installed_versions_dir}/${new_version_app}"
+
+  # Make sure that the update was successful by comparing the version found in
+  # the update with the version now on disk.
+  if [[ "${new_version_app}" != "${UPDATE_VERSION}" ]]; then
+    err "new_version_app and UPDATE_VERSION do not match"
+    exit 15
+  fi
+
+  # Notify LaunchServices.  This is not considered a critical step, and
+  # lsregister's exit codes shouldn't be confused with this script's own.
+  # Redirect stdout to /dev/null to suppress the useless "ThrottleProcessIO:
+  # throttling disk i/o" messages that lsregister might print.
+  note "notifying LaunchServices"
+  local coreservices="/System/Library/Frameworks/CoreServices.framework"
+  local launchservices="${coreservices}/Frameworks/LaunchServices.framework"
+  local lsregister="${launchservices}/Support/lsregister"
+  "${lsregister}" -f "${installed_app_path}" > /dev/null || true
+
+  # The remaining steps are not considered critical.
+  set +e
+
+  # Try to clean up old versions that are not in use.  The strategy is to keep
+  # the versioned directory corresponding to the update just applied
+  # (obviously) and the version that was just replaced, and to use ps and lsof
+  # to see if it looks like any processes are currently using any other old
+  # directories.  Directories not in use are removed.  Old versioned
+  # directories that are in use are left alone so as to not interfere with
+  # running processes.  These directories can be cleaned up by this script on
+  # future updates.
+  #
+  # To determine which directories are in use, both ps and lsof are used.
+  # Each approach has limitations.
+  #
+  # The ps check looks for processes within the versioned directory.  Only
+  # helper processes, such as renderers, are within the versioned directory.
+  # Browser processes are not, so the ps check will not find them, and will
+  # assume that a versioned directory is not in use if a browser is open
+  # without any windows.  The ps mechanism can also only detect processes
+  # running on the system that is performing the update.  If network shares
+  # are involved, all bets are off.
+  #
+  # The lsof check looks to see what processes have the framework dylib open.
+  # Browser processes will have their versioned framework dylib open, so this
+  # check is able to catch browsers even if there are no associated helper
+  # processes.  Like the ps check, the lsof check is limited to processes on
+  # the system that is performing the update.  Finally, unless running as
+  # root, the lsof check can only find processes running as the effective user
+  # performing the update.
+  #
+  # These limitations are motivations to additionally preserve the versioned
+  # directory corresponding to the version that was just replaced.
+  note "cleaning up old versioned directories"
+
+  local versioned_dir
+  for versioned_dir in "${installed_versions_dir_new}/"*; do
+    note "versioned_dir = ${versioned_dir}"
+    if [[ "${versioned_dir}" = "${new_versioned_dir}" ]] ||
+       [[ "${versioned_dir}" = "${old_versioned_dir}" ]] ||
+       [[ "${versioned_dir}" = "${installed_versions_dir_new}/Current" ]]; then
+      # This is the versioned directory corresponding to the update that was
+      # just applied or the version that was previously in use.  Leave it
+      # alone.
+      continue
+    fi
+
+    # Look for any processes whose executables are within this versioned
+    # directory.  They'll be helper processes, such as renderers.  Their
+    # existence indicates that this versioned directory is currently in use.
+    local ps_string="${versioned_dir}/"
+
+    # Look for any processes using the framework dylib.  This will catch
+    # browser processes where the ps check will not, but it is limited to
+    # processes running as the effective user.
+    local lsof_file
+    if [[ -e "${versioned_dir}/${FRAMEWORK_DIR}/${FRAMEWORK_NAME}" ]]; then
+      # Old layout.
+      lsof_file="${versioned_dir}/${FRAMEWORK_DIR}/${FRAMEWORK_NAME}"
+    else
+      # New layout.
+      lsof_file="${versioned_dir}/${FRAMEWORK_NAME}"
+    fi
+
+    # ps -e displays all users' processes, -ww causes ps to not truncate
+    # lines, -o comm instructs it to only print the command name, and the =
+    # tells it to not print a header line.
+    # The cut invocation filters the ps output to only have at most the number
+    # of characters in ${ps_string}.  This is done so that grep can look for
+    # an exact match.
+    # grep -F tells grep to look for lines that are exact matches (not regular
+    # expressions), -q tells it to not print any output and just indicate
+    # matches by exit status, and -x tells it that the entire line must match
+    # ${ps_string} exactly, as opposed to matching a substring.  A match
+    # causes grep to exit zero (true).
+    #
+    # lsof will exit nonzero if ${lsof_file} does not exist or is open by any
+    # process.  If the file exists and is open, it will exit zero (true).
+    if (! ps -ewwo comm= | \
+          cut -c "1-${#ps_string}" | \
+          grep -Fqx "${ps_string}") &&
+       (! lsof "${lsof_file}" >& /dev/null); then
+      # It doesn't look like anything is using this versioned directory.  Get
+      # rid of it.
+      note "versioned_dir doesn't appear to be in use, removing"
+      rm -rf "${versioned_dir}"
+    else
+      note "versioned_dir is in use, skipping"
+    fi
+  done
+
+  note "setting permissions"
+
+  local chmod_mode="a+rX,u+w,go-w"
+  if [[ "${installed_app_path:0:14}" = "/Applications/" ]] &&
+    chgrp -Rh admin "${installed_app_path}" 2> /dev/null; then
+    chmod_mode="a+rX,ug+w,o-w"
+  else
+    chown -Rh root:wheel "${installed_app_path}" 2> /dev/null
+  fi
+
+  note "chmod_mode = ${chmod_mode}"
+  chmod -R "${chmod_mode}" "${installed_app_path}" 2> /dev/null
+
+  # On the Mac, or at least on HFS+, symbolic link permissions are significant,
+  # but chmod -R and -h can't be used together.  Do another pass to fix the
+  # permissions on any symbolic links.
+  find "${installed_app_path}" -type l -exec chmod -h "${chmod_mode}" {} + \
+      2> /dev/null
+
+  # If an update is triggered from within the application itself, the update
+  # process inherits the quarantine bit (LSFileQuarantineEnabled).  Any files
+  # or directories created during the update will be quarantined in that case,
+  # which may cause Launch Services to display quarantine UI.  That's bad,
+  # especially if it happens when the outer .app launches a quarantined inner
+  # helper.  If the application is already on the system and is being updated,
+  # then it can be assumed that it should not be quarantined.  Use xattr to
+  # drop the quarantine attribute.
+  #
+  # TODO(mark): Instead of letting the quarantine attribute be set and then
+  # dropping it here, figure out a way to get the update process to run
+  # without LSFileQuarantineEnabled even when triggering an update from within
+  # the application.
+  note "lifting quarantine"
+
+  xattr -d -r "${QUARANTINE_ATTR}" "${installed_app_path}" 2> /dev/null
+
+  # Great success!
+  note "done!"
+
+  trap - EXIT
+
+  return 0
+}
+
+# Check "less than" instead of "not equal to" in case there are changes to pass
+# more arguments.
+if [[ ${#} -lt 3 ]]; then
+  usage
+  echo ${#} >& 1
+  exit 16
+fi
+
+main "${@}"
+exit ${?}
diff --git a/chrome/updater/mac/BUILD.gn b/chrome/updater/mac/BUILD.gn
index a7d4f53..c61124bc 100644
--- a/chrome/updater/mac/BUILD.gn
+++ b/chrome/updater/mac/BUILD.gn
@@ -9,6 +9,7 @@
 group("mac") {
   deps = [
     ":app_install",
+    ":browser_install_script",
     ":updater_bundle",
     ":updater_install_script",
     "//chrome/updater/mac/signing",
@@ -120,7 +121,7 @@
 }
 
 action("updater_install_script") {
-  script = "setup/embed_version.py"
+  script = "embed_variables.py"
 
   inputs = [
     script,
@@ -134,6 +135,27 @@
     rebase_path("setup/.install.sh"),
     "-o",
     rebase_path(root_out_dir + "/chrome/updater/.install"),
+    "-p",
+    updater_product_full_name,
+  ]
+}
+
+action("browser_install_script") {
+  script = "embed_variables.py"
+
+  inputs = [
+    script,
+    ".install.sh",
+  ]
+
+  outputs = [ "$root_out_dir/$browser_product_name Packaging/updater/.install" ]
+
+  args = [
+    "-i",
+    rebase_path(".install.sh"),
+    "-o",
+    rebase_path(
+        root_out_dir + "/$browser_product_name Packaging/updater/.install"),
     "-v",
     chrome_version_full,
   ]
diff --git a/chrome/updater/mac/embed_variables.py b/chrome/updater/mac/embed_variables.py
new file mode 100644
index 0000000..1e26802c
--- /dev/null
+++ b/chrome/updater/mac/embed_variables.py
@@ -0,0 +1,65 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import ConfigParser
+import glob
+import optparse
+import os
+import shutil
+import subprocess
+import sys
+
+
+def embed_version(input_file, output_file, version, product_full_name):
+    fin = open(input_file, 'r')
+    fout = open(output_file, 'w')
+
+    if version:
+        replace_string = 'UPDATE_VERSION=\"' + version + '\"'
+        for line in fin:
+            fout.write(line.replace('UPDATE_VERSION=', replace_string))
+
+    if product_full_name:
+        replace_string = 'PRODUCT_NAME=\"' + product_full_name + '\"'
+        for line in fin:
+            fout.write(line.replace('PRODUCT_NAME=', replace_string))
+
+    fin.close()
+    fout.close()
+
+    os.chmod(output_file, 0755)
+
+
+def parse_options():
+    parser = optparse.OptionParser()
+    parser.add_option('-i', '--input_file', help='Path to the input script.')
+    parser.add_option(
+        '-o',
+        '--output_file',
+        help='Path to where we should output the script')
+    parser.add_option(
+        '-v',
+        '--version',
+        help='Version of the application bundle being built.')
+    parser.add_option(
+        '-p',
+        '--product_full_name',
+        help='Name of the product being built.')
+    options, _ = parser.parse_args()
+
+    if not options.version and not options.product_full_name:
+        parser.error('You must provide a version or a product name')
+
+    return options
+
+
+def main(options):
+    embed_version(options.input_file, options.output_file,
+                  options.version, options.product_full_name)
+    return 0
+
+
+if '__main__' == __name__:
+    options = parse_options()
+    sys.exit(main(options))
diff --git a/chrome/updater/mac/setup/.install.sh b/chrome/updater/mac/setup/.install.sh
index f2e8545..13da606 100755
--- a/chrome/updater/mac/setup/.install.sh
+++ b/chrome/updater/mac/setup/.install.sh
@@ -5,688 +5,42 @@
 
 # usage: install.sh update_dmg_mount_point installed_app_path current_version
 #
-# Called by Omaha v4 to update the installed application with a new version from
-# the dmg.
+# Called by Omaha v4 to install the updater.
 #
 # Exit codes:
-#   0   Success!
-#   1   Unknown Failure
-#   2   Could not locate installed app path.
-#   3   DMG mount point is not an absolute path.
-#   4   Path to new .app must be an absolute path.
-#   5   Update app is not using the new version folder layout.
-#   6   Installed app path must be an absolute path to a directory.
-#   7   Installed app's versioned directory is in old format.
-#   8   Installed app's versioned directory is in old format.
-#   9   Installed app's versioned directory is in old format.
-#   10  Making versioned directory for new version failed.
-#   11  Could not remove existing file where versioned directory should be.
-#   12  rsync of versioned directory failed.
-#   13  rsync of app directory failed.
-#   14  We could not determine the new app version.
-#   15  The new app version does not match the update version.
-#   16  This will return a usage message.
-
-set -eu
+#   0         Success!
+#   10 - 42   Error messages from the install.
+#   99        This will return a usage message.
+#
 
 # Set path to /bin, /usr/bin, /sbin, /usr/sbin
 export PATH="/bin:/usr/bin:/sbin:/usr/sbin"
 
-# Environment sanitization.  Clear environment variables that might impact the
-# interpreter's operation.  The |bash -p| invocation on the #! line takes the
-# bite out of BASH_ENV, ENV, and SHELLOPTS (among other features), but
-# clearing them here ensures that they won't impact any shell scripts used as
-# utility programs. SHELLOPTS is read-only and can't be unset, only
-# unexported.
-unset BASH_ENV CDPATH ENV GLOBIGNORE IFS POSIXLY_CORRECT
-export -n SHELLOPTS
-
-set -o pipefail
-shopt -s nullglob
-
-ME="$(basename "${0}")"
-readonly ME
-
-# We will populate this variable with the version of the application bundle
-# it'll be packaged with when the build is run. This allows us to not do a
-# defaults read on Info.plist to find the update_version.
-UPDATE_VERSION=
-readonly UPDATE_VERSION
-
-err() {
-  local error="${1}"
-  local id=": ${$} $(date "+%Y-%m-%d %H:%M:%S %z")"
-  echo "${ME}${id}: ${error}" >& 2
-}
-
-note() {
-  local message="${1}"
-  echo "${ME}: ${$} $(date "+%Y-%m-%d %H:%M:%S %z"): ${message}" >& 1
-}
-
-g_temp_dir=
-cleanup() {
-  local status=${?}
-
-  trap - EXIT
-  trap '' HUP INT QUIT TERM
-
-  if [[ ${status} -ge 128 ]]; then
-    err "Caught signal $((${status} - 128))"
-  fi
-
-  if [[ -n "${g_temp_dir}" ]]; then
-    rm -rf "${g_temp_dir}"
-  fi
-
-  exit ${status}
-}
-
-# Returns 0 (true) if |symlink| exists, is a symbolic link, and appears
-# writable on the basis of its POSIX permissions.  This is used to determine
-# writability like test's -w primary, but -w resolves symbolic links and this
-# function does not.
-is_writable_symlink() {
-  local symlink="${1}"
-
-  local link_mode="$(stat -f %Sp "${symlink}" 2> /dev/null || true)"
-  if [[ -z "${link_mode}" ]] || [[ "${link_mode:0:1}" != "l" ]]; then
-    return 1
-  fi
-
-  local link_user="$(stat -f %u "${symlink}" 2> /dev/null || true)"
-  local link_group="$(stat -f %g "${symlink}" 2> /dev/null || true)"
-  if [[ -z "${link_user}" ]] || [[ -z "${link_group}" ]]; then
-    return 1
-  fi
-
-  # If the users match, check the owner-write bit.
-  if [[ ${EUID} -eq "${link_user}" ]]; then
-    if [[ "${link_mode:2:1}" = "w" ]]; then
-      return 0
-    fi
-    return 1
-  fi
-
-  # If the file's group matches any of the groups that this process is a
-  # member of, check the group-write bit.
-  local group_match=
-  local group
-  for group in "${GROUPS[@]}"; do
-    if [[ "${group}" -eq "${link_group}" ]]; then
-      group_match="y"
-      break
-    fi
-  done
-  if [[ -n "${group_match}" ]]; then
-    if [[ "${link_mode:5:1}" = "w" ]]; then
-      return 0
-    fi
-    return 1
-  fi
-
-  # Check the other-write bit.
-  if [[ "${link_mode:8:1}" = "w" ]]; then
-    return 0
-  fi
-
-  return 1
-}
-
-# If |symlink| exists and is a symbolic link, but is not writable according to
-# is_writable_symlink, this function attempts to replace it with a new
-# writable symbolic link.  If |symlink| does not exist, is not a symbolic
-# link, or is already writable, this function does nothing.  This function
-# always returns 0 (true).
-ensure_writable_symlink() {
-  local symlink="${1}"
-
-  if [[ -L "${symlink}" ]] && ! is_writable_symlink "${symlink}"; then
-    # If ${symlink} refers to a directory, doing this naively might result in
-    # the new link being placed in that directory, instead of replacing the
-    # existing link.  ln -fhs is supposed to handle this case, but it does so
-    # by unlinking (removing) the existing symbolic link before creating a new
-    # one.  That leaves a small window during which the symbolic link is not
-    # present on disk at all.
-    #
-    # To avoid that possibility, a new symbolic link is created in a temporary
-    # location and then swapped into place with mv.  An extra temporary
-    # directory is used to convince mv to replace the symbolic link: again, if
-    # the existing link refers to a directory, "mv newlink oldlink" will
-    # actually leave oldlink alone and place newlink into the directory.
-    # "mv newlink dirname(oldlink)" works as expected, but in order to replace
-    # oldlink, newlink must have the same basename, hence the temporary
-    # directory.
-
-    local target="$(readlink "${symlink}" 2> /dev/null || true)"
-    if [[ -z "${target}" ]]; then
-      return 0
-    fi
-
-    # Error handling strategy: if anything fails, such as the mktemp, ln,
-    # chmod, or mv, ignore the failure and return 0 (success), leaving the
-    # existing state with the non-writable symbolic link intact.  Failures
-    # in this function will be difficult to understand and diagnose, and a
-    # non-writable symbolic link is not necessarily fatal.  If something else
-    # requires a writable symbolic link, allowing it to fail when a symbolic
-    # link is not writable is easier to understand than bailing out of the
-    # script on failure here.
-
-    local symlink_dir="$(dirname "${symlink}")"
-    local temp_link_dir=\
-      "$(mktemp -d "${symlink_dir}/.symlink_temp.XXXXXX" || true)"
-    if [[ -z "${temp_link_dir}" ]]; then
-      return 0
-    fi
-    local temp_link="${temp_link_dir}/$(basename "${symlink}")"
-
-    (ln -fhs "${target}" "${temp_link}" &&
-        chmod -h 755 "${temp_link}" &&
-        mv -f "${temp_link}" "${symlink_dir}/") || true
-    rm -rf "${temp_link_dir}"
-  fi
-
-  return 0
-}
-
-# ensure_writable_symlinks_recursive calls ensure_writable_symlink for every
-# symbolic link in |directory|, recursively.
-#
-# In some very weird and rare cases, it is possible to wind up with a user
-# installation that contains symbolic links that the user does not have write
-# permission over.  More on how that might happen later.
-#
-# If a weird and rare case like this is observed, rsync will exit with an
-# error when attempting to update the times on these symbolic links.  rsync
-# may not be intelligent enough to try creating a new symbolic link in these
-# cases, but this script can be.
-#
-# The problem occurs when an administrative user first drag-installs the
-# application to /Applications, resulting in the program's user being set to
-# the user's own ID.  If, subsequently, a .pkg package is installed over that,
-# the existing directory ownership will be preserved, but file ownership will
-# be changed to whatever is specified by the package, typically root.  This
-# applies to symbolic links as well.  On a subsequent update, rsync will be
-# able to copy the new files into place, because the user still has permission
-# to write to the directories.  If the symbolic link targets are not changing,
-# though, rsync will not replace them, and they will remain owned by root.
-# The user will not have permission to update the time on the symbolic links,
-# resulting in an rsync error.
-ensure_writable_symlinks_recursive() {
-  local directory="${1}"
-
-  # This fix-up is not necessary when running as root, because root will
-  # always be able to write everything needed.
-  if [[ ${EUID} -eq 0 ]]; then
-    return 0
-  fi
-
-  # This step isn't critical.
-  local set_e=
-  if [[ "${-}" =~ e ]]; then
-    set_e="y"
-    set +e
-  fi
-
-  # Use find -print0 with read -d $'\0' to handle even the weirdest paths.
-  local symlink
-  while IFS= read -r -d $'\0' symlink; do
-    ensure_writable_symlink "${symlink}"
-  done < <(find "${directory}" -type l -print0)
-
-  # Go back to how things were.
-  if [[ -n "${set_e}" ]]; then
-    set -e
-  fi
-}
-
-# Runs "defaults read" to obtain the value of a key in a property list. As
-# with "defaults read", an absolute path to a plist is supplied, without the
-# ".plist" extension.
-#
-# As of Mac OS X 10.8, defaults (and NSUserDefaults and CFPreferences)
-# normally communicates with cfprefsd to read and write plists. Changes to a
-# plist file aren't necessarily reflected immediately via this API family when
-# not made through this API family, because cfprefsd may return cached data
-# from a former on-disk version of a plist file instead of reading the current
-# version from disk. The old behavior can be restored by setting the
-# __CFPREFERENCES_AVOID_DAEMON environment variable, although extreme care
-# should be used because portions of the system that use this API family
-# normally and thus use cfprefsd and its cache will become unsynchronized with
-# the on-disk state.
-#
-# This function is provided to set __CFPREFERENCES_AVOID_DAEMON when calling
-# "defaults read" and thus avoid cfprefsd and its on-disk cache, and is
-# intended only to be used to read values from Info.plist files, which are not
-# preferences. The use of "defaults" for this purpose has always been
-# questionable, but there's no better option to interact with plists from
-# shell scripts. Definitely don't use infoplist_read to read preference
-# plists.
-#
-# This function exists because the update process delivers new copies of
-# Info.plist files to the disk behind cfprefsd's back, and if cfprefsd becomes
-# aware of the original version of the file for any reason (such as this
-# script reading values from it via "defaults read"), the new version of the
-# file will not be immediately effective or visible via cfprefsd after the
-# update is applied.
-infoplist_read() {
-  __CFPREFERENCES_AVOID_DAEMON=1 defaults read "${@}"
-}
+PRODUCT_NAME=
+readonly PRODUCT_NAME
+readonly APP_DIR="${PRODUCT_NAME}.app"
 
 usage() {
-  echo "usage: ${ME} update_dmg_mount_point installed_app_path current_version"\
-  >& 2
+  echo "usage: ${ME} update_dmg_mount_point" >& 2
 }
 
 main() {
   local update_dmg_mount_point="${1}"
-  local installed_app_path="${2}"
-  local old_version_app="${3}"
+  local path_to_executable=\
+"${update_dmg_mount_point}/${APP_DIR}/Contents/MacOS/${PRODUCT_NAME}"
 
-  # Early steps are critical.  Don't continue past any failure.
-  set -e
+  # Run the executable with install
+  exit_code="$(${path_to_executable} --install)"
 
-  trap cleanup EXIT HUP INT QUIT TERM
-
-  # Figure out where to install.
-  if [[ ! -d "${installed_app_path}" ]]; then
-    err "couldn't locate installed_app_path"
-    exit 2
-  fi
-  note "installed_app_path = ${installed_app_path}"
-  note "old_version_app = ${old_version_app}"
-
-  # The app directory, product name, etc. can all be gotten from the
-  # installed_app_path.
-  readonly APP_DIR="$(basename "${installed_app_path}")"
-  readonly PRODUCT_NAME="${APP_DIR%.*}"
-  readonly FRAMEWORK_NAME="${PRODUCT_NAME} Framework"
-  readonly FRAMEWORK_DIR="${FRAMEWORK_NAME}.framework"
-  readonly CONTENTS_DIR="Contents"
-  readonly APP_PLIST="${CONTENTS_DIR}/Info"
-  readonly VERSIONS_DIR_NEW=\
-"${CONTENTS_DIR}/Frameworks/${FRAMEWORK_DIR}/Versions"
-  readonly APP_VERSION_KEY="CFBundleShortVersionString"
-
-  readonly QUARANTINE_ATTR="com.apple.quarantine"
-
-  # Don't use rsync --archive, because --archive includes --group and --owner,
-  # which copy groups and owners, respectively, from the source, and that is
-  # undesirable in this case (often, this script will have permission to set
-  # those attributes).  --archive also includes --devices and --specials, which
-  # copy files that should never occur in the transfer; --devices only works
-  # when running as root, so for consistency between privileged and unprivileged
-  # operation, this option is omitted as well.  --archive does not include
-  # --ignore-times, which is desirable, as it forces rsync to copy files even
-  # when their sizes and modification times are identical, as their content
-  # still may be different.
-  readonly RSYNC_FLAGS="--ignore-times --links --perms --recursive --times"
-
-  note "update_dmg_mount_point = ${update_dmg_mount_point}"
-
-  if [[ -z "${update_dmg_mount_point}" ]] ||
-     [[ "${update_dmg_mount_point:0:1}" != "/" ]] ||
-     ! [[ -d "${update_dmg_mount_point}" ]]; then
-    err "update_dmg_mount_point must be an absolute path to a directory"
-    usage
-    exit 3
-  fi
-
-  # The update to install.
-
-  # update_app is the path to the new version of the .app.
-  local update_app="${update_dmg_mount_point}/${APP_DIR}"
-  note "update_app = ${update_app}"
-
-  # Make sure that it's an absolute path.
-  if [[ "${update_app:0:1}" != "/" ]]; then
-    err "update_app must be an absolute path"
-    exit 4
-  fi
-
-  if [[ ! -d "${update_app}/${VERSIONS_DIR_NEW}" ]]; then
-    err "update app not using new layout"
-    exit 5
-  fi
-
-  if [[ "${installed_app_path:0:1}" != "/" ]] ||
-     ! [[ -d "${installed_app_path}" ]]; then
-    err "installed_app_path must be an absolute path to a directory"
-    exit 6
-  fi
-
-  # Figure out what the existing installed application is using for its
-  # versioned directory.  This will be used later, to avoid removing the
-  # existing installed version's versioned directory in case anything is still
-  # using it.
-  note "reading install values"
-
-  local installed_app_path_plist="${installed_app_path}/${APP_PLIST}"
-  note "installed_app_path_plist = ${installed_app_path_plist}"
-
-  local installed_versions_dir_new="${installed_app_path}/${VERSIONS_DIR_NEW}"
-  note "installed_versions_dir_new = ${installed_versions_dir_new}"
-
-  local installed_versions_dir="${installed_versions_dir_new}"
-  if [[ ! -d "${installed_versions_dir}" ]]; then
-    err "installed app does not have versioned dir. Might be an old version."
-    exit 7
-  fi
-  note "installed_versions_dir = ${installed_versions_dir}"
-
-  # If the installed application is incredibly old, or in a skeleton bootstrap
-  # installation, old_versioned_dir may not exist.
-  local old_versioned_dir
-  if [[ -n "${old_version_app}" ]]; then
-    if [[ -d "${installed_versions_dir_new}/${old_version_app}" ]]; then
-      old_versioned_dir="${installed_versions_dir_new}/${old_version_app}"
-    else
-      err "installed app does not have versioned dir. Might be an old version."
-      exit 8
-    fi
-  fi
-  note "old_versioned_dir = ${old_versioned_dir}"
-
-  local update_versioned_dir=\
-"${update_app}/${VERSIONS_DIR_NEW}/${UPDATE_VERSION}"
-  if [[ ! -d "${update_versioned_dir}" ]]; then
-    err "Update versioned dir does not have the new layout."
-    exit 9
-  fi
-
-  ensure_writable_symlinks_recursive "${installed_app_path}"
-
-  # By copying to ${installed_app_path}, the existing application name will be
-  # preserved, if the user has renamed the application on disk.  Respecting
-  # the user's changes is friendly.
-
-  # Make sure that ${installed_versions_dir} exists, so that it can receive
-  # the versioned directory.  It may not exist if updating from an older
-  # version that did not use the same versioned layout on disk.  Later, during
-  # the rsync to copy the application directory, the mode bits and timestamp on
-  # ${installed_versions_dir} will be set to conform to whatever is present in
-  # the update.
-  #
-  # ${installed_app_path} is guaranteed to exist at this point, but
-  # ${installed_app_path}/${CONTENTS_DIR} may not if things are severely broken
-  # or if this update is actually an initial installation from an updater
-  # skeleton bootstrap.  The mkdir creates ${installed_app_path}/${CONTENTS_DIR}
-  # if it doesn't exist; its mode bits will be fixed up in a subsequent rsync.
-  note "creating installed_versions_dir"
-  if ! mkdir -p "${installed_versions_dir}"; then
-    err "mkdir of installed_versions_dir failed"
-    exit 10
-  fi
-
-  local new_versioned_dir="${installed_versions_dir}/${UPDATE_VERSION}"
-  note "new_versioned_dir = ${new_versioned_dir}"
-
-  # If there's an entry at ${new_versioned_dir} but it's not a directory
-  # (or it's a symbolic link, whether or not it points to a directory), rsync
-  # won't get rid of it.  It's never correct to have a non-directory in place
-  # of the versioned directory, so toss out whatever's there.
-  if [[ -e "${new_versioned_dir}" ]] &&
-     ([[ -L "${new_versioned_dir}" ]] ||
-      ! [[ -d "${new_versioned_dir}" ]]); then
-    note "removing non-directory in place of versioned directory"
-    rm -f "${new_versioned_dir}" 2> /dev/null || true
-
-    # If the non-directory new_versioned_dir still exists after we attempted to
-    # remove it, just fail early here, before we get to the rsync.
-    if [[ -e "${new_versioned_dir}" ]]; then
-      err "could not remove existing file where versioned directory should be"
-      exit 11
-    fi
-  fi
-
-  # Copy the versioned directory.  The new versioned directory should have a
-  # different name than any existing one, so this won't harm anything already
-  # present in ${installed_versions_dir}, including the versioned directory
-  # being used by any running processes.  If this step is interrupted, there
-  # will be an incomplete versioned directory left behind, but it won't
-  # interfere with anything, and it will be replaced or removed during a future
-  # update attempt.
-  #
-  # In certain cases, same-version updates are distributed to move users
-  # between channels; when this happens, the contents of the versioned
-  # directories are identical and rsync will not render the versioned
-  # directory unusable even for an instant.
-  if [[ -n "${update_versioned_dir}" ]]; then
-    note "rsyncing versioned directory"
-    if ! rsync ${RSYNC_FLAGS} --delete-before "${update_versioned_dir}/" \
-                                              "${new_versioned_dir}"; then
-      err "rsync of versioned directory failed, status ${PIPESTATUS[0]}"
-
-      # If the rsync of a new-layout versioned directory failed, remove it.
-      # The incomplete version would break code signature validation.
-      note "cleaning up new_versioned_dir"
-      rm -rf "${new_versioned_dir}"
-      exit 12
-    fi
-  fi
-
-  # See if the timestamp of what's currently on disk is newer than the
-  # update's outer .app's timestamp.  rsync will copy the update's timestamp
-  # over, but if that timestamp isn't as recent as what's already on disk, the
-  # .app will need to be touched.
-  local needs_touch=
-  if [[ "${installed_app_path}" -nt "${update_app}" ]]; then
-    needs_touch="y"
-  fi
-
-  # Copy the unversioned files into place, leaving everything in
-  # ${installed_versions_dir} alone.  If this step is interrupted, the
-  # application will at least remain in a usable state, although it may not
-  # pass signature validation.  Depending on when this step is interrupted,
-  # the application will either launch the old or the new version.  The
-  # critical point is when the main executable is replaced.  There isn't very
-  # much to copy in this step, because most of the application is in the
-  # versioned directory.  This step only accounts for around 50 files, most of
-  # which are small localized InfoPlist.strings files.  Note that
-  # ${VERSIONS_DIR_NEW} are included to copy their mode bits and timestamps, but
-  # their contents are excluded, having already been installed above. The
-  # ${VERSIONS_DIR_NEW}/Current symbolic link is updated or created in this
-  # step, however.
-  note "rsyncing app directory"
-  if ! rsync ${RSYNC_FLAGS} --delete-after \
-       --include="/${VERSIONS_DIR_NEW}/Current" \
-       --exclude="/${VERSIONS_DIR_NEW}/*" "${update_app}/" \
-       "${installed_app_path}"; then
-    err "rsync of app directory failed, status ${PIPESTATUS[0]}"
-    exit 13
-  fi
-
-  note "rsyncs complete"
-
-  if [[ -n "${g_temp_dir}" ]]; then
-    # The temporary directory, if any, is no longer needed.
-    rm -rf "${g_temp_dir}" 2> /dev/null || true
-    g_temp_dir=
-    note "g_temp_dir = ${g_temp_dir}"
-  fi
-
-  # If necessary, touch the outermost .app so that it appears to the outside
-  # world that something was done to the bundle.  This will cause
-  # LaunchServices to invalidate the information it has cached about the
-  # bundle even if lsregister does not run.  This is not done if rsync already
-  # updated the timestamp to something newer than what had been on disk.  This
-  # is not considered a critical step, and if it fails, this script will not
-  # exit.
-  if [[ -n "${needs_touch}" ]]; then
-    touch -cf "${installed_app_path}" || true
-  fi
-
-  # Read the new values, such as the version.
-  note "reading new values"
-
-  local new_version_app
-  if ! new_version_app="$(infoplist_read "${installed_app_path_plist}" \
-                                         "${APP_VERSION_KEY}")" ||
-     [[ -z "${new_version_app}" ]]; then
-    err "couldn't determine new_version_app"
-    exit 14
-  fi
-
-  local new_versioned_dir="${installed_versions_dir}/${new_version_app}"
-
-  # Make sure that the update was successful by comparing the version found in
-  # the update with the version now on disk.
-  if [[ "${new_version_app}" != "${UPDATE_VERSION}" ]]; then
-    err "new_version_app and UPDATE_VERSION do not match"
-    exit 15
-  fi
-
-  # Notify LaunchServices.  This is not considered a critical step, and
-  # lsregister's exit codes shouldn't be confused with this script's own.
-  # Redirect stdout to /dev/null to suppress the useless "ThrottleProcessIO:
-  # throttling disk i/o" messages that lsregister might print.
-  note "notifying LaunchServices"
-  local coreservices="/System/Library/Frameworks/CoreServices.framework"
-  local launchservices="${coreservices}/Frameworks/LaunchServices.framework"
-  local lsregister="${launchservices}/Support/lsregister"
-  "${lsregister}" -f "${installed_app_path}" > /dev/null || true
-
-  # The remaining steps are not considered critical.
-  set +e
-
-  # Try to clean up old versions that are not in use.  The strategy is to keep
-  # the versioned directory corresponding to the update just applied
-  # (obviously) and the version that was just replaced, and to use ps and lsof
-  # to see if it looks like any processes are currently using any other old
-  # directories.  Directories not in use are removed.  Old versioned
-  # directories that are in use are left alone so as to not interfere with
-  # running processes.  These directories can be cleaned up by this script on
-  # future updates.
-  #
-  # To determine which directories are in use, both ps and lsof are used.
-  # Each approach has limitations.
-  #
-  # The ps check looks for processes within the versioned directory.  Only
-  # helper processes, such as renderers, are within the versioned directory.
-  # Browser processes are not, so the ps check will not find them, and will
-  # assume that a versioned directory is not in use if a browser is open
-  # without any windows.  The ps mechanism can also only detect processes
-  # running on the system that is performing the update.  If network shares
-  # are involved, all bets are off.
-  #
-  # The lsof check looks to see what processes have the framework dylib open.
-  # Browser processes will have their versioned framework dylib open, so this
-  # check is able to catch browsers even if there are no associated helper
-  # processes.  Like the ps check, the lsof check is limited to processes on
-  # the system that is performing the update.  Finally, unless running as
-  # root, the lsof check can only find processes running as the effective user
-  # performing the update.
-  #
-  # These limitations are motivations to additionally preserve the versioned
-  # directory corresponding to the version that was just replaced.
-  note "cleaning up old versioned directories"
-
-  local versioned_dir
-  for versioned_dir in "${installed_versions_dir_new}/"*; do
-    note "versioned_dir = ${versioned_dir}"
-    if [[ "${versioned_dir}" = "${new_versioned_dir}" ]] ||
-       [[ "${versioned_dir}" = "${old_versioned_dir}" ]] ||
-       [[ "${versioned_dir}" = "${installed_versions_dir_new}/Current" ]]; then
-      # This is the versioned directory corresponding to the update that was
-      # just applied or the version that was previously in use.  Leave it
-      # alone.
-      continue
-    fi
-
-    # Look for any processes whose executables are within this versioned
-    # directory.  They'll be helper processes, such as renderers.  Their
-    # existence indicates that this versioned directory is currently in use.
-    local ps_string="${versioned_dir}/"
-
-    # Look for any processes using the framework dylib.  This will catch
-    # browser processes where the ps check will not, but it is limited to
-    # processes running as the effective user.
-    local lsof_file
-    if [[ -e "${versioned_dir}/${FRAMEWORK_DIR}/${FRAMEWORK_NAME}" ]]; then
-      # Old layout.
-      lsof_file="${versioned_dir}/${FRAMEWORK_DIR}/${FRAMEWORK_NAME}"
-    else
-      # New layout.
-      lsof_file="${versioned_dir}/${FRAMEWORK_NAME}"
-    fi
-
-    # ps -e displays all users' processes, -ww causes ps to not truncate
-    # lines, -o comm instructs it to only print the command name, and the =
-    # tells it to not print a header line.
-    # The cut invocation filters the ps output to only have at most the number
-    # of characters in ${ps_string}.  This is done so that grep can look for
-    # an exact match.
-    # grep -F tells grep to look for lines that are exact matches (not regular
-    # expressions), -q tells it to not print any output and just indicate
-    # matches by exit status, and -x tells it that the entire line must match
-    # ${ps_string} exactly, as opposed to matching a substring.  A match
-    # causes grep to exit zero (true).
-    #
-    # lsof will exit nonzero if ${lsof_file} does not exist or is open by any
-    # process.  If the file exists and is open, it will exit zero (true).
-    if (! ps -ewwo comm= | \
-          cut -c "1-${#ps_string}" | \
-          grep -Fqx "${ps_string}") &&
-       (! lsof "${lsof_file}" >& /dev/null); then
-      # It doesn't look like anything is using this versioned directory.  Get
-      # rid of it.
-      note "versioned_dir doesn't appear to be in use, removing"
-      rm -rf "${versioned_dir}"
-    else
-      note "versioned_dir is in use, skipping"
-    fi
-  done
-
-  note "setting permissions"
-
-  local chmod_mode="a+rX,u+w,go-w"
-  if [[ "${installed_app_path:0:14}" = "/Applications/" ]] &&
-    chgrp -Rh admin "${installed_app_path}" 2> /dev/null; then
-    chmod_mode="a+rX,ug+w,o-w"
-  else
-    chown -Rh root:wheel "${installed_app_path}" 2> /dev/null
-  fi
-
-  note "chmod_mode = ${chmod_mode}"
-  chmod -R "${chmod_mode}" "${installed_app_path}" 2> /dev/null
-
-  # On the Mac, or at least on HFS+, symbolic link permissions are significant,
-  # but chmod -R and -h can't be used together.  Do another pass to fix the
-  # permissions on any symbolic links.
-  find "${installed_app_path}" -type l -exec chmod -h "${chmod_mode}" {} + \
-      2> /dev/null
-
-  # If an update is triggered from within the application itself, the update
-  # process inherits the quarantine bit (LSFileQuarantineEnabled).  Any files
-  # or directories created during the update will be quarantined in that case,
-  # which may cause Launch Services to display quarantine UI.  That's bad,
-  # especially if it happens when the outer .app launches a quarantined inner
-  # helper.  If the application is already on the system and is being updated,
-  # then it can be assumed that it should not be quarantined.  Use xattr to
-  # drop the quarantine attribute.
-  #
-  # TODO(mark): Instead of letting the quarantine attribute be set and then
-  # dropping it here, figure out a way to get the update process to run
-  # without LSFileQuarantineEnabled even when triggering an update from within
-  # the application.
-  note "lifting quarantine"
-
-  xattr -d -r "${QUARANTINE_ATTR}" "${installed_app_path}" 2> /dev/null
-
-  # Great success!
-  note "done!"
-
-  trap - EXIT
-
-  return 0
+  return exit_code
 }
 
 # Check "less than" instead of "not equal to" in case there are changes to pass
 # more arguments.
-if [[ ${#} -lt 3 ]]; then
+if [[ ${#} -lt 1 ]]; then
   usage
   echo ${#} >& 1
-  exit 16
+  exit 99
 fi
 
 main "${@}"
diff --git a/chrome/updater/mac/setup/embed_version.py b/chrome/updater/mac/setup/embed_version.py
deleted file mode 100644
index 4b35c1b0..0000000
--- a/chrome/updater/mac/setup/embed_version.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# Copyright 2020 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import ConfigParser
-import glob
-import optparse
-import os
-import shutil
-import subprocess
-import sys
-
-
-def embed_version(input_file, output_file, version):
-    fin = open(input_file, 'r')
-    fout = open(output_file, 'w')
-    replace_string = 'UPDATE_VERSION=\"' + version + '\"'
-
-    for line in fin:
-        fout.write(line.replace('UPDATE_VERSION=', replace_string))
-
-    fin.close()
-    fout.close()
-
-    os.chmod(output_file, 0755)
-
-
-def parse_options():
-    parser = optparse.OptionParser()
-    parser.add_option('-i', '--input_file', help='Path to the input script.')
-    parser.add_option(
-        '-o',
-        '--output_file',
-        help='Path to where we should output the script')
-    parser.add_option(
-        '-v',
-        '--version',
-        help='Version of the application bundle being built.')
-    options, _ = parser.parse_args()
-
-    if not options.version:
-        parser.error('You must provide a version')
-
-    return options
-
-
-def main(options):
-    embed_version(options.input_file, options.output_file, options.version)
-    return 0
-
-
-if '__main__' == __name__:
-    options = parse_options()
-    sys.exit(main(options))
diff --git a/chrome/updater/mac/update_service_out_of_process.mm b/chrome/updater/mac/update_service_out_of_process.mm
index 7c40a47..4c5e133 100644
--- a/chrome/updater/mac/update_service_out_of_process.mm
+++ b/chrome/updater/mac/update_service_out_of_process.mm
@@ -72,19 +72,6 @@
   [super dealloc];
 }
 
-- (void)getUpdaterVersionWithReply:
-    (void (^_Nonnull)(NSString* _Nullable version))reply {
-  auto errorHandler = ^(NSError* xpcError) {
-    LOG(ERROR) << "XPC connection failed: "
-               << base::SysNSStringToUTF8([xpcError description]);
-    reply(nil);
-  };
-
-  [[_updateCheckXPCConnection.get()
-      remoteObjectProxyWithErrorHandler:errorHandler]
-      getUpdaterVersionWithReply:reply];
-}
-
 - (void)registerForUpdatesWithAppId:(NSString* _Nullable)appId
                           brandCode:(NSString* _Nullable)brandCode
                                 tag:(NSString* _Nullable)tag
@@ -139,19 +126,6 @@
                         reply:reply];
 }
 
-- (void)haltForUpdateToVersion:(NSString* _Nonnull)version
-                         reply:(void (^_Nonnull)(BOOL shouldUpdate))reply {
-  auto errorHandler = ^(NSError* xpcError) {
-    LOG(ERROR) << "XPC connection failed: "
-               << base::SysNSStringToUTF8([xpcError description]);
-    reply(NO);
-  };
-
-  [[_updateCheckXPCConnection remoteObjectProxyWithErrorHandler:errorHandler]
-      haltForUpdateToVersion:version
-                       reply:reply];
-}
-
 @end
 
 // Interface to communicate with the XPC Updater Service.
diff --git a/chrome/updater/test/test_app/update_client_mac.mm b/chrome/updater/test/test_app/update_client_mac.mm
index 183b16c..0bba6a5 100644
--- a/chrome/updater/test/test_app/update_client_mac.mm
+++ b/chrome/updater/test/test_app/update_client_mac.mm
@@ -70,17 +70,6 @@
   return self;
 }
 
-- (void)getUpdaterVersionWithReply:(void (^_Nonnull)(NSString* version))reply {
-  auto errorHandler = ^(NSError* xpcError) {
-    LOG(ERROR) << "XPC connection failed: "
-               << base::SysNSStringToUTF8([xpcError description]);
-    reply(nil);
-  };
-
-  [[_xpcConnection.get() remoteObjectProxyWithErrorHandler:errorHandler]
-      getUpdaterVersionWithReply:reply];
-}
-
 - (void)registerForUpdatesWithAppId:(NSString* _Nullable)appId
                           brandCode:(NSString* _Nullable)brandCode
                                 tag:(NSString* _Nullable)tag
@@ -125,19 +114,6 @@
                         reply:reply];
 }
 
-- (void)haltForUpdateToVersion:(NSString* _Nonnull)version
-                         reply:(void (^_Nonnull)(BOOL shouldUpdate))reply {
-  auto errorHandler = ^(NSError* xpcError) {
-    LOG(ERROR) << "XPC connection failed: "
-               << base::SysNSStringToUTF8([xpcError description]);
-    reply(NO);
-  };
-
-  [[_xpcConnection remoteObjectProxyWithErrorHandler:errorHandler]
-      haltForUpdateToVersion:version
-                       reply:reply];
-}
-
 - (BOOL)CanDialIPC {
   return true;
 }
diff --git a/chromecast/bindings/BUILD.gn b/chromecast/bindings/BUILD.gn
index 31233e7..c230e2b 100644
--- a/chromecast/bindings/BUILD.gn
+++ b/chromecast/bindings/BUILD.gn
@@ -9,7 +9,11 @@
 }
 
 source_set("named_message_port_connector_resources") {
-  data = [ "resources/named_message_port_connector.js" ]
+  data = [
+    "resources/named_message_port_connector.js",
+    "${target_gen_dir}/bindings_resources.pak",
+  ]
+  deps = [ ":bindings_resources" ]
 }
 
 grit("bindings_resources") {
diff --git a/chromecast/browser/cast_display_configurator.cc b/chromecast/browser/cast_display_configurator.cc
index 9f9e1b9..dfee32f 100644
--- a/chromecast/browser/cast_display_configurator.cc
+++ b/chromecast/browser/cast_display_configurator.cc
@@ -19,7 +19,6 @@
 #include "chromecast/graphics/cast_screen.h"
 #include "chromecast/public/graphics_properties_shlib.h"
 #include "ui/display/types/display_snapshot.h"
-#include "ui/display/types/native_display_delegate.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/ozone/public/ozone_platform.h"
 
@@ -127,26 +126,24 @@
       false /* force_initial_configure */));
 }
 
-void CastDisplayConfigurator::EnableDisplay() {
+void CastDisplayConfigurator::EnableDisplay(
+    display::ConfigureCallback callback) {
   if (!delegate_ || !display_)
     return;
 
   display::DisplayConfigurationParams display_config_params(
       display_->display_id(), gfx::Point(), display_->native_mode());
-  delegate_->Configure(display_config_params, base::BindOnce([](bool status) {
-                         LOG_IF(FATAL, !status) << "Failed to enable display";
-                       }));
+  delegate_->Configure(display_config_params, std::move(callback));
 }
 
-void CastDisplayConfigurator::DisableDisplay() {
+void CastDisplayConfigurator::DisableDisplay(
+    display::ConfigureCallback callback) {
   if (!delegate_ || !display_)
     return;
 
   display::DisplayConfigurationParams display_config_params(
       display_->display_id(), gfx::Point(), nullptr);
-  delegate_->Configure(display_config_params, base::BindOnce([](bool status) {
-                         LOG_IF(ERROR, !status) << "Failed to disable display";
-                       }));
+  delegate_->Configure(display_config_params, std::move(callback));
 }
 
 void CastDisplayConfigurator::ConfigureDisplayFromCommandLine() {
diff --git a/chromecast/browser/cast_display_configurator.h b/chromecast/browser/cast_display_configurator.h
index 46a62f2..cdb14e4a 100644
--- a/chromecast/browser/cast_display_configurator.h
+++ b/chromecast/browser/cast_display_configurator.h
@@ -11,12 +11,12 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "ui/display/display.h"
+#include "ui/display/types/native_display_delegate.h"
 #include "ui/display/types/native_display_observer.h"
 
 namespace display {
 class DisplayMode;
 class DisplaySnapshot;
-class NativeDisplayDelegate;
 struct GammaRampRGBEntry;
 }  // namespace display
 
@@ -45,8 +45,8 @@
   void OnConfigurationChanged() override;
   void OnDisplaySnapshotsInvalidated() override {}
 
-  void EnableDisplay();
-  void DisableDisplay();
+  void EnableDisplay(display::ConfigureCallback callback);
+  void DisableDisplay(display::ConfigureCallback callback);
 
   void ConfigureDisplayFromCommandLine();
   void SetColorMatrix(const std::vector<float>& color_matrix);
diff --git a/chromecast/media/cma/base/demuxer_stream_adapter_unittest.cc b/chromecast/media/cma/base/demuxer_stream_adapter_unittest.cc
index 3e4d424..c62d4cb 100644
--- a/chromecast/media/cma/base/demuxer_stream_adapter_unittest.cc
+++ b/chromecast/media/cma/base/demuxer_stream_adapter_unittest.cc
@@ -149,7 +149,6 @@
 
 void DemuxerStreamAdapterTest::OnFlushCompleted() {
   ASSERT_EQ(frame_received_count_, total_expected_frames_);
-  ASSERT_FALSE(demuxer_stream_->IsReadPending());
   base::RunLoop::QuitCurrentWhenIdleDeprecated();
 }
 
diff --git a/chromecast/media/cma/base/demuxer_stream_for_test.cc b/chromecast/media/cma/base/demuxer_stream_for_test.cc
index f1c11a720..73240257 100644
--- a/chromecast/media/cma/base/demuxer_stream_for_test.cc
+++ b/chromecast/media/cma/base/demuxer_stream_for_test.cc
@@ -18,8 +18,7 @@
       cycle_count_(cycle_count),
       delayed_frame_count_(delayed_frame_count),
       config_idx_(config_idx),
-      frame_count_(0),
-      has_pending_read_(false) {
+      frame_count_(0) {
   DCHECK_LE(delayed_frame_count, cycle_count);
 }
 
@@ -27,10 +26,8 @@
 }
 
 void DemuxerStreamForTest::Read(ReadCB read_cb) {
-  has_pending_read_ = true;
   if (!config_idx_.empty() && config_idx_.front() == frame_count_) {
     config_idx_.pop_front();
-    has_pending_read_ = false;
     std::move(read_cb).Run(kConfigChanged,
                            scoped_refptr<::media::DecoderBuffer>());
     return;
@@ -72,13 +69,7 @@
   return true;
 }
 
-bool DemuxerStreamForTest::IsReadPending() const {
-  return has_pending_read_;
-}
-
 void DemuxerStreamForTest::DoRead(ReadCB read_cb) {
-  has_pending_read_ = false;
-
   if (total_frame_count_ != -1 && frame_count_ >= total_frame_count_) {
     // End of stream
     std::move(read_cb).Run(kOk, ::media::DecoderBuffer::CreateEOSBuffer());
diff --git a/chromecast/media/cma/base/demuxer_stream_for_test.h b/chromecast/media/cma/base/demuxer_stream_for_test.h
index f380b28..64f862c 100644
--- a/chromecast/media/cma/base/demuxer_stream_for_test.h
+++ b/chromecast/media/cma/base/demuxer_stream_for_test.h
@@ -46,7 +46,6 @@
   ::media::VideoDecoderConfig video_decoder_config() override;
   Type type() const override;
   bool SupportsConfigChanges() override;
-  bool IsReadPending() const override;
 
   // Frame duration
   static const int kDemuxerStreamForTestFrameDuration = 40;
@@ -63,8 +62,6 @@
   // Number of frames sent so far.
   int frame_count_;
 
-  bool has_pending_read_;
-
   DISALLOW_COPY_AND_ASSIGN(DemuxerStreamForTest);
 };
 
diff --git a/chromeos/BUILD.gn b/chromeos/BUILD.gn
index b251755..5372723 100644
--- a/chromeos/BUILD.gn
+++ b/chromeos/BUILD.gn
@@ -73,8 +73,6 @@
     "printing/printer_translator.h",
     "printing/uri.cc",
     "printing/uri.h",
-    "printing/uri_components.cc",
-    "printing/uri_components.h",
     "printing/uri_impl.cc",
     "printing/uri_impl.h",
     "printing/usb_printer_id.cc",
@@ -284,32 +282,6 @@
   tast_test("chrome_all_tast_tests") {
     # To disable a specific test, add it the following list and cite a bug.
     tast_disabled_tests = [
-      # All crostini tests. See crbug.com/1100710.
-      "crostini.AudioSanity.artifact",
-      "crostini.CopyPaste.wayland_to_wayland_artifact",
-      "crostini.CopyPaste.wayland_to_x11_artifact",
-      "crostini.DisplayDensity.wayland_artifact",
-      "crostini.DisplayDensity.x11_artifact",
-      "crostini.GPUEnabled.artifact_gpu",
-      "crostini.GPUEnabled.artifact_sw",
-      "crostini.LaunchTerminal.artifact",
-      "crostini.PackageInfo.artifact",
-      "crostini.PackageInstallUninstall.artifact",
-      "crostini.Restart.artifact",
-      "crostini.Sanity.artifact",
-      "crostini.SecureCopyPaste.copy_wayland_artifact",
-      "crostini.SecureCopyPaste.copy_x11_artifact",
-      "crostini.SecureCopyPaste.paste_wayland_artifact",
-      "crostini.SecureCopyPaste.paste_x11_artifact",
-      "crostini.Toolkit.gtk3_wayland",
-      "crostini.Toolkit.gtk3_x11",
-      "crostini.Toolkit.qt5",
-      "crostini.Toolkit.tkinter",
-      "crostini.UninstallInvalidApp.artifact",
-      "crostini.VerifyAppWayland.artifact",
-      "crostini.VerifyAppX11.artifact",
-      "crostini.Webserver.artifact",
-
       # crbug.com/1097630
       "security.OpenFDs",
 
@@ -335,6 +307,10 @@
 
       # crbug.com/1099695
       "platform.Drivefs",
+
+      # crbug.com/1105335
+      "graphics.VAAPIUnittest.jpeg_decoder",
+      "graphics.VAAPIUnittest.common",
     ]
   }
 
diff --git a/chromeos/attestation/BUILD.gn b/chromeos/attestation/BUILD.gn
index c1cada8..6ae6f961 100644
--- a/chromeos/attestation/BUILD.gn
+++ b/chromeos/attestation/BUILD.gn
@@ -22,7 +22,10 @@
   sources = [
     "attestation_flow.cc",
     "attestation_flow.h",
+    "attestation_flow_integrated.cc",
+    "attestation_flow_integrated.h",
     "attestation_flow_utils.cc",
+    "attestation_flow_utils.h",
   ]
 }
 
@@ -49,11 +52,13 @@
     "//chromeos/cryptohome:test_support",
     "//chromeos/dbus:test_support",
     "//chromeos/dbus/attestation",
+    "//chromeos/dbus/attestation:attestation_proto",
     "//components/account_id",
     "//testing/gmock",
     "//testing/gtest",
   ]
   sources = [
+    "attestation_flow_integrated_unittest.cc",
     "attestation_flow_unittest.cc",
     "attestation_flow_utils_unittest.cc",
   ]
diff --git a/chromeos/attestation/attestation_flow_integrated.cc b/chromeos/attestation/attestation_flow_integrated.cc
new file mode 100644
index 0000000..247090f
--- /dev/null
+++ b/chromeos/attestation/attestation_flow_integrated.cc
@@ -0,0 +1,190 @@
+// Copyright 2020 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 "chromeos/attestation/attestation_flow_integrated.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/optional.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/timer/timer.h"
+#include "chromeos/attestation/attestation_flow_utils.h"
+#include "chromeos/cryptohome/async_method_caller.h"
+#include "chromeos/cryptohome/cryptohome_parameters.h"
+#include "chromeos/dbus/attestation/attestation_client.h"
+#include "chromeos/dbus/attestation/interface.pb.h"
+#include "components/account_id/account_id.h"
+
+namespace chromeos {
+namespace attestation {
+
+namespace {
+
+// A reasonable timeout that gives enough time for attestation to be ready,
+// yet does not make the caller wait too long.
+constexpr base::TimeDelta kReadyTimeout = base::TimeDelta::FromSeconds(60);
+
+// Delay before checking again whether the TPM has been prepared for
+// attestation.
+constexpr base::TimeDelta kRetryDelay = base::TimeDelta::FromMilliseconds(300);
+
+// Default ACA type when not specified during construction.
+constexpr ::attestation::ACAType kDefaultAcaType =
+    ::attestation::ACAType::DEFAULT_ACA;
+
+bool IsPreparedWith(const ::attestation::GetEnrollmentPreparationsReply& reply,
+                    ::attestation::ACAType aca_type) {
+  for (const auto& preparation : reply.enrollment_preparations()) {
+    if (preparation.first == aca_type) {
+      return preparation.second;
+    }
+  }
+  return false;
+}
+
+base::Optional<::attestation::CertificateProfile> ProfileToAttestationProtoEnum(
+    AttestationCertificateProfile p) {
+  switch (p) {
+    case PROFILE_ENTERPRISE_MACHINE_CERTIFICATE:
+      return ::attestation::CertificateProfile::ENTERPRISE_MACHINE_CERTIFICATE;
+    case PROFILE_ENTERPRISE_USER_CERTIFICATE:
+      return ::attestation::CertificateProfile::ENTERPRISE_USER_CERTIFICATE;
+    case PROFILE_CONTENT_PROTECTION_CERTIFICATE:
+      return ::attestation::CertificateProfile::CONTENT_PROTECTION_CERTIFICATE;
+    case PROFILE_ENTERPRISE_ENROLLMENT_CERTIFICATE:
+      return ::attestation::CertificateProfile::
+          ENTERPRISE_ENROLLMENT_CERTIFICATE;
+    default:
+      return {};
+  }
+}
+
+}  // namespace
+
+AttestationFlowIntegrated::AttestationFlowIntegrated()
+    : AttestationFlowIntegrated(kDefaultAcaType) {}
+
+// This constructor passes |nullptr|s to the base class
+// |AttestationFlow| because we don't use cryptohome client and server
+// proxy in |AttestationFlowIntegrated|.
+//
+// TOOD(b/158955123): Remove this transitional state along with the removal of
+// |AttestationFlow|.
+AttestationFlowIntegrated::AttestationFlowIntegrated(
+    ::attestation::ACAType aca_type)
+    : AttestationFlow(nullptr, nullptr, nullptr),
+      aca_type_(aca_type),
+      attestation_client_(AttestationClient::Get()),
+      ready_timeout_(kReadyTimeout),
+      retry_delay_(kRetryDelay) {}
+
+AttestationFlowIntegrated::~AttestationFlowIntegrated() = default;
+
+void AttestationFlowIntegrated::GetCertificate(
+    AttestationCertificateProfile certificate_profile,
+    const AccountId& account_id,
+    const std::string& request_origin,
+    bool force_new_key,
+    const std::string& key_name,
+    CertificateCallback callback) {
+  const std::string attestation_key_name =
+      !key_name.empty()
+          ? key_name
+          : GetKeyNameForProfile(certificate_profile, request_origin);
+
+  base::OnceCallback<void(bool)> start_certificate_request = base::BindOnce(
+      &AttestationFlowIntegrated::StartCertificateRequest,
+      weak_factory_.GetWeakPtr(), certificate_profile, account_id,
+      request_origin, force_new_key, attestation_key_name, std::move(callback));
+
+  base::TimeTicks end_time = base::TimeTicks::Now() + ready_timeout_;
+  WaitForAttestationPrepared(end_time, std::move(start_certificate_request));
+}
+
+void AttestationFlowIntegrated::WaitForAttestationPrepared(
+    base::TimeTicks end_time,
+    base::OnceCallback<void(bool)> callback) {
+  ::attestation::GetEnrollmentPreparationsRequest request;
+  request.set_aca_type(aca_type_);
+  attestation_client_->GetEnrollmentPreparations(
+      request, base::BindOnce(
+                   &AttestationFlowIntegrated::OnPreparedCheckComplete,
+                   weak_factory_.GetWeakPtr(), end_time, std::move(callback)));
+}
+
+void AttestationFlowIntegrated::OnPreparedCheckComplete(
+    base::TimeTicks end_time,
+    base::OnceCallback<void(bool)> callback,
+    const ::attestation::GetEnrollmentPreparationsReply& reply) {
+  if (reply.status() == ::attestation::STATUS_SUCCESS &&
+      IsPreparedWith(reply, aca_type_)) {
+    std::move(callback).Run(true);
+    return;
+  }
+
+  if (base::TimeTicks::Now() < end_time) {
+    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE,
+        base::BindOnce(&AttestationFlowIntegrated::WaitForAttestationPrepared,
+                       weak_factory_.GetWeakPtr(), end_time,
+                       std::move(callback)),
+        retry_delay_);
+    return;
+  }
+  std::move(callback).Run(false);
+}
+
+void AttestationFlowIntegrated::StartCertificateRequest(
+    AttestationCertificateProfile certificate_profile,
+    const AccountId& account_id,
+    const std::string& request_origin,
+    bool generate_new_key,
+    const std::string& key_name,
+    CertificateCallback callback,
+    bool is_prepared) {
+  if (!is_prepared) {
+    LOG(ERROR) << __func__ << ": Not prepared.";
+    std::move(callback).Run(ATTESTATION_UNSPECIFIED_FAILURE, "");
+    return;
+  }
+
+  ::attestation::GetCertificateRequest request;
+  request.set_aca_type(aca_type_);
+  base::Optional<::attestation::CertificateProfile> profile_attestation_enum =
+      ProfileToAttestationProtoEnum(certificate_profile);
+  if (!profile_attestation_enum) {
+    LOG(ERROR) << __func__ << ": Unexpected profile value: "
+               << static_cast<int>(certificate_profile);
+    std::move(callback).Run(ATTESTATION_UNSPECIFIED_FAILURE, "");
+    return;
+  }
+
+  request.set_certificate_profile(*profile_attestation_enum);
+  request.set_request_origin(request_origin);
+  request.set_username(cryptohome::Identification(account_id).id());
+  request.set_key_label(key_name);
+  request.set_shall_trigger_enrollment(true);
+  request.set_forced(generate_new_key);
+
+  attestation_client_->GetCertificate(
+      request, base::BindOnce(&AttestationFlowIntegrated::OnCertRequestFinished,
+                              weak_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void AttestationFlowIntegrated::OnCertRequestFinished(
+    CertificateCallback callback,
+    const ::attestation::GetCertificateReply& reply) {
+  if (reply.status() == ::attestation::STATUS_SUCCESS) {
+    std::move(callback).Run(ATTESTATION_SUCCESS, reply.certificate());
+  } else {
+    std::move(callback).Run(ATTESTATION_UNSPECIFIED_FAILURE, "");
+  }
+}
+
+}  // namespace attestation
+}  // namespace chromeos
diff --git a/chromeos/attestation/attestation_flow_integrated.h b/chromeos/attestation/attestation_flow_integrated.h
new file mode 100644
index 0000000..4e5b403
--- /dev/null
+++ b/chromeos/attestation/attestation_flow_integrated.h
@@ -0,0 +1,156 @@
+// Copyright (c) 2020 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 CHROMEOS_ATTESTATION_ATTESTATION_FLOW_INTEGRATED_H_
+#define CHROMEOS_ATTESTATION_ATTESTATION_FLOW_INTEGRATED_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/component_export.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "chromeos/attestation/attestation_flow.h"
+#include "chromeos/dbus/attestation/interface.pb.h"
+#include "chromeos/dbus/constants/attestation_constants.h"
+#include "chromeos/dbus/dbus_method_call_status.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+class AccountId;
+
+namespace chromeos {
+
+class AttestationClient;
+
+namespace attestation {
+
+// Implements the message flow for Chrome OS attestation tasks by checking the
+// enrollment preparatoins and then send certificate request. This is meant to
+// replace its base class, |AttestationFlow|; after all consumptions switch to
+// this class the base class will be converted to pure virtual interface or even
+// removed.
+//
+// Note: This class is not thread safe.
+class COMPONENT_EXPORT(CHROMEOS_ATTESTATION) AttestationFlowIntegrated
+    : public AttestationFlow {
+ public:
+  AttestationFlowIntegrated();
+  explicit AttestationFlowIntegrated(::attestation::ACAType aca_type);
+  ~AttestationFlowIntegrated() override;
+
+  // Not copyable or movable.
+  AttestationFlowIntegrated(const AttestationFlowIntegrated&) = delete;
+  AttestationFlowIntegrated(AttestationFlowIntegrated&&) = delete;
+  AttestationFlowIntegrated& operator=(const AttestationFlowIntegrated&) =
+      delete;
+  AttestationFlowIntegrated& operator=(AttestationFlowIntegrated&&) = delete;
+
+  // Sets the timeout for attestation to be ready.
+  void set_ready_timeout_for_testing(base::TimeDelta ready_timeout) {
+    ready_timeout_ = ready_timeout;
+  }
+
+  // Sets the retry delay.
+  void set_retry_delay_for_testing(base::TimeDelta retry_delay) {
+    retry_delay_ = retry_delay;
+  }
+
+  // Gets an attestation certificate for a hardware-protected key.  If a key for
+  // the given profile does not exist, it will be generated and a certificate
+  // request will be made to the Chrome OS Privacy CA to issue a certificate for
+  // the key.  If the key already exists and |force_new_key| is false, the
+  // existing certificate is returned.
+  //
+  // Parameters
+  //   certificate_profile - Specifies what kind of certificate should be
+  //                         requested from the CA.
+  //   account_id - Identifies the currently active user. This is ignored when
+  //                using the enterprise machine cert profile.
+  //   request_origin - For content protection profiles, certificate requests
+  //                    are origin-specific.  This string must uniquely identify
+  //                    the origin of the request.
+  //   force_new_key - If set to true, a new key will be generated even if a key
+  //                   already exists for the profile.  The new key will replace
+  //                   the existing key on success.
+  //   key_name - The name of the key. If left empty, a default name derived
+  //              from the |certificate_profile| and |account_id| will be used.
+  //   callback - A callback which will be called when the operation completes.
+  //              On success |result| will be true and |data| will contain the
+  //              PCA-issued certificate chain in PEM format.
+  void GetCertificate(AttestationCertificateProfile certificate_profile,
+                      const AccountId& account_id,
+                      const std::string& request_origin,
+                      bool force_new_key,
+                      const std::string& key_name,
+                      CertificateCallback callback) override;
+
+ private:
+  // Asynchronously waits for attestation to be ready and start enrollment once
+  // it is. If attestation is not ready by the time the flow's timeout is
+  // reached, fail.
+  //
+  // Parameters
+  //   end_time - Time after which preparation should time out.
+  //   callback - Called with the success or failure of the enrollment.
+  void WaitForAttestationPrepared(base::TimeTicks end_time,
+                                  base::OnceCallback<void(bool)> callback);
+
+  // Handles the result of a call to TpmAttestationIsPrepared. Starts enrollment
+  // on success and retries after |retry_delay_| if not.
+  //
+  // Parameters
+  //   end_time - Time after which preparation should time out.
+  //   callback - Called with the success or failure of the enrollment.
+  //   reply - Reply from the attestation service.
+  void OnPreparedCheckComplete(
+      base::TimeTicks end_time,
+      base::OnceCallback<void(bool)> callback,
+      const ::attestation::GetEnrollmentPreparationsReply& reply);
+
+  // Asynchronously initiates the certificate request flow.  Attestation
+  // enrollment must complete successfully before this operation can succeed.
+  //
+  // Parameters
+  //   certificate_profile - Specifies what kind of certificate should be
+  //                         requested from the CA.
+  //   account_id - Identifies the active user.
+  //   request_origin - An identifier for the origin of this request.
+  //   generate_new_key - If set to true a new key is generated.
+  //   key_name - The name of the key. If left empty, a default name derived
+  //              from the |certificate_profile| and |account_id| will be used.
+  //   callback - Called when the operation completes.
+  //   is_prepared - Success or failure of the enrollment preparation phase.
+  void StartCertificateRequest(
+      const AttestationCertificateProfile certificate_profile,
+      const AccountId& account_id,
+      const std::string& request_origin,
+      bool generate_new_key,
+      const std::string& key_name,
+      CertificateCallback callback,
+      bool is_prepared);
+
+  // Called after cryptohome finishes processing of a certificate request.
+  //
+  // Parameters
+  //   callback - Called when the operation completes.
+  //   reply - The reply from |attestation_client_|.
+  void OnCertRequestFinished(CertificateCallback callback,
+                             const ::attestation::GetCertificateReply& reply);
+
+  ::attestation::ACAType aca_type_;
+  AttestationClient* attestation_client_;
+
+  base::TimeDelta ready_timeout_;
+  base::TimeDelta retry_delay_;
+
+  base::WeakPtrFactory<AttestationFlowIntegrated> weak_factory_{this};
+};
+
+}  // namespace attestation
+}  // namespace chromeos
+
+#endif  // CHROMEOS_ATTESTATION_ATTESTATION_FLOW_INTEGRATED_H_
diff --git a/chromeos/attestation/attestation_flow_integrated_unittest.cc b/chromeos/attestation/attestation_flow_integrated_unittest.cc
new file mode 100644
index 0000000..ed52aaa2
--- /dev/null
+++ b/chromeos/attestation/attestation_flow_integrated_unittest.cc
@@ -0,0 +1,343 @@
+// Copyright 2020 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 "chromeos/attestation/attestation_flow_integrated.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/optional.h"
+#include "base/run_loop.h"
+#include "base/test/mock_callback.h"
+#include "base/test/task_environment.h"
+#include "base/timer/timer.h"
+#include "chromeos/attestation/attestation_flow_utils.h"
+#include "chromeos/cryptohome/cryptohome_parameters.h"
+#include "chromeos/dbus/attestation/attestation_client.h"
+#include "chromeos/dbus/attestation/interface.pb.h"
+#include "chromeos/dbus/constants/attestation_constants.h"
+#include "chromeos/dbus/cryptohome/fake_cryptohome_client.h"
+#include "components/account_id/account_id.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+namespace attestation {
+
+namespace {
+
+using testing::_;
+using testing::SaveArg;
+
+}  // namespace
+
+class AttestationFlowIntegratedTest : public testing::Test {
+ public:
+  AttestationFlowIntegratedTest() {
+    chromeos::AttestationClient::InitializeFake();
+  }
+  ~AttestationFlowIntegratedTest() override {
+    chromeos::AttestationClient::Shutdown();
+  }
+  void QuitRunLoopCertificateCallback(
+      AttestationFlowIntegrated::CertificateCallback callback,
+      AttestationStatus status,
+      const std::string& cert) {
+    LOG(WARNING) << "Quitting run loop.";
+    run_loop_->Quit();
+    if (callback)
+      std::move(callback).Run(status, cert);
+  }
+
+ protected:
+  void AllowlistCertificateRequest(
+      ::attestation::ACAType aca_type,
+      ::attestation::GetCertificateRequest request) {
+    request.set_aca_type(aca_type);
+    if (request.key_label().empty()) {
+      request.set_key_label(
+          GetKeyNameForProfile(static_cast<AttestationCertificateProfile>(
+                                   request.certificate_profile()),
+                               request.request_origin()));
+    }
+    chromeos::AttestationClient::Get()
+        ->GetTestInterface()
+        ->AllowlistCertificateRequest(request);
+  }
+  void Run() {
+    base::RunLoop run_loop;
+    run_loop_ = &run_loop;
+    run_loop_->Run();
+  }
+
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  base::RunLoop* run_loop_;
+};
+
+TEST_F(AttestationFlowIntegratedTest, GetCertificate) {
+  chromeos::AttestationClient::Get()
+      ->GetTestInterface()
+      ->ConfigureEnrollmentPreparations(true);
+
+  ::attestation::GetCertificateRequest request;
+  request.set_certificate_profile(
+      ::attestation::CertificateProfile::ENTERPRISE_USER_CERTIFICATE);
+  request.set_username("username@email.com");
+  request.set_key_label("label");
+  request.set_request_origin("origin");
+
+  AllowlistCertificateRequest(::attestation::ACAType::DEFAULT_ACA, request);
+
+  base::MockCallback<AttestationFlowIntegrated::CertificateCallback> callback1,
+      callback2, callback3;
+  std::string certificate1, certificate2, certificate3;
+  EXPECT_CALL(callback1, Run(AttestationStatus::ATTESTATION_SUCCESS, _))
+      .WillOnce(SaveArg<1>(&certificate1));
+  EXPECT_CALL(callback2, Run(AttestationStatus::ATTESTATION_SUCCESS, _))
+      .WillOnce(SaveArg<1>(&certificate2));
+  EXPECT_CALL(callback3, Run(AttestationStatus::ATTESTATION_SUCCESS, _))
+      .WillOnce(SaveArg<1>(&certificate3));
+
+  AttestationFlowIntegrated flow;
+  flow.GetCertificate(
+      static_cast<AttestationCertificateProfile>(request.certificate_profile()),
+      AccountId::FromUserEmail(request.username()), request.request_origin(),
+      /*generate_new_key=*/true, request.key_label(), callback1.Get());
+  flow.GetCertificate(
+      static_cast<AttestationCertificateProfile>(request.certificate_profile()),
+      AccountId::FromUserEmail(request.username()), request.request_origin(),
+      /*generate_new_key=*/true, request.key_label(), callback2.Get());
+  flow.GetCertificate(
+      static_cast<AttestationCertificateProfile>(request.certificate_profile()),
+      AccountId::FromUserEmail(request.username()), request.request_origin(),
+      /*generate_new_key=*/false, request.key_label(),
+      base::BindOnce(
+          &AttestationFlowIntegratedTest::QuitRunLoopCertificateCallback,
+          base::Unretained(this), callback3.Get()));
+  Run();
+  EXPECT_FALSE(certificate1.empty());
+  EXPECT_FALSE(certificate2.empty());
+  EXPECT_NE(certificate1, certificate2);
+  EXPECT_EQ(certificate2, certificate3);
+}
+
+TEST_F(AttestationFlowIntegratedTest, GetCertificateFailed) {
+  chromeos::AttestationClient::Get()
+      ->GetTestInterface()
+      ->ConfigureEnrollmentPreparations(true);
+
+  ::attestation::GetCertificateRequest request;
+  request.set_certificate_profile(
+      ::attestation::CertificateProfile::ENTERPRISE_USER_CERTIFICATE);
+  request.set_username("username@email.com");
+  request.set_key_label("label");
+  request.set_request_origin("origin");
+
+  base::MockCallback<AttestationFlowIntegrated::CertificateCallback> callback;
+  AttestationStatus status = AttestationStatus::ATTESTATION_SUCCESS;
+  EXPECT_CALL(callback, Run(_, _)).WillOnce(SaveArg<0>(&status));
+
+  AttestationFlowIntegrated flow;
+  flow.GetCertificate(
+      static_cast<AttestationCertificateProfile>(request.certificate_profile()),
+      AccountId::FromUserEmail(request.username()), request.request_origin(),
+      /*generate_new_key=*/true, request.key_label(),
+      base::BindOnce(
+          &AttestationFlowIntegratedTest::QuitRunLoopCertificateCallback,
+          base::Unretained(this), callback.Get()));
+  Run();
+  EXPECT_NE(status, AttestationStatus::ATTESTATION_SUCCESS);
+}
+
+TEST_F(AttestationFlowIntegratedTest, GetCertificateFailedInvalidProfile) {
+  chromeos::AttestationClient::Get()
+      ->GetTestInterface()
+      ->ConfigureEnrollmentPreparations(true);
+
+  ::attestation::GetCertificateRequest request;
+  request.set_certificate_profile(
+      ::attestation::CertificateProfile::CAST_CERTIFICATE);
+  request.set_username("username@email.com");
+  request.set_key_label("label");
+  request.set_request_origin("origin");
+
+  base::MockCallback<AttestationFlowIntegrated::CertificateCallback> callback;
+  AttestationStatus status = AttestationStatus::ATTESTATION_SUCCESS;
+  EXPECT_CALL(callback, Run(_, _)).WillOnce(SaveArg<0>(&status));
+
+  AttestationFlowIntegrated flow;
+  flow.GetCertificate(
+      static_cast<AttestationCertificateProfile>(request.certificate_profile()),
+      AccountId::FromUserEmail(request.username()), request.request_origin(),
+      /*generate_new_key=*/true, request.key_label(),
+      base::BindOnce(
+          &AttestationFlowIntegratedTest::QuitRunLoopCertificateCallback,
+          base::Unretained(this), callback.Get()));
+  Run();
+  EXPECT_NE(status, AttestationStatus::ATTESTATION_SUCCESS);
+}
+
+TEST_F(AttestationFlowIntegratedTest, GetCertificateAttestationNotPrepared) {
+  chromeos::AttestationClient::Get()
+      ->GetTestInterface()
+      ->ConfigureEnrollmentPreparationsSequence({false, true});
+
+  ::attestation::GetCertificateRequest request;
+  request.set_certificate_profile(
+      ::attestation::CertificateProfile::ENTERPRISE_USER_CERTIFICATE);
+  request.set_username("username@email.com");
+  request.set_key_label("label");
+  request.set_request_origin("origin");
+
+  AllowlistCertificateRequest(::attestation::ACAType::DEFAULT_ACA, request);
+
+  base::MockCallback<AttestationFlowIntegrated::CertificateCallback> callback;
+  std::string certificate;
+  EXPECT_CALL(callback, Run(AttestationStatus::ATTESTATION_SUCCESS, _))
+      .WillOnce(SaveArg<1>(&certificate));
+
+  AttestationFlowIntegrated flow;
+  flow.set_retry_delay_for_testing(base::TimeDelta::FromMilliseconds(10));
+  flow.GetCertificate(
+      static_cast<AttestationCertificateProfile>(request.certificate_profile()),
+      AccountId::FromUserEmail(request.username()), request.request_origin(),
+      /*generate_new_key=*/true, request.key_label(),
+      base::BindOnce(
+          &AttestationFlowIntegratedTest::QuitRunLoopCertificateCallback,
+          base::Unretained(this), callback.Get()));
+  Run();
+  EXPECT_FALSE(certificate.empty());
+}
+
+TEST_F(AttestationFlowIntegratedTest, GetCertificateAttestationNeverPrepared) {
+  chromeos::AttestationClient::Get()
+      ->GetTestInterface()
+      ->ConfigureEnrollmentPreparations(false);
+
+  ::attestation::GetCertificateRequest request;
+  request.set_certificate_profile(
+      ::attestation::CertificateProfile::ENTERPRISE_USER_CERTIFICATE);
+  request.set_username("username@email.com");
+  request.set_key_label("label");
+  request.set_request_origin("origin");
+
+  AllowlistCertificateRequest(::attestation::ACAType::DEFAULT_ACA, request);
+
+  base::MockCallback<AttestationFlowIntegrated::CertificateCallback> callback;
+  AttestationStatus status = AttestationStatus::ATTESTATION_SUCCESS;
+  EXPECT_CALL(callback, Run(_, _)).WillOnce(SaveArg<0>(&status));
+
+  AttestationFlowIntegrated flow;
+  flow.set_ready_timeout_for_testing(base::TimeDelta::FromMilliseconds(10));
+  flow.set_retry_delay_for_testing(base::TimeDelta::FromMilliseconds(3));
+  flow.GetCertificate(
+      static_cast<AttestationCertificateProfile>(request.certificate_profile()),
+      AccountId::FromUserEmail(request.username()), request.request_origin(),
+      /*generate_new_key=*/true, request.key_label(),
+      base::BindOnce(
+          &AttestationFlowIntegratedTest::QuitRunLoopCertificateCallback,
+          base::Unretained(this), callback.Get()));
+  Run();
+  EXPECT_NE(status, AttestationStatus::ATTESTATION_SUCCESS);
+}
+
+TEST_F(AttestationFlowIntegratedTest, GetCertificateAttestationTestAca) {
+  chromeos::AttestationClient::Get()
+      ->GetTestInterface()
+      ->ConfigureEnrollmentPreparations(true);
+
+  ::attestation::GetCertificateRequest request;
+  request.set_certificate_profile(
+      ::attestation::CertificateProfile::ENTERPRISE_USER_CERTIFICATE);
+  request.set_username("username@email.com");
+  request.set_key_label("label");
+  request.set_request_origin("origin");
+
+  AllowlistCertificateRequest(::attestation::ACAType::TEST_ACA, request);
+
+  base::MockCallback<AttestationFlowIntegrated::CertificateCallback> callback;
+  std::string certificate;
+  EXPECT_CALL(callback, Run(AttestationStatus::ATTESTATION_SUCCESS, _))
+      .WillOnce(SaveArg<1>(&certificate));
+
+  AttestationFlowIntegrated flow(::attestation::ACAType::TEST_ACA);
+  flow.GetCertificate(
+      static_cast<AttestationCertificateProfile>(request.certificate_profile()),
+      AccountId::FromUserEmail(request.username()), request.request_origin(),
+      /*generate_new_key=*/true, request.key_label(),
+      base::BindOnce(
+          &AttestationFlowIntegratedTest::QuitRunLoopCertificateCallback,
+          base::Unretained(this), callback.Get()));
+  Run();
+  EXPECT_FALSE(certificate.empty());
+}
+
+TEST_F(AttestationFlowIntegratedTest, GetCertificateAttestationEmptyAccountId) {
+  chromeos::AttestationClient::Get()
+      ->GetTestInterface()
+      ->ConfigureEnrollmentPreparations(true);
+
+  ::attestation::GetCertificateRequest request;
+  request.set_certificate_profile(
+      ::attestation::CertificateProfile::ENTERPRISE_MACHINE_CERTIFICATE);
+  request.set_username("");
+  request.set_key_label("label");
+  request.set_request_origin("origin");
+
+  AllowlistCertificateRequest(::attestation::ACAType::DEFAULT_ACA, request);
+
+  base::MockCallback<AttestationFlowIntegrated::CertificateCallback> callback;
+  std::string certificate;
+  EXPECT_CALL(callback, Run(AttestationStatus::ATTESTATION_SUCCESS, _))
+      .WillOnce(SaveArg<1>(&certificate));
+
+  AttestationFlowIntegrated flow;
+  flow.GetCertificate(
+      static_cast<AttestationCertificateProfile>(request.certificate_profile()),
+      EmptyAccountId(), request.request_origin(),
+      /*generate_new_key=*/true, request.key_label(),
+      base::BindOnce(
+          &AttestationFlowIntegratedTest::QuitRunLoopCertificateCallback,
+          base::Unretained(this), callback.Get()));
+  Run();
+  EXPECT_FALSE(certificate.empty());
+}
+
+TEST_F(AttestationFlowIntegratedTest,
+       GetCertificateAttestationKeyNameFromProfile) {
+  chromeos::AttestationClient::Get()
+      ->GetTestInterface()
+      ->ConfigureEnrollmentPreparations(true);
+
+  ::attestation::GetCertificateRequest request;
+  request.set_certificate_profile(
+      ::attestation::CertificateProfile::ENTERPRISE_ENROLLMENT_CERTIFICATE);
+  request.set_username("");
+  // Note: no key label is set.
+  request.set_request_origin("origin");
+
+  AllowlistCertificateRequest(::attestation::ACAType::DEFAULT_ACA, request);
+
+  base::MockCallback<AttestationFlowIntegrated::CertificateCallback> callback;
+  std::string certificate;
+  EXPECT_CALL(callback, Run(AttestationStatus::ATTESTATION_SUCCESS, _))
+      .WillOnce(SaveArg<1>(&certificate));
+
+  AttestationFlowIntegrated flow;
+  flow.GetCertificate(
+      static_cast<AttestationCertificateProfile>(request.certificate_profile()),
+      EmptyAccountId(), request.request_origin(),
+      /*generate_new_key=*/true, request.key_label(),
+      base::BindOnce(
+          &AttestationFlowIntegratedTest::QuitRunLoopCertificateCallback,
+          base::Unretained(this), callback.Get()));
+  Run();
+  EXPECT_FALSE(certificate.empty());
+}
+
+}  // namespace attestation
+}  // namespace chromeos
diff --git a/chromeos/audio/audio_device.cc b/chromeos/audio/audio_device.cc
index 8ac4d05..2654522 100644
--- a/chromeos/audio/audio_device.cc
+++ b/chromeos/audio/audio_device.cc
@@ -153,7 +153,6 @@
   else
     display_name = node.device_name;
   device_name = node.device_name;
-  mic_positions = node.mic_positions;
   priority = GetDevicePriority(type, node.is_input);
   active = node.active;
   plugged_time = node.plugged_time;
@@ -192,7 +191,6 @@
                       active ? "true" : "false");
   base::StringAppendF(&result, "plugged_time= %s ",
                       base::NumberToString(plugged_time).c_str());
-  base::StringAppendF(&result, "mic_positions = %s ", mic_positions.c_str());
 
   return result;
 }
diff --git a/chromeos/audio/audio_device.h b/chromeos/audio/audio_device.h
index 8fbb157..d779398 100644
--- a/chromeos/audio/audio_device.h
+++ b/chromeos/audio/audio_device.h
@@ -93,7 +93,6 @@
   uint64_t deprecated_stable_device_id = 0;
   std::string display_name;
   std::string device_name;
-  std::string mic_positions;
   AudioDeviceType type = AUDIO_TYPE_OTHER;
   uint8_t priority = 0;
   bool active = false;
diff --git a/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter.cc b/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter.cc
index 31e4118..4476f93 100644
--- a/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter.cc
+++ b/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter.cc
@@ -242,6 +242,11 @@
   return this;
 }
 
+std::unique_ptr<media::CallbackRegistration>
+ContentDecryptionModuleAdapter::RegisterEventCB(EventCB event_cb) {
+  return event_callbacks_.Register(std::move(event_cb));
+}
+
 media::Decryptor* ContentDecryptionModuleAdapter::GetDecryptor() {
   return this;
 }
@@ -270,13 +275,9 @@
   DVLOG(2) << __func__
            << " has_additional_usable_key: " << has_additional_usable_key;
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  if (has_additional_usable_key) {
-    base::AutoLock auto_lock(new_key_cb_lock_);
-    if (new_audio_key_cb_)
-      new_audio_key_cb_.Run();
-    if (new_video_key_cb_)
-      new_video_key_cb_.Run();
-  }
+
+  if (has_additional_usable_key)
+    event_callbacks_.Notify(Event::kHasAdditionalUsableKey);
 
   session_keys_change_cb_.Run(session_id, has_additional_usable_key,
                               std::move(keys_info));
@@ -290,19 +291,6 @@
       session_id, base::Time::FromDoubleT(new_expiry_time_sec));
 }
 
-void ContentDecryptionModuleAdapter::RegisterNewKeyCB(StreamType stream_type,
-                                                      NewKeyCB new_key_cb) {
-  base::AutoLock auto_lock(new_key_cb_lock_);
-  switch (stream_type) {
-    case kAudio:
-      new_audio_key_cb_ = std::move(new_key_cb);
-      break;
-    case kVideo:
-      new_video_key_cb_ = std::move(new_key_cb);
-      break;
-  }
-}
-
 void ContentDecryptionModuleAdapter::Decrypt(
     StreamType stream_type,
     scoped_refptr<media::DecoderBuffer> encrypted,
diff --git a/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter.h b/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter.h
index de504e0..6537792 100644
--- a/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter.h
+++ b/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter.h
@@ -13,6 +13,7 @@
 #include "base/threading/thread_checker.h"
 #include "chromeos/components/cdm_factory_daemon/cdm_storage_adapter.h"
 #include "chromeos/components/cdm_factory_daemon/mojom/content_decryption_module.mojom.h"
+#include "media/base/callback_registry.h"
 #include "media/base/cdm_context.h"
 #include "media/base/cdm_key_information.h"
 #include "media/base/cdm_promise_adapter.h"
@@ -33,7 +34,7 @@
 // clear and not decoding.
 //
 // This implementation runs in the GPU process and expects all calls to be
-// executed on the mojo thread.  Decrypt, RegisteryNewKeyCB and CancelDecrypt
+// executed on the mojo thread.  Decrypt, RegisterEventCB and CancelDecrypt
 // are exceptions, and can be called from any thread.
 class COMPONENT_EXPORT(CDM_FACTORY_DAEMON) ContentDecryptionModuleAdapter
     : public cdm::mojom::ContentDecryptionModuleClient,
@@ -85,6 +86,8 @@
   media::CdmContext* GetCdmContext() override;
 
   // media::CdmContext:
+  std::unique_ptr<media::CallbackRegistration> RegisterEventCB(
+      EventCB event_cb) override;
   Decryptor* GetDecryptor() override;
 
   // cdm::mojom::ContentDecryptionModuleClient:
@@ -101,7 +104,6 @@
                                  double new_expiry_time_sec) override;
 
   // media::Decryptor:
-  void RegisterNewKeyCB(StreamType stream_type, NewKeyCB key_added_cb) override;
   void Decrypt(StreamType stream_type,
                scoped_refptr<media::DecoderBuffer> encrypted,
                DecryptCB decrypt_cb) override;
@@ -165,11 +167,7 @@
   // Keep track of outstanding promises.
   media::CdmPromiseAdapter cdm_promise_adapter_;
 
-  // Protect |new_audio_key_cb_| and |new_video_key_cb_| as they are set on the
-  // decoder thread but called on the mojo thread.
-  mutable base::Lock new_key_cb_lock_;
-  NewKeyCB new_audio_key_cb_ GUARDED_BY(new_key_cb_lock_);
-  NewKeyCB new_video_key_cb_ GUARDED_BY(new_key_cb_lock_);
+  media::CallbackRegistry<EventCB::RunType> event_callbacks_;
 
   // WeakPtrFactory to use for callbacks.
   base::WeakPtrFactory<ContentDecryptionModuleAdapter> weak_factory_{this};
diff --git a/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter_unittest.cc b/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter_unittest.cc
index ab414082..9172c10 100644
--- a/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter_unittest.cc
+++ b/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter_unittest.cc
@@ -440,19 +440,26 @@
 }
 
 TEST_F(ContentDecryptionModuleAdapterTest, RegisterNewKeyCB) {
-  base::MockRepeatingClosure closure;
-  cdm_adapter_->RegisterNewKeyCB(media::Decryptor::kVideo, closure.Get());
-  cdm_adapter_->RegisterNewKeyCB(media::Decryptor::kAudio, closure.Get());
+  base::MockCallback<media::CdmContext::EventCB> event_cb_1;
+  base::MockCallback<media::CdmContext::EventCB> event_cb_2;
+  auto cb_registration_1 = cdm_adapter_->RegisterEventCB(event_cb_1.Get());
+  auto cb_registration_2 = cdm_adapter_->RegisterEventCB(event_cb_2.Get());
 
-  // It should get invoked for each audio & video.
-  EXPECT_CALL(closure, Run()).Times(2);
+  // All registered event callbacks should be invoked.
+  EXPECT_CALL(event_cb_1,
+              Run(media::CdmContext::Event::kHasAdditionalUsableKey));
+  EXPECT_CALL(event_cb_2,
+              Run(media::CdmContext::Event::kHasAdditionalUsableKey));
   EXPECT_CALL(mock_session_keys_change_cb_, Run(kFakeSessionId1, true, _));
   cdm_adapter_->OnSessionKeysChange(kFakeSessionId1, true, {});
+  base::RunLoop().RunUntilIdle();
 
-  // If no keys change, they should not get called.
-  EXPECT_CALL(closure, Run()).Times(0);
+  // If no keys change, no registered event callbacks should be invoked.
+  EXPECT_CALL(event_cb_1, Run(_)).Times(0);
+  EXPECT_CALL(event_cb_2, Run(_)).Times(0);
   EXPECT_CALL(mock_session_keys_change_cb_, Run(kFakeSessionId1, false, _));
   cdm_adapter_->OnSessionKeysChange(kFakeSessionId1, false, {});
+  base::RunLoop().RunUntilIdle();
 }
 
 TEST_F(ContentDecryptionModuleAdapterTest, Decrypt_Unencrypted) {
diff --git a/chromeos/components/local_search_service/inverted_index_search.cc b/chromeos/components/local_search_service/inverted_index_search.cc
index 2d7b1fe..54cb2daf 100644
--- a/chromeos/components/local_search_service/inverted_index_search.cc
+++ b/chromeos/components/local_search_service/inverted_index_search.cc
@@ -21,8 +21,11 @@
 
 using chromeos::string_matching::TokenizedString;
 
-std::vector<Token> ExtractDocumentTokens(const Data& data,
-                                         const std::string& locale) {
+std::vector<Token> ExtractDocumentTokens(const Data& data) {
+  // Use input locale unless it's empty. In this case we will use system
+  // default locale.
+  const std::string locale =
+      data.locale.empty() ? base::i18n::GetConfiguredLocale() : data.locale;
   std::vector<Token> document_tokens;
   for (const Content& content : data.contents) {
     DCHECK_GE(content.weight, 0);
@@ -37,9 +40,10 @@
 
 }  // namespace
 
-InvertedIndexSearch::InvertedIndexSearch() {
-  inverted_index_ = std::make_unique<InvertedIndex>();
-}
+InvertedIndexSearch::InvertedIndexSearch(IndexId index_id,
+                                         PrefService* local_state)
+    : Index(index_id, Backend::kInvertedIndex, local_state),
+      inverted_index_(std::make_unique<InvertedIndex>()) {}
 
 InvertedIndexSearch::~InvertedIndexSearch() = default;
 
@@ -48,33 +52,24 @@
 }
 
 void InvertedIndexSearch::AddOrUpdate(
-    const std::vector<local_search_service::Data>& data,
-    bool build_index) {
+    const std::vector<local_search_service::Data>& data) {
   for (const Data& d : data) {
-    // Use input locale unless it's empty. In this case we will use system
-    // default locale.
-    const std::string locale =
-        d.locale.empty() ? base::i18n::GetConfiguredLocale() : d.locale;
-    const std::vector<Token> document_tokens = ExtractDocumentTokens(d, locale);
+    const std::vector<Token> document_tokens = ExtractDocumentTokens(d);
     DCHECK(!document_tokens.empty());
     inverted_index_->AddDocument(d.id, document_tokens);
   }
 
-  if (build_index) {
-    inverted_index_->BuildInvertedIndex();
-  }
+  inverted_index_->BuildInvertedIndex();
 }
 
-uint32_t InvertedIndexSearch::Delete(const std::vector<std::string>& ids,
-                                     bool build_index) {
+uint32_t InvertedIndexSearch::Delete(const std::vector<std::string>& ids) {
   uint32_t num_deleted = 0u;
   for (const auto& id : ids) {
     DCHECK(!id.empty());
     num_deleted += inverted_index_->RemoveDocument(id);
   }
-  if (build_index) {
-    inverted_index_->BuildInvertedIndex();
-  }
+
+  inverted_index_->BuildInvertedIndex();
   return num_deleted;
 }
 
@@ -105,9 +100,8 @@
     tokens.insert(token);
   }
 
-  // TODO(jiameng): allow thresholds to be passed in as search params.
   *results = inverted_index_->FindMatchingDocumentsApproximately(
-      tokens, 0.1 /* prefix_threhold */, 0.6 /* block_threshold */);
+      tokens, search_params_.prefix_threshold, search_params_.fuzzy_threshold);
 
   if (results->size() > max_results && max_results > 0u)
     results->resize(max_results);
diff --git a/chromeos/components/local_search_service/inverted_index_search.h b/chromeos/components/local_search_service/inverted_index_search.h
index a233a68..df273b8f 100644
--- a/chromeos/components/local_search_service/inverted_index_search.h
+++ b/chromeos/components/local_search_service/inverted_index_search.h
@@ -12,6 +12,7 @@
 
 #include "base/macros.h"
 #include "base/strings/string16.h"
+#include "chromeos/components/local_search_service/index.h"
 #include "chromeos/components/local_search_service/shared_structs.h"
 
 namespace chromeos {
@@ -19,32 +20,31 @@
 
 class InvertedIndex;
 
+// An implementation of Index.
 // A search via the inverted index backend with TF-IDF based document ranking.
-class InvertedIndexSearch {
+class InvertedIndexSearch : public Index {
  public:
-  InvertedIndexSearch();
-  ~InvertedIndexSearch();
+  InvertedIndexSearch(IndexId index_id, PrefService* local_state);
+  ~InvertedIndexSearch() override;
 
   InvertedIndexSearch(const InvertedIndexSearch&) = delete;
   InvertedIndexSearch& operator=(const InvertedIndexSearch&) = delete;
 
-  // Returns number of data items.
-  uint64_t GetSize();
-
-  // Adds or updates data.
-  // IDs of data should not be empty.
-  void AddOrUpdate(const std::vector<Data>& data, bool build_index = true);
-
-  // Deletes data with |ids| and returns number of items deleted.
-  // If an id doesn't exist in the InvertedIndexSearch, no operation will be
-  // done. IDs should not be empty.
-  uint32_t Delete(const std::vector<std::string>& ids, bool build_index = true);
-
-  // Returns matching results for a given query.
-  // Zero |max_results| means no max.
+  // Index overrides:
+  uint64_t GetSize() override;
+  // TODO(jiameng): we always build the index after documents are updated. May
+  // revise this strategy if there is a different use case.
+  void AddOrUpdate(const std::vector<Data>& data) override;
+  // TODO(jiameng): we always build the index after documents are deleted. May
+  // revise this strategy if there is a different use case.
+  uint32_t Delete(const std::vector<std::string>& ids) override;
+  // Returns matching results for a given query by approximately matching the
+  // query with terms in the documents. Documents are ranked by TF-IDF scores.
+  // Scores in results are positive but not guaranteed to be in any particular
+  // range.
   ResponseStatus Find(const base::string16& query,
                       uint32_t max_results,
-                      std::vector<Result>* results);
+                      std::vector<Result>* results) override;
 
   // Returns document id and number of occurrences of |term|.
   // Document ids are sorted in alphabetical order.
diff --git a/chromeos/components/local_search_service/inverted_index_search_unittest.cc b/chromeos/components/local_search_service/inverted_index_search_unittest.cc
index e371a7b..0a2ccef 100644
--- a/chromeos/components/local_search_service/inverted_index_search_unittest.cc
+++ b/chromeos/components/local_search_service/inverted_index_search_unittest.cc
@@ -25,8 +25,13 @@
 }  // namespace
 
 class InvertedIndexSearchTest : public testing::Test {
+  void SetUp() override {
+    search_ = std::make_unique<InvertedIndexSearch>(IndexId::kCrosSettings,
+                                                    nullptr /* local_state */);
+  }
+
  protected:
-  InvertedIndexSearch search_;
+  std::unique_ptr<InvertedIndexSearch> search_;
 };
 
 TEST_F(InvertedIndexSearchTest, Add) {
@@ -37,20 +42,20 @@
       {"id2", {{"cid_3", "help article on wi-fi"}}}};
 
   const std::vector<Data> data = CreateTestData(data_to_register);
-  search_.AddOrUpdate(data);
-  EXPECT_EQ(search_.GetSize(), 2u);
+  search_->AddOrUpdate(data);
+  EXPECT_EQ(search_->GetSize(), 2u);
 
   {
     // "network" does not exist in the index.
     const TermOccurrence doc_with_freq =
-        search_.FindTermForTesting(base::UTF8ToUTF16("network"));
+        search_->FindTermForTesting(base::UTF8ToUTF16("network"));
     EXPECT_TRUE(doc_with_freq.empty());
   }
 
   {
     // "help" exists in the index.
     const TermOccurrence doc_with_freq =
-        search_.FindTermForTesting(base::UTF8ToUTF16("help"));
+        search_->FindTermForTesting(base::UTF8ToUTF16("help"));
     EXPECT_EQ(doc_with_freq.size(), 2u);
     EXPECT_EQ(doc_with_freq[0].first, "id1");
     EXPECT_EQ(doc_with_freq[0].second, 3u);
@@ -61,25 +66,25 @@
   {
     // "wifi" exists in the index but "wi-fi" doesn't because of normalization.
     TermOccurrence doc_with_freq =
-        search_.FindTermForTesting(base::UTF8ToUTF16("wifi"));
+        search_->FindTermForTesting(base::UTF8ToUTF16("wifi"));
     EXPECT_EQ(doc_with_freq.size(), 2u);
     EXPECT_EQ(doc_with_freq[0].first, "id1");
     EXPECT_EQ(doc_with_freq[0].second, 2u);
     EXPECT_EQ(doc_with_freq[1].first, "id2");
     EXPECT_EQ(doc_with_freq[1].second, 1u);
 
-    doc_with_freq = search_.FindTermForTesting(base::UTF8ToUTF16("wi-fi"));
+    doc_with_freq = search_->FindTermForTesting(base::UTF8ToUTF16("wi-fi"));
     EXPECT_TRUE(doc_with_freq.empty());
 
     // "WiFi" doesn't exist because the index stores normalized word.
-    doc_with_freq = search_.FindTermForTesting(base::UTF8ToUTF16("WiFi"));
+    doc_with_freq = search_->FindTermForTesting(base::UTF8ToUTF16("WiFi"));
     EXPECT_TRUE(doc_with_freq.empty());
   }
 
   {
     // "this" does not exist in the index because it's a stopword
     const TermOccurrence doc_with_freq =
-        search_.FindTermForTesting(base::UTF8ToUTF16("this"));
+        search_->FindTermForTesting(base::UTF8ToUTF16("this"));
     EXPECT_TRUE(doc_with_freq.empty());
   }
 }
@@ -92,8 +97,8 @@
       {"id2", {{"cid_3", "help article on wi-fi"}}}};
 
   const std::vector<Data> data = CreateTestData(data_to_register);
-  search_.AddOrUpdate(data);
-  EXPECT_EQ(search_.GetSize(), 2u);
+  search_->AddOrUpdate(data);
+  EXPECT_EQ(search_->GetSize(), 2u);
 
   const std::map<std::string, std::vector<ContentWithId>> data_to_update = {
       {"id1",
@@ -102,12 +107,12 @@
       {"id3", {{"cid_3", "Google Map"}}}};
 
   const std::vector<Data> updated_data = CreateTestData(data_to_update);
-  search_.AddOrUpdate(updated_data);
-  EXPECT_EQ(search_.GetSize(), 3u);
+  search_->AddOrUpdate(updated_data);
+  EXPECT_EQ(search_->GetSize(), 3u);
 
   {
     const TermOccurrence doc_with_freq =
-        search_.FindTermForTesting(base::UTF8ToUTF16("bluetooth"));
+        search_->FindTermForTesting(base::UTF8ToUTF16("bluetooth"));
     EXPECT_EQ(doc_with_freq.size(), 1u);
     EXPECT_EQ(doc_with_freq[0].first, "id1");
     EXPECT_EQ(doc_with_freq[0].second, 1u);
@@ -115,7 +120,7 @@
 
   {
     const TermOccurrence doc_with_freq =
-        search_.FindTermForTesting(base::UTF8ToUTF16("wifi"));
+        search_->FindTermForTesting(base::UTF8ToUTF16("wifi"));
     EXPECT_EQ(doc_with_freq.size(), 1u);
     EXPECT_EQ(doc_with_freq[0].first, "id2");
     EXPECT_EQ(doc_with_freq[0].second, 1u);
@@ -123,7 +128,7 @@
 
   {
     const TermOccurrence doc_with_freq =
-        search_.FindTermForTesting(base::UTF8ToUTF16("google"));
+        search_->FindTermForTesting(base::UTF8ToUTF16("google"));
     EXPECT_EQ(doc_with_freq.size(), 2u);
     EXPECT_EQ(doc_with_freq[0].first, "id1");
     EXPECT_EQ(doc_with_freq[0].second, 2u);
@@ -140,14 +145,14 @@
       {"id2", {{"cid_3", "help article on wi-fi"}}}};
 
   const std::vector<Data> data = CreateTestData(data_to_register);
-  search_.AddOrUpdate(data);
-  EXPECT_EQ(search_.GetSize(), 2u);
+  search_->AddOrUpdate(data);
+  EXPECT_EQ(search_->GetSize(), 2u);
 
-  EXPECT_EQ(search_.Delete({"id1", "id3"}), 1u);
+  EXPECT_EQ(search_->Delete({"id1", "id3"}), 1u);
 
   {
     const TermOccurrence doc_with_freq =
-        search_.FindTermForTesting(base::UTF8ToUTF16("wifi"));
+        search_->FindTermForTesting(base::UTF8ToUTF16("wifi"));
     EXPECT_EQ(doc_with_freq.size(), 1u);
     EXPECT_EQ(doc_with_freq[0].first, "id2");
     EXPECT_EQ(doc_with_freq[0].second, 1u);
@@ -165,40 +170,40 @@
   // Nothing has been added to the index.
   std::vector<Result> results;
   EXPECT_EQ(
-      search_.Find(base::UTF8ToUTF16("network"), /*max_results=*/10, &results),
+      search_->Find(base::UTF8ToUTF16("network"), /*max_results=*/10, &results),
       ResponseStatus::kEmptyIndex);
   EXPECT_TRUE(results.empty());
 
   // Data is added and then deleted from index, making the index empty.
-  search_.AddOrUpdate(data);
-  EXPECT_EQ(search_.GetSize(), 2u);
-  EXPECT_EQ(search_.Delete({"id1", "id2"}), 2u);
-  EXPECT_EQ(search_.GetSize(), 0u);
+  search_->AddOrUpdate(data);
+  EXPECT_EQ(search_->GetSize(), 2u);
+  EXPECT_EQ(search_->Delete({"id1", "id2"}), 2u);
+  EXPECT_EQ(search_->GetSize(), 0u);
 
   EXPECT_EQ(
-      search_.Find(base::UTF8ToUTF16("network"), /*max_results=*/10, &results),
+      search_->Find(base::UTF8ToUTF16("network"), /*max_results=*/10, &results),
       ResponseStatus::kEmptyIndex);
   EXPECT_TRUE(results.empty());
 
   // Index is populated again, but query is empty.
-  search_.AddOrUpdate(data);
-  EXPECT_EQ(search_.GetSize(), 2u);
+  search_->AddOrUpdate(data);
+  EXPECT_EQ(search_->GetSize(), 2u);
 
-  EXPECT_EQ(search_.Find(base::UTF8ToUTF16(""), /*max_results=*/10, &results),
+  EXPECT_EQ(search_->Find(base::UTF8ToUTF16(""), /*max_results=*/10, &results),
             ResponseStatus::kEmptyQuery);
   EXPECT_TRUE(results.empty());
 
   // No document is found for a given query.
-  EXPECT_EQ(search_.Find(base::UTF8ToUTF16("networkstuff"), /*max_results=*/10,
-                         &results),
+  EXPECT_EQ(search_->Find(base::UTF8ToUTF16("networkstuff"), /*max_results=*/10,
+                          &results),
             ResponseStatus::kSuccess);
   EXPECT_TRUE(results.empty());
 
   {
     // A document is found.
     // Query's case is normalized.
-    EXPECT_EQ(search_.Find(base::UTF8ToUTF16("ANOTHER networkstuff"),
-                           /*max_results=*/10, &results),
+    EXPECT_EQ(search_->Find(base::UTF8ToUTF16("ANOTHER networkstuff"),
+                            /*max_results=*/10, &results),
               ResponseStatus::kSuccess);
     EXPECT_EQ(results.size(), 1u);
 
@@ -214,8 +219,8 @@
 
   {
     // Two documents are found.
-    EXPECT_EQ(search_.Find(base::UTF8ToUTF16("another help"),
-                           /*max_results=*/10, &results),
+    EXPECT_EQ(search_->Find(base::UTF8ToUTF16("another help"),
+                            /*max_results=*/10, &results),
               ResponseStatus::kSuccess);
     EXPECT_EQ(results.size(), 2u);
 
@@ -245,8 +250,8 @@
 
   {
     // Same as above,  but max number of results is set to 1.
-    EXPECT_EQ(search_.Find(base::UTF8ToUTF16("another help"), /*max_results=*/1,
-                           &results),
+    EXPECT_EQ(search_->Find(base::UTF8ToUTF16("another help"),
+                            /*max_results=*/1, &results),
               ResponseStatus::kSuccess);
     EXPECT_EQ(results.size(), 1u);
     EXPECT_EQ(results[0].id, "id1");
@@ -254,8 +259,8 @@
 
   {
     // Same as above, but set max_results to 0, meaning no max.
-    EXPECT_EQ(search_.Find(base::UTF8ToUTF16("another help"), /*max_results=*/0,
-                           &results),
+    EXPECT_EQ(search_->Find(base::UTF8ToUTF16("another help"),
+                            /*max_results=*/0, &results),
               ResponseStatus::kSuccess);
     EXPECT_EQ(results.size(), 2u);
   }
diff --git a/chromeos/components/local_search_service/local_search_service.cc b/chromeos/components/local_search_service/local_search_service.cc
index a85d5da..4c2d5f6a 100644
--- a/chromeos/components/local_search_service/local_search_service.cc
+++ b/chromeos/components/local_search_service/local_search_service.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "chromeos/components/local_search_service/inverted_index_search.h"
 #include "chromeos/components/local_search_service/linear_map_search.h"
 
 namespace chromeos {
@@ -20,8 +21,6 @@
                                     PrefService* local_state) {
   auto it = indices_.find(index_id);
   if (it == indices_.end()) {
-    // TODO(jiameng): allow inverted index in the next cl.
-    DCHECK_EQ(backend, Backend::kLinearMap);
     switch (backend) {
       case Backend::kLinearMap:
         it = indices_
@@ -29,9 +28,9 @@
                                         index_id, local_state))
                  .first;
         break;
-      default:
+      case Backend::kInvertedIndex:
         it = indices_
-                 .emplace(index_id, std::make_unique<LinearMapSearch>(
+                 .emplace(index_id, std::make_unique<InvertedIndexSearch>(
                                         index_id, local_state))
                  .first;
     }
diff --git a/chromeos/components/local_search_service/local_search_service.h b/chromeos/components/local_search_service/local_search_service.h
index 4a304516..c13fdcf4 100644
--- a/chromeos/components/local_search_service/local_search_service.h
+++ b/chromeos/components/local_search_service/local_search_service.h
@@ -29,10 +29,7 @@
   LocalSearchService(const LocalSearchService&) = delete;
   LocalSearchService& operator=(const LocalSearchService&) = delete;
 
-  // TODO(jiameng): remove default values.
-  Index* GetIndex(IndexId index_id,
-                  Backend backend = Backend::kLinearMap,
-                  PrefService* local_state = nullptr);
+  Index* GetIndex(IndexId index_id, Backend backend, PrefService* local_state);
 
  private:
   std::map<IndexId, std::unique_ptr<Index>> indices_;
diff --git a/chromeos/components/local_search_service/local_search_service_unittest.cc b/chromeos/components/local_search_service/local_search_service_unittest.cc
index ec07961d..f8d9bce 100644
--- a/chromeos/components/local_search_service/local_search_service_unittest.cc
+++ b/chromeos/components/local_search_service/local_search_service_unittest.cc
@@ -21,8 +21,18 @@
   LocalSearchService service_;
 };
 
-TEST_F(LocalSearchServiceTest, GetIndex) {
-  Index* const index = service_.GetIndex(IndexId::kCrosSettings);
+TEST_F(LocalSearchServiceTest, GetLinearMapSearch) {
+  Index* const index = service_.GetIndex(
+      IndexId::kCrosSettings, Backend::kLinearMap, nullptr /* local_state */);
+  CHECK(index);
+
+  EXPECT_EQ(index->GetSize(), 0u);
+}
+
+TEST_F(LocalSearchServiceTest, GetInvertedIndexSearch) {
+  Index* const index =
+      service_.GetIndex(IndexId::kCrosSettings, Backend::kInvertedIndex,
+                        nullptr /* local_state */);
   CHECK(index);
 
   EXPECT_EQ(index->GetSize(), 0u);
diff --git a/chromeos/components/media_app_ui/resources/js/launch.js b/chromeos/components/media_app_ui/resources/js/launch.js
index b4db220..0015d55 100644
--- a/chromeos/components/media_app_ui/resources/js/launch.js
+++ b/chromeos/components/media_app_ui/resources/js/launch.js
@@ -175,10 +175,18 @@
   await advance(navigate.direction);
 });
 
+guestMessagePipe.registerHandler(Message.REQUEST_SAVE_FILE, async (message) => {
+  const {suggestedName, mimeType} =
+      /** @type {!RequestSaveFileMessage} */ (message);
+  const handle = await pickWritableFile(suggestedName, mimeType);
+  /** @type {!RequestSaveFileResponse} */
+  const response = {token: generateToken(handle)};
+  return response;
+});
+
 guestMessagePipe.registerHandler(Message.SAVE_COPY, async (message) => {
-  const {blob, suggestedName} = /** @type {!SaveCopyMessage} */ (message);
-  const fileSystemHandle = await pickWritableFile(suggestedName, blob.type);
-  const {handle} = await getFileFromHandle(fileSystemHandle);
+  const {blob, token} = /** @type {!SaveCopyMessage} */ (message);
+  const handle = tokenMap.get(token);
   // Note `handle` could be the same as a `FileSystemFileHandle` that exists in
   // `tokenMap`. Possibly even the `File` currently open. But that's OK. E.g.
   // the next overwrite-file request will just invoke `saveBlobToFile` in the
@@ -195,8 +203,8 @@
  */
 function pickWritableFile(suggestedName, mimeType) {
   const extension = suggestedName.split('.').reverse()[0];
-  // TODO(b/141587270): Add a default filename when it's supported by the native
-  // file api.
+  // TODO(b/161087799): Add a default filename when it's supported by the
+  // native file api.
   /** @type {!FilePickerOptions} */
   const options = {
     types: [
diff --git a/chromeos/components/media_app_ui/resources/js/media_app.externs.js b/chromeos/components/media_app_ui/resources/js/media_app.externs.js
index 4a7a803..f4b2d1b 100644
--- a/chromeos/components/media_app_ui/resources/js/media_app.externs.js
+++ b/chromeos/components/media_app_ui/resources/js/media_app.externs.js
@@ -135,12 +135,28 @@
  */
 mediaApp.ClientApiDelegate.prototype.openFeedbackDialog = function() {};
 /**
- * Saves a copy of `file` in a custom location with a custom
- * name which the user is prompted for via a native save file dialog.
+ * Request for the user to be prompted with a save file dialog. Once the user
+ * selects a location a new file handle is created and a unique token to that
+ * file will be returned. This token can be then used with saveCopy(). The file
+ * extension on `suggestedName` and the provided `mimeType` are used to inform
+ * the save as dialog what file should be created. Once the Native Filesystem
+ * API allows, this save as dialog will additionally have the filename input be
+ * pre-filled with `suggestedName`.
+ * TODO(b/161087799): Update function description once Native Filesystem API
+ * supports suggestedName.
+ * @param {string} suggestedName
+ * @param {string} mimeType
+ * @return {!Promise<number>}
+ */
+mediaApp.ClientApiDelegate.prototype.requestSaveFile = function(
+    suggestedName, mimeType) {};
+/**
+ * Saves a copy of `file` in the file specified by `token`.
  * @param {!mediaApp.AbstractFile} file
+ * @param {number} token
  * @return {!Promise<undefined>}
  */
-mediaApp.ClientApiDelegate.prototype.saveCopy = function(file) {};
+mediaApp.ClientApiDelegate.prototype.saveCopy = function(file, token) {};
 
 /**
  * The client Api for interacting with the media app instance.
diff --git a/chromeos/components/media_app_ui/resources/js/message_types.js b/chromeos/components/media_app_ui/resources/js/message_types.js
index 87bad194..35e6ba4 100644
--- a/chromeos/components/media_app_ui/resources/js/message_types.js
+++ b/chromeos/components/media_app_ui/resources/js/message_types.js
@@ -20,6 +20,7 @@
   OPEN_FEEDBACK_DIALOG: 'open-feedback-dialog',
   OVERWRITE_FILE: 'overwrite-file',
   RENAME_FILE: 'rename-file',
+  REQUEST_SAVE_FILE: 'request-save-file',
   SAVE_COPY: 'save-copy'
 };
 
@@ -101,21 +102,30 @@
 
 /**
  * Message sent by the unprivileged context to the privileged context requesting
- * for the provided file to be copied and saved in a new location which the user
- * is prompted for, i.e a 'save as' operation. Once the native filesystem api
- * allows, this save as dialog will have the filename input be pre-filled with
- * `suggestedName`. `suggestedName` is additionally used to determine the file
- * extension which helps inform the save as dialog as to which files should be
- * overwritable. This method can be called with any file, not just the currently
- * writable file.
- * @typedef {{blob: !Blob, suggestedName: string}}
+ * for the user to be prompted with a save file dialog. Once the user selects a
+ * location a new file handle is created and a unique token to that file
+ * will be returned. This token can be then used with saveCopy(). The file
+ * extension on `suggestedName` and the provided `mimeType` are used to inform
+ * the save as dialog what file should be created. Once the native filesystem
+ * api allows, this save as dialog will additionally have the filename input be
+ * pre-filled with `suggestedName`.
+ * @typedef {{suggestedName: string, mimeType: string}}
  */
-let SaveCopyMessage;
+let RequestSaveFileMessage;
 
 /**
- * Response message sent by the privileged context indicating the error message
- * if the associated save as operation could not be performed. Returns nothing
- * if the operation was successful.
- * @typedef {{ errorMessage: ?string }}
+ * Response message sent by the privileged context with a unique identifier for
+ * the new writable file created on disk by the corresponding request save
+ * file message.
+ * @typedef {{token: number}}
  */
-let SaveCopyResponse;
+let RequestSaveFileResponse;
+
+/**
+ * Message sent by the unprivileged context to the privileged context requesting
+ * for the provided file to be copied and saved in the location specified by
+ * `token`. This method can be called with any file, not just the currently
+ * writable file.
+ * @typedef {{blob: !Blob, token: number}}
+ */
+let SaveCopyMessage;
diff --git a/chromeos/components/media_app_ui/resources/js/receiver.js b/chromeos/components/media_app_ui/resources/js/receiver.js
index 8eeb3ee..54a3e9ff 100644
--- a/chromeos/components/media_app_ui/resources/js/receiver.js
+++ b/chromeos/components/media_app_ui/resources/js/receiver.js
@@ -191,14 +191,39 @@
     return /** @type {?string} */ (response['errorMessage']);
   },
   /**
+   * @param {string} suggestedName
+   * @param {string} mimeType
+   * @return {!Promise<number>}
+   */
+  async requestSaveFile(suggestedName, mimeType) {
+    /** @type {!RequestSaveFileMessage} */
+    const msg = {suggestedName, mimeType};
+    const response =
+        /** @type {!RequestSaveFileResponse} */ (
+            await parentMessagePipe.sendMessage(
+                Message.REQUEST_SAVE_FILE, msg));
+    return response.token;
+  },
+  /**
    * @param {!mediaApp.AbstractFile} abstractFile
+   * @param {number} token
+   * @this {mediaApp.ClientApiDelegate}
    * @return {!Promise<undefined>}
    */
-  async saveCopy(abstractFile) {
+  async saveCopy(abstractFile, token) {
+    if (token === undefined) {
+      // The guest frame must be running an older version of backlight which is
+      // assuming the `saveCopy(abstractFile)` interface. Make the
+      // requestSaveFile call on its behalf for backwards compatibility.
+      // TODO(b/160938402): remove this.
+      token =
+          await this.requestSaveFile(abstractFile.name, abstractFile.mimeType);
+    }
+
     /** @type {!SaveCopyMessage} */
-    const msg = {blob: abstractFile.blob, suggestedName: abstractFile.name};
+    const msg = {blob: abstractFile.blob, token};
     await parentMessagePipe.sendMessage(Message.SAVE_COPY, msg);
-  }
+  },
 };
 
 /**
diff --git a/chromeos/components/media_app_ui/test/driver_api.js b/chromeos/components/media_app_ui/test/driver_api.js
index 0ef08828..ffab3aa71 100644
--- a/chromeos/components/media_app_ui/test/driver_api.js
+++ b/chromeos/components/media_app_ui/test/driver_api.js
@@ -22,6 +22,7 @@
  *     property: (string|undefined),
  *     renameLastFile: (string|undefined),
  *     requestFullscreen: (boolean|undefined),
+ *     requestSaveFile: (boolean|undefined),
  *     saveCopy: (boolean|undefined),
  *     testQuery: string,
  * }}
diff --git a/chromeos/components/media_app_ui/test/guest_query_receiver.js b/chromeos/components/media_app_ui/test/guest_query_receiver.js
index 07df5fe..5ced8e6 100644
--- a/chromeos/components/media_app_ui/test/guest_query_receiver.js
+++ b/chromeos/components/media_app_ui/test/guest_query_receiver.js
@@ -84,13 +84,24 @@
     } catch (/** @type{!Error} */ error) {
       result = `renameOriginalFile failed Error: ${error}`;
     }
+  } else if (data.requestSaveFile) {
+    const existingFile = assertCast(lastReceivedFileList).item(0);
+    if (!existingFile) {
+      result = 'requestSaveFile failed, no file loaded';
+    } else {
+      const token = await DELEGATE.requestSaveFile(
+          existingFile.name, existingFile.mimeType);
+      result = token.toString();
+    }
   } else if (data.saveCopy) {
     const existingFile = assertCast(lastReceivedFileList).item(0);
     if (!existingFile) {
       result = 'saveCopy failed, no file loaded';
     } else {
-      DELEGATE.saveCopy(existingFile);
-      result = 'boo yah!';
+      const token = await DELEGATE.requestSaveFile(
+          existingFile.name, existingFile.mimeType);
+      await DELEGATE.saveCopy(existingFile, token);
+      result = 'file successfully saved';
     }
   } else if (data.getFileErrors) {
     result =
diff --git a/chromeos/components/media_app_ui/test/media_app_ui_browsertest.js b/chromeos/components/media_app_ui/test/media_app_ui_browsertest.js
index 416bbc1..ffabfcf 100644
--- a/chromeos/components/media_app_ui/test/media_app_ui_browsertest.js
+++ b/chromeos/components/media_app_ui/test/media_app_ui_browsertest.js
@@ -824,8 +824,8 @@
   testDone();
 });
 
-// Tests the IPC behind the saveCopy delegate function.
-TEST_F('MediaAppUIBrowserTest', 'SaveCopyIPC', async () => {
+// Tests the IPC behind the requestSaveFile delegate function.
+TEST_F('MediaAppUIBrowserTest', 'RequestSaveFileIPC', async () => {
   // Mock out choose file system entries since it can only be interacted with
   // via trusted user gestures.
   const newFileHandle = new FakeFileSystemFileHandle();
@@ -838,13 +838,27 @@
   const testImage = await createTestImageFile(10, 10);
   await loadFile(testImage, new FakeFileSystemFileHandle());
 
-  const result = await sendTestMessage({saveCopy: true});
-  assertEquals(result.testQueryResult, 'boo yah!');
+  const result = await sendTestMessage({requestSaveFile: true});
   const options = await chooseEntries;
-
+  const lastToken = [...tokenMap.keys()].slice(-1)[0];
+  assertMatch(result.testQueryResult, lastToken);
   assertEquals(options.types.length, 1);
   assertEquals(options.types[0].description, 'png');
   assertDeepEquals(options.types[0].accept['image/png'], ['png']);
+  testDone();
+});
+
+// Tests the IPC behind the saveCopy delegate function.
+TEST_F('MediaAppUIBrowserTest', 'SaveCopyIPC', async () => {
+  // Mock out choose file system entries since it can only be interacted with
+  // via trusted user gestures.
+  const newFileHandle = new FakeFileSystemFileHandle();
+  window.showSaveFilePicker = () => Promise.resolve(newFileHandle);
+  const testImage = await createTestImageFile(10, 10);
+  await loadFile(testImage, new FakeFileSystemFileHandle());
+
+  const result = await sendTestMessage({saveCopy: true});
+  assertEquals(result.testQueryResult, 'file successfully saved');
 
   const writeResult = await newFileHandle.lastWritable.closePromise;
   assertEquals(await writeResult.text(), await testImage.text());
diff --git a/chromeos/components/print_management/resources/printing_app_icon.svg b/chromeos/components/print_management/resources/printing_app_icon.svg
index 574b2252..437d733d 100644
--- a/chromeos/components/print_management/resources/printing_app_icon.svg
+++ b/chromeos/components/print_management/resources/printing_app_icon.svg
@@ -1,17 +1,18 @@
 <svg width="192" height="192" viewBox="0 0 192 192" fill="none" xmlns="http://www.w3.org/2000/svg">
-<g clip-path="url(#clip0)">
-<circle cx="96" cy="96" r="88" fill="#3C4043"/>
-<path d="M150 73C150 68.5817 146.418 65 142 65L50 65C45.5817 65 42 68.5817 42 73L42 125C42 129.418 45.5817 133 50 133L142 133C146.418 133 150 129.418 150 125L150 73Z" fill="#AECBFA"/>
-<rect x="58" y="103" width="76" height="16" rx="2" fill="#5F6368"/>
-<path d="M65 48C65 46.8954 65.8954 46 67 46H125C126.105 46 127 46.8954 127 48V65H65V48Z" fill="white"/>
-<path d="M65 110H127V142C127 143.105 126.105 144 125 144H67C65.8954 144 65 143.105 65 142V110Z" fill="white"/>
-<rect x="75" y="130" width="42" height="4" rx="2" fill="#5F6368"/>
-<rect x="75" y="120" width="42" height="4" rx="2" fill="#5F6368"/>
-<circle cx="128" cy="89" r="6" fill="#1A73E8"/>
-</g>
-<defs>
-<clipPath id="clip0">
-<rect width="192" height="192" fill="white"/>
-</clipPath>
-</defs>
-</svg>
+  <circle cx="96" cy="95.9998" r="88" fill="#5F6368"/>
+  <path d="M150 73.0003C150 68.5821 146.418 65.0003 142 65.0003L50 65.0003C45.5817 65.0003 42 68.5821 42 73.0003L42 125C42 129.419 45.5817 133 50 133L142 133C146.418 133 150 129.419 150 125L150 73.0003Z" fill="#AECBFA"/>
+  <rect x="58" y="103" width="76" height="16" rx="2" fill="#5F6368"/>
+  <path d="M65 48.0002C65 46.8956 65.8954 46.0002 67 46.0002H125C126.105 46.0002 127 46.8956 127 48.0002V65.0001H65V48.0002Z" fill="white"/>
+  <path d="M65 110H127V142C127 143.104 126.105 144 125 144H67C65.8954 144 65 143.104 65 142V110Z" fill="white"/>
+  <rect x="75" y="130" width="42" height="4" rx="2" fill="#5F6368"/>
+  <rect x="75" y="120" width="42" height="4" rx="2" fill="#5F6368"/>
+  <circle cx="128" cy="88.9997" r="6" fill="#1A73E8"/>
+  <circle cx="96" cy="96" r="88" fill="#5F6368"/>
+  <path d="M156 70.8642C156 65.9686 152.031 62 147.136 62L44.8642 62C39.9686 62 36 65.9686 36 70.8642L36 129.136C36 134.031 39.9686 138 44.8642 138L147.136 138C152.031 138 156 134.031 156 129.136L156 70.8642Z" fill="#4285F4"/>
+  <rect x="54" y="104" width="84" height="18" rx="2.21605" fill="#5F6368"/>
+  <path d="M62 43.216C62 41.9922 62.9922 41 64.216 41H127.784C129.008 41 130 41.9922 130 43.216V62.0525H62V43.216Z" fill="white"/>
+  <path d="M62 112H130V147.784C130 149.008 129.008 150 127.784 150H64.216C62.9922 150 62 149.008 62 147.784V112Z" fill="white"/>
+  <rect x="73" y="134" width="46" height="4" rx="2" fill="#5F6368"/>
+  <rect x="73" y="122" width="46" height="4" rx="2" fill="#5F6368"/>
+  <circle cx="132" cy="88" r="6" fill="#80F9F9"/>
+</svg>
\ No newline at end of file
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index 30ecb35..5fa873b 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -152,6 +152,10 @@
 // If enabled, DriveFS will be used for Drive sync.
 const base::Feature kDriveFs{"DriveFS", base::FEATURE_ENABLED_BY_DEFAULT};
 
+// Enables duplex native messaging between DriveFS and extensions.
+const base::Feature kDriveFsBidirectionalNativeMessaging{
+    "DriveFsBidirectionalNativeMessaging", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables DriveFS' experimental local files mirroring functionality.
 const base::Feature kDriveFsMirroring{"DriveFsMirroring",
                                       base::FEATURE_DISABLED_BY_DEFAULT};
@@ -275,6 +279,10 @@
 const base::Feature kLacrosSupport{"LacrosSupport",
                                    base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enables or disables language settings update.
+const base::Feature kLanguageSettingsUpdate{"LanguageSettingsUpdate",
+                                            base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables or disables device management disclosure on login / lock screen.
 const base::Feature kLoginDeviceManagementDisclosure{
     "LoginDeviceManagementDisclosure", base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/chromeos/constants/chromeos_features.h b/chromeos/constants/chromeos_features.h
index 485c7ae..64cf887 100644
--- a/chromeos/constants/chromeos_features.h
+++ b/chromeos/constants/chromeos_features.h
@@ -74,6 +74,8 @@
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kDlcSettingsUi;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
+extern const base::Feature kDriveFsBidirectionalNativeMessaging;
+COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kDriveFsMirroring;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kEmojiSuggestAddition;
@@ -122,6 +124,8 @@
 extern const base::Feature kInstantTethering;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const base::Feature kLacrosSupport;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
+extern const base::Feature kLanguageSettingsUpdate;
+COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kLoginDeviceManagementDisclosure;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kLoginDisplayPasswordButton;
diff --git a/chromeos/constants/chromeos_switches.cc b/chromeos/constants/chromeos_switches.cc
index 25a486b23..e65f741 100644
--- a/chromeos/constants/chromeos_switches.cc
+++ b/chromeos/constants/chromeos_switches.cc
@@ -565,6 +565,11 @@
 // throttled.
 const char kDisableArcCpuRestriction[] = "disable-arc-cpu-restriction";
 
+// If this switch is passed, the device policy MinimumChromeVersionEnforced
+// assumes that the device has reached Auto Update Expiration. This is useful
+// for testing the policy behaviour on the DUT.
+const char kUpdateRequiredAueForTest[] = "aue-reached-for-update-required-test";
+
 bool WakeOnWifiEnabled() {
   return !base::CommandLine::ForCurrentProcess()->HasSwitch(kDisableWakeOnWifi);
 }
@@ -637,5 +642,10 @@
       kOobeForceTabletFirstRun);
 }
 
+bool IsAueReachedForUpdateRequiredForTest() {
+  return base::CommandLine::ForCurrentProcess()->HasSwitch(
+      kUpdateRequiredAueForTest);
+}
+
 }  // namespace switches
 }  // namespace chromeos
diff --git a/chromeos/constants/chromeos_switches.h b/chromeos/constants/chromeos_switches.h
index 4da209e2..64d69c8 100644
--- a/chromeos/constants/chromeos_switches.h
+++ b/chromeos/constants/chromeos_switches.h
@@ -220,6 +220,8 @@
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kWakeOnWifiPacket[];
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const char kUnfilteredBluetoothDevices[];
+COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
+extern const char kUpdateRequiredAueForTest[];
 
 ////////////////////////////////////////////////////////////////////////////////
 
@@ -274,6 +276,11 @@
 // device is not in tablet mode.
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool ShouldOobeUseTabletModeFirstRun();
 
+// Returns true if device policy MinimumChromeVersionEnforced should assume that
+// Auto Update Expiration is reached. This should only be used for testing.
+COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
+bool IsAueReachedForUpdateRequiredForTest();
+
 }  // namespace switches
 }  // namespace chromeos
 
diff --git a/chromeos/dbus/BUILD.gn b/chromeos/dbus/BUILD.gn
index 9a14b14..71cdd771 100644
--- a/chromeos/dbus/BUILD.gn
+++ b/chromeos/dbus/BUILD.gn
@@ -4,14 +4,10 @@
 
 import("//testing/test.gni")
 import("//third_party/protobuf/proto_library.gni")
+import("use_real_dbus_clients.gni")
 
 assert(is_chromeos, "Non-Chrome-OS builds must not depend on //chromeos")
 
-declare_args() {
-  # Instantiate real D-Bus clients instead of fakes.
-  use_real_dbus_clients = is_chromeos_device
-}
-
 component("dbus") {
   output_name = "chromeos_dbus"  # Avoid conflict with //dbus
   defines = [ "IS_CHROMEOS_DBUS_IMPL" ]
diff --git a/chromeos/dbus/audio/audio_node.h b/chromeos/dbus/audio/audio_node.h
index 9288407..f2dd692db 100644
--- a/chromeos/dbus/audio/audio_node.h
+++ b/chromeos/dbus/audio/audio_node.h
@@ -24,7 +24,6 @@
   std::string device_name;
   std::string type;
   std::string name;
-  std::string mic_positions;
   bool active = false;
   // Time that the node was plugged in.
   uint64_t plugged_time = 0;
diff --git a/chromeos/dbus/audio/cras_audio_client.cc b/chromeos/dbus/audio/cras_audio_client.cc
index 31825500..30ebe722 100644
--- a/chromeos/dbus/audio/cras_audio_client.cc
+++ b/chromeos/dbus/audio/cras_audio_client.cc
@@ -738,9 +738,6 @@
       } else if (key == cras::kPluggedTimeProperty) {
         if (!value_reader.PopUint64(&node->plugged_time))
           return false;
-      } else if (key == cras::kMicPositionsProperty) {
-        if (!value_reader.PopString(&node->mic_positions))
-          return false;
       } else if (key == cras::kStableDeviceIdProperty) {
         if (!value_reader.PopUint64(&node->stable_device_id_v1))
           return false;
diff --git a/chromeos/dbus/audio/cras_audio_client_unittest.cc b/chromeos/dbus/audio/cras_audio_client_unittest.cc
index 3b95510..105ab83 100644
--- a/chromeos/dbus/audio/cras_audio_client_unittest.cc
+++ b/chromeos/dbus/audio/cras_audio_client_unittest.cc
@@ -275,11 +275,6 @@
     sub_writer.CloseContainer(&entry_writer);
 
     sub_writer.OpenDictEntry(&entry_writer);
-    entry_writer.AppendString(cras::kMicPositionsProperty);
-    entry_writer.AppendVariantOfString(node_list[i].mic_positions);
-    sub_writer.CloseContainer(&entry_writer);
-
-    sub_writer.OpenDictEntry(&entry_writer);
     entry_writer.AppendString(cras::kStableDeviceIdProperty);
     entry_writer.AppendVariantOfUint64(node_list[i].stable_device_id_v1);
     sub_writer.CloseContainer(&entry_writer);
@@ -313,7 +308,6 @@
     EXPECT_EQ(expected_node_list[i].device_name, node_list[i].device_name);
     EXPECT_EQ(expected_node_list[i].type, node_list[i].type);
     EXPECT_EQ(expected_node_list[i].name, node_list[i].name);
-    EXPECT_EQ(expected_node_list[i].mic_positions, node_list[i].mic_positions);
     EXPECT_EQ(expected_node_list[i].active, node_list[i].active);
     EXPECT_EQ(expected_node_list[i].plugged_time, node_list[i].plugged_time);
     EXPECT_EQ(expected_node_list[i].StableDeviceIdVersion(),
diff --git a/chromeos/dbus/shill/fake_shill_device_client.cc b/chromeos/dbus/shill/fake_shill_device_client.cc
index f3c6abb..60684fb 100644
--- a/chromeos/dbus/shill/fake_shill_device_client.cc
+++ b/chromeos/dbus/shill/fake_shill_device_client.cc
@@ -61,8 +61,7 @@
 const char FakeShillDeviceClient::kDefaultSimPin[] = "1111";
 const int FakeShillDeviceClient::kSimPinRetryCount = 3;
 
-FakeShillDeviceClient::FakeShillDeviceClient()
-    : initial_tdls_busy_count_(0), tdls_busy_count_(0) {}
+FakeShillDeviceClient::FakeShillDeviceClient() {}
 
 FakeShillDeviceClient::~FakeShillDeviceClient() = default;
 
@@ -266,52 +265,6 @@
   base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, std::move(callback));
 }
 
-void FakeShillDeviceClient::PerformTDLSOperation(
-    const dbus::ObjectPath& device_path,
-    const std::string& operation,
-    const std::string& peer,
-    StringCallback callback,
-    ErrorCallback error_callback) {
-  if (!stub_devices_.HasKey(device_path.value())) {
-    PostNotFoundError(std::move(error_callback));
-    return;
-  }
-  // Use -1 to emulate a TDLS failure.
-  if (tdls_busy_count_ == -1) {
-    base::ThreadTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::BindOnce(std::move(error_callback),
-                                  shill::kErrorDhcpFailed, "Failed"));
-    return;
-  }
-  if (operation != shill::kTDLSStatusOperation && tdls_busy_count_ > 0) {
-    --tdls_busy_count_;
-    base::ThreadTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE,
-        base::BindOnce(std::move(error_callback), shill::kErrorResultInProgress,
-                       "In-Progress"));
-    return;
-  }
-
-  tdls_busy_count_ = initial_tdls_busy_count_;
-
-  std::string result;
-  if (operation == shill::kTDLSDiscoverOperation) {
-    if (tdls_state_.empty())
-      tdls_state_ = shill::kTDLSDisconnectedState;
-  } else if (operation == shill::kTDLSSetupOperation) {
-    if (tdls_state_.empty())
-      tdls_state_ = shill::kTDLSConnectedState;
-  } else if (operation == shill::kTDLSTeardownOperation) {
-    if (tdls_state_.empty())
-      tdls_state_ = shill::kTDLSDisconnectedState;
-  } else if (operation == shill::kTDLSStatusOperation) {
-    result = tdls_state_;
-  }
-
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(std::move(callback), result));
-}
-
 void FakeShillDeviceClient::AddWakeOnPacketConnection(
     const dbus::ObjectPath& device_path,
     const net::IPEndPoint& ip_endpoint,
@@ -491,14 +444,6 @@
   return std::string();
 }
 
-void FakeShillDeviceClient::SetTDLSBusyCount(int count) {
-  tdls_busy_count_ = std::max(count, -1);
-}
-
-void FakeShillDeviceClient::SetTDLSState(const std::string& state) {
-  tdls_state_ = state;
-}
-
 void FakeShillDeviceClient::SetSimLocked(const std::string& device_path,
                                          bool locked) {
   SimLockStatus status = GetSimLockStatus(device_path);
diff --git a/chromeos/dbus/shill/fake_shill_device_client.h b/chromeos/dbus/shill/fake_shill_device_client.h
index efc9cff..5a4ac24 100644
--- a/chromeos/dbus/shill/fake_shill_device_client.h
+++ b/chromeos/dbus/shill/fake_shill_device_client.h
@@ -69,11 +69,6 @@
   void Reset(const dbus::ObjectPath& device_path,
              base::OnceClosure callback,
              ErrorCallback error_callback) override;
-  void PerformTDLSOperation(const dbus::ObjectPath& device_path,
-                            const std::string& operation,
-                            const std::string& peer,
-                            StringCallback callback,
-                            ErrorCallback error_callback) override;
   void AddWakeOnPacketConnection(const dbus::ObjectPath& device_path,
                                  const net::IPEndPoint& ip_endpoint,
                                  base::OnceClosure callback,
@@ -111,8 +106,6 @@
                          const base::Value& value,
                          bool notify_changed) override;
   std::string GetDevicePathForType(const std::string& type) override;
-  void SetTDLSBusyCount(int count) override;
-  void SetTDLSState(const std::string& state) override;
   void SetSimLocked(const std::string& device_path, bool locked) override;
   void AddCellularFoundNetwork(const std::string& device_path) override;
   void SetUsbEthernetMacAddressSourceError(
@@ -163,16 +156,6 @@
   std::map<dbus::ObjectPath, std::unique_ptr<PropertyObserverList>>
       observer_list_;
 
-  // Number of times to return InProgress for TDLS. Set to -1 to emulate
-  // TDLS failure.
-  int initial_tdls_busy_count_;
-
-  // Current TDLS busy count.
-  int tdls_busy_count_;
-
-  // Fake state for TDLS.
-  std::string tdls_state_;
-
   // Wake on packet connections for each device.
   std::map<dbus::ObjectPath, std::set<net::IPEndPoint>>
       wake_on_packet_connections_;
diff --git a/chromeos/dbus/shill/fake_shill_manager_client.cc b/chromeos/dbus/shill/fake_shill_manager_client.cc
index ae98f84..120ce5d 100644
--- a/chromeos/dbus/shill/fake_shill_manager_client.cc
+++ b/chromeos/dbus/shill/fake_shill_manager_client.cc
@@ -37,8 +37,6 @@
 
 namespace {
 
-// Allow parsed command line option 'tdls_busy' to set the fake busy count.
-int s_tdls_busy_count = 0;
 int s_extra_wifi_networks = 0;
 
 // For testing dynamic WEP networks (uses wifi2).
@@ -816,11 +814,6 @@
   }
 
   // Wifi
-  if (s_tdls_busy_count != 0) {
-    ShillDeviceClient::Get()->GetTestInterface()->SetTDLSBusyCount(
-        s_tdls_busy_count);
-  }
-
   state = GetInitialStateForType(shill::kTypeWifi, &enabled);
   if (state != kTechnologyUnavailable) {
     bool portaled = false;
@@ -1236,12 +1229,6 @@
     if (!present)
       shill_initial_state_map_[shill::kTypeCellular] = kNetworkDisabled;
     return true;
-  } else if (arg0 == "tdls_busy") {
-    if (!arg1.empty())
-      base::StringToInt(arg1, &s_tdls_busy_count);
-    else
-      s_tdls_busy_count = 1;
-    return true;
   } else if (arg0 == "olp") {
     cellular_olp_ = arg1;
     return true;
diff --git a/chromeos/dbus/shill/shill_device_client.cc b/chromeos/dbus/shill/shill_device_client.cc
index 0757d9e2..613439fb 100644
--- a/chromeos/dbus/shill/shill_device_client.cc
+++ b/chromeos/dbus/shill/shill_device_client.cc
@@ -174,21 +174,6 @@
                                           std::move(error_callback));
   }
 
-  void PerformTDLSOperation(const dbus::ObjectPath& device_path,
-                            const std::string& operation,
-                            const std::string& peer,
-                            StringCallback callback,
-                            ErrorCallback error_callback) override {
-    dbus::MethodCall method_call(shill::kFlimflamDeviceInterface,
-                                 shill::kPerformTDLSOperationFunction);
-    dbus::MessageWriter writer(&method_call);
-    writer.AppendString(operation);
-    writer.AppendString(peer);
-    GetHelper(device_path)
-        ->CallStringMethodWithErrorCallback(&method_call, std::move(callback),
-                                            std::move(error_callback));
-  }
-
   void AddWakeOnPacketConnection(const dbus::ObjectPath& device_path,
                                  const net::IPEndPoint& ip_endpoint,
                                  base::OnceClosure callback,
diff --git a/chromeos/dbus/shill/shill_device_client.h b/chromeos/dbus/shill/shill_device_client.h
index b6b2956..32fb7ce 100644
--- a/chromeos/dbus/shill/shill_device_client.h
+++ b/chromeos/dbus/shill/shill_device_client.h
@@ -53,8 +53,6 @@
                                    const base::Value& value,
                                    bool notify_changed) = 0;
     virtual std::string GetDevicePathForType(const std::string& type) = 0;
-    virtual void SetTDLSBusyCount(int count) = 0;
-    virtual void SetTDLSState(const std::string& state) = 0;
     // If |lock_type| is true, sets Cellular.SIMLockStatus.LockType to sim-pin,
     // otherwise clears LockType. (This will unblock a PUK locked SIM).
     // Sets RetriesLeft to the PIN retry default. LockEnabled is unaffected.
@@ -156,14 +154,6 @@
                      base::OnceClosure callback,
                      ErrorCallback error_callback) = 0;
 
-  // Calls the PerformTDLSOperation method.
-  // |callback| is called after the method call finishes.
-  virtual void PerformTDLSOperation(const dbus::ObjectPath& device_path,
-                                    const std::string& operation,
-                                    const std::string& peer,
-                                    StringCallback callback,
-                                    ErrorCallback error_callback) = 0;
-
   // Adds |ip_endpoint| to the list of tcp connections that the device should
   // monitor to wake the system from suspend.
   virtual void AddWakeOnPacketConnection(const dbus::ObjectPath& device_path,
diff --git a/chromeos/dbus/use_real_dbus_clients.gni b/chromeos/dbus/use_real_dbus_clients.gni
new file mode 100644
index 0000000..a74a7e2
--- /dev/null
+++ b/chromeos/dbus/use_real_dbus_clients.gni
@@ -0,0 +1,10 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/chromeos/args.gni")
+
+declare_args() {
+  # Instantiate real D-Bus clients instead of fakes.
+  use_real_dbus_clients = is_chromeos_device
+}
diff --git a/chromeos/memory/userspace_swap/swap_storage.cc b/chromeos/memory/userspace_swap/swap_storage.cc
index 6780ac3..cd719b31 100644
--- a/chromeos/memory/userspace_swap/swap_storage.cc
+++ b/chromeos/memory/userspace_swap/swap_storage.cc
@@ -183,13 +183,13 @@
     // we're encrypted before writing to an encrypted file system so we log a
     // warning.
     const base::FilePath swap_folder(
-        "/mnt/stateful_partition/unencrypted/swap/");
+        "/mnt/stateful_partition/unencrypted/userspace_swap.tmp/");
     if (base::PathExists(swap_folder)) {
       return swap_folder;
     }
 
-    LOG(WARNING) << "Swap folder " << swap_folder
-                 << " did not exist so userspace swap will be be disabled";
+    PLOG(WARNING) << "Swap folder " << swap_folder
+                  << " did not exist so userspace swap will be be disabled";
     return base::FilePath();
   }();
 
diff --git a/chromeos/network/fake_network_device_handler.cc b/chromeos/network/fake_network_device_handler.cc
index cc99be0..c94fb6c 100644
--- a/chromeos/network/fake_network_device_handler.cc
+++ b/chromeos/network/fake_network_device_handler.cc
@@ -12,8 +12,7 @@
 
 void FakeNetworkDeviceHandler::GetDeviceProperties(
     const std::string& device_path,
-    network_handler::DictionaryResultCallback callback,
-    const network_handler::ErrorCallback& error_callback) const {}
+    network_handler::ResultCallback callback) const {}
 
 void FakeNetworkDeviceHandler::SetDeviceProperty(
     const std::string& device_path,
@@ -60,17 +59,6 @@
 void FakeNetworkDeviceHandler::SetUsbEthernetMacAddressSource(
     const std::string& source) {}
 
-void FakeNetworkDeviceHandler::SetWifiTDLSEnabled(
-    const std::string& ip_or_mac_address,
-    bool enabled,
-    const network_handler::StringResultCallback& callback,
-    const network_handler::ErrorCallback& error_callback) {}
-
-void FakeNetworkDeviceHandler::GetWifiTDLSStatus(
-    const std::string& ip_or_mac_address,
-    const network_handler::StringResultCallback& callback,
-    const network_handler::ErrorCallback& error_callback) {}
-
 void FakeNetworkDeviceHandler::AddWifiWakeOnPacketConnection(
     const net::IPEndPoint& ip_endpoint,
     const base::Closure& callback,
diff --git a/chromeos/network/fake_network_device_handler.h b/chromeos/network/fake_network_device_handler.h
index 2a08116..3f4b60a 100644
--- a/chromeos/network/fake_network_device_handler.h
+++ b/chromeos/network/fake_network_device_handler.h
@@ -26,8 +26,7 @@
   // NetworkDeviceHandler overrides
   void GetDeviceProperties(
       const std::string& device_path,
-      network_handler::DictionaryResultCallback callback,
-      const network_handler::ErrorCallback& error_callback) const override;
+      network_handler::ResultCallback callback) const override;
 
   void SetDeviceProperty(
       const std::string& device_path,
@@ -71,17 +70,6 @@
 
   void SetUsbEthernetMacAddressSource(const std::string& source) override;
 
-  void SetWifiTDLSEnabled(
-      const std::string& ip_or_mac_address,
-      bool enabled,
-      const network_handler::StringResultCallback& callback,
-      const network_handler::ErrorCallback& error_callback) override;
-
-  void GetWifiTDLSStatus(
-      const std::string& ip_or_mac_address,
-      const network_handler::StringResultCallback& callback,
-      const network_handler::ErrorCallback& error_callback) override;
-
   void AddWifiWakeOnPacketConnection(
       const net::IPEndPoint& ip_endpoint,
       const base::Closure& callback,
diff --git a/chromeos/network/managed_network_configuration_handler_impl.cc b/chromeos/network/managed_network_configuration_handler_impl.cc
index 01df186c..81dd1927 100644
--- a/chromeos/network/managed_network_configuration_handler_impl.cc
+++ b/chromeos/network/managed_network_configuration_handler_impl.cc
@@ -1100,42 +1100,29 @@
 
   // Request the device properties. On success or failure pass (a possibly
   // modified) |shill_properties| to |send_callback|.
-  std::unique_ptr<base::DictionaryValue> shill_properties_copy_error_copy(
-      shill_properties_copy->DeepCopy());
-  auto repeating_send_callback =
-      base::AdaptCallbackForRepeating(std::move(send_callback));
   network_device_handler_->GetDeviceProperties(
       device_path,
       base::BindOnce(
-          &ManagedNetworkConfigurationHandlerImpl::GetDevicePropertiesSuccess,
+          &ManagedNetworkConfigurationHandlerImpl::OnGetDeviceProperties,
           weak_ptr_factory_.GetWeakPtr(), service_path,
-          std::move(shill_properties_copy), repeating_send_callback),
-      base::Bind(
-          &ManagedNetworkConfigurationHandlerImpl::GetDevicePropertiesFailure,
-          weak_ptr_factory_.GetWeakPtr(), service_path,
-          base::Passed(&shill_properties_copy_error_copy),
-          repeating_send_callback));
+          std::move(shill_properties_copy), std::move(send_callback)));
 }
 
-void ManagedNetworkConfigurationHandlerImpl::GetDevicePropertiesSuccess(
+void ManagedNetworkConfigurationHandlerImpl::OnGetDeviceProperties(
     const std::string& service_path,
     std::unique_ptr<base::DictionaryValue> network_properties,
     GetDevicePropertiesCallback send_callback,
     const std::string& device_path,
-    const base::DictionaryValue& device_properties) {
+    base::Optional<base::Value> device_properties) {
+  if (!device_properties) {
+    NET_LOG(ERROR) << "Error getting device properties: "
+                   << NetworkPathId(service_path);
+    std::move(send_callback).Run(service_path, std::move(network_properties));
+    return;
+  }
   // Create a "Device" dictionary in |network_properties|.
-  network_properties->SetKey(shill::kDeviceProperty, device_properties.Clone());
-  std::move(send_callback).Run(service_path, std::move(network_properties));
-}
-
-void ManagedNetworkConfigurationHandlerImpl::GetDevicePropertiesFailure(
-    const std::string& service_path,
-    std::unique_ptr<base::DictionaryValue> network_properties,
-    GetDevicePropertiesCallback send_callback,
-    const std::string& error_name,
-    std::unique_ptr<base::DictionaryValue> error_data) {
-  NET_LOG(ERROR) << "Error getting device properties: "
-                 << NetworkPathId(service_path);
+  network_properties->SetKey(shill::kDeviceProperty,
+                             std::move(*device_properties));
   std::move(send_callback).Run(service_path, std::move(network_properties));
 }
 
diff --git a/chromeos/network/managed_network_configuration_handler_impl.h b/chromeos/network/managed_network_configuration_handler_impl.h
index 8630405..907f255c 100644
--- a/chromeos/network/managed_network_configuration_handler_impl.h
+++ b/chromeos/network/managed_network_configuration_handler_impl.h
@@ -200,18 +200,12 @@
       const std::string& service_path,
       const base::DictionaryValue& shill_properties);
 
-  void GetDevicePropertiesSuccess(
+  void OnGetDeviceProperties(
       const std::string& service_path,
       std::unique_ptr<base::DictionaryValue> network_properties,
       GetDevicePropertiesCallback send_callback,
       const std::string& device_path,
-      const base::DictionaryValue& device_properties);
-  void GetDevicePropertiesFailure(
-      const std::string& service_path,
-      std::unique_ptr<base::DictionaryValue> network_properties,
-      GetDevicePropertiesCallback send_callback,
-      const std::string& error_name,
-      std::unique_ptr<base::DictionaryValue> error_data);
+      base::Optional<base::Value> device_properties);
 
   // Called from SetProperties, calls NCH::SetShillProperties.
   void SetShillProperties(
diff --git a/chromeos/network/managed_network_configuration_handler_unittest.cc b/chromeos/network/managed_network_configuration_handler_unittest.cc
index 58de85d..cdd2c84e 100644
--- a/chromeos/network/managed_network_configuration_handler_unittest.cc
+++ b/chromeos/network/managed_network_configuration_handler_unittest.cc
@@ -475,10 +475,8 @@
 
   // The passphrase isn't sent again, because it's configured by the user and
   // Shill doesn't send it on GetProperties calls.
-  expected_shill_properties->RemoveWithoutPathExpansion(
-      shill::kPassphraseProperty, nullptr);
-  expected_shill_properties->RemoveWithoutPathExpansion(
-      shill::kPassphraseRequiredProperty, nullptr);
+  expected_shill_properties->RemoveKey(shill::kPassphraseProperty);
+  expected_shill_properties->RemoveKey(shill::kPassphraseRequiredProperty);
 
   // Before setting policy, old_entry_path should exist.
   ASSERT_TRUE(GetShillProfileClient()->HasService("old_entry_path"));
@@ -620,10 +618,8 @@
 
   // The passphrase isn't sent again, because it's configured by the user and
   // Shill doesn't send it on GetProperties calls.
-  expected_shill_properties->RemoveWithoutPathExpansion(
-      shill::kPassphraseProperty, nullptr);
-  expected_shill_properties->RemoveWithoutPathExpansion(
-      shill::kPassphraseRequiredProperty, nullptr);
+  expected_shill_properties->RemoveKey(shill::kPassphraseProperty);
+  expected_shill_properties->RemoveKey(shill::kPassphraseRequiredProperty);
 
   SetPolicy(::onc::ONC_SOURCE_USER_POLICY, kUser1, "policy/policy_wifi1.onc");
   base::RunLoop().RunUntilIdle();
diff --git a/chromeos/network/mock_network_device_handler.h b/chromeos/network/mock_network_device_handler.h
index 0503d39..ab217b4 100644
--- a/chromeos/network/mock_network_device_handler.h
+++ b/chromeos/network/mock_network_device_handler.h
@@ -25,11 +25,9 @@
   MockNetworkDeviceHandler();
   virtual ~MockNetworkDeviceHandler();
 
-  MOCK_CONST_METHOD3(
-      GetDeviceProperties,
-      void(const std::string& device_path,
-           network_handler::DictionaryResultCallback callback,
-           const network_handler::ErrorCallback& error_callback));
+  MOCK_CONST_METHOD2(GetDeviceProperties,
+                     void(const std::string& device_path,
+                          network_handler::ResultCallback callback));
 
   MOCK_METHOD5(SetDeviceProperty,
                void(const std::string& device_path,
@@ -78,17 +76,6 @@
   MOCK_METHOD1(SetUsbEthernetMacAddressSource,
                void(const std::string& enabled));
 
-  MOCK_METHOD4(SetWifiTDLSEnabled,
-               void(const std::string& ip_or_mac_address,
-                    bool enabled,
-                    const network_handler::StringResultCallback& callback,
-                    const network_handler::ErrorCallback& error_callback));
-
-  MOCK_METHOD3(GetWifiTDLSStatus,
-               void(const std::string& ip_or_mac_address,
-                    const network_handler::StringResultCallback& callback,
-                    const network_handler::ErrorCallback& error_callback));
-
   MOCK_METHOD3(AddWifiWakeOnPacketConnection,
                void(const net::IPEndPoint& ip_endpoint,
                     const base::Closure& callback,
diff --git a/chromeos/network/network_connect.cc b/chromeos/network/network_connect.cc
index ad76547..9fba44a 100644
--- a/chromeos/network/network_connect.cc
+++ b/chromeos/network/network_connect.cc
@@ -282,7 +282,7 @@
   // Remove cleared properties from properties_to_set.
   for (std::vector<std::string>::iterator iter = properties_to_clear->begin();
        iter != properties_to_clear->end(); ++iter) {
-    properties_to_set->RemoveWithoutPathExpansion(*iter, NULL);
+    properties_to_set->RemoveKey(*iter);
   }
 }
 
diff --git a/chromeos/network/network_device_handler.h b/chromeos/network/network_device_handler.h
index 1ef250c..421fcd2 100644
--- a/chromeos/network/network_device_handler.h
+++ b/chromeos/network/network_device_handler.h
@@ -54,12 +54,11 @@
   NetworkDeviceHandler();
   virtual ~NetworkDeviceHandler();
 
-  // Gets the properties of the device with id |device_path|. See note on
-  // |callback| and |error_callback|, in class description above.
+  // Invokes |callback| with the properties for the device matching
+  // |device_path| on success, or nullopt on failure.
   virtual void GetDeviceProperties(
       const std::string& device_path,
-      network_handler::DictionaryResultCallback callback,
-      const network_handler::ErrorCallback& error_callback) const = 0;
+      network_handler::ResultCallback callback) const = 0;
 
   // Sets the value of property |name| on device with id |device_path| to
   // |value|. This function provides a generic setter to be used by the UI or
@@ -172,21 +171,6 @@
   // USB Ethernet device.
   virtual void SetUsbEthernetMacAddressSource(const std::string& source) = 0;
 
-  // Attempts to enable or disable TDLS for the specified IP or MAC address for
-  // the active wifi device.
-  virtual void SetWifiTDLSEnabled(
-      const std::string& ip_or_mac_address,
-      bool enabled,
-      const network_handler::StringResultCallback& callback,
-      const network_handler::ErrorCallback& error_callback) = 0;
-
-  // Returns the TDLS status for the specified IP or MAC address for
-  // the active wifi device.
-  virtual void GetWifiTDLSStatus(
-      const std::string& ip_or_mac_address,
-      const network_handler::StringResultCallback& callback,
-      const network_handler::ErrorCallback& error_callback) = 0;
-
   // Adds |ip_endpoint| to the list of tcp connections that the wifi device
   // should monitor to wake the system from suspend.
   virtual void AddWifiWakeOnPacketConnection(
diff --git a/chromeos/network/network_device_handler_impl.cc b/chromeos/network/network_device_handler_impl.cc
index e87763b..a7ee773a 100644
--- a/chromeos/network/network_device_handler_impl.cc
+++ b/chromeos/network/network_device_handler_impl.cc
@@ -25,8 +25,8 @@
 #include "chromeos/dbus/shill/shill_device_client.h"
 #include "chromeos/dbus/shill/shill_ipconfig_client.h"
 #include "chromeos/network/device_state.h"
+#include "chromeos/network/network_event_log.h"
 #include "chromeos/network/network_state_handler.h"
-#include "components/device_event_log/device_event_log.h"
 #include "dbus/object_path.h"
 #include "third_party/cros_system_api/dbus/service_constants.h"
 
@@ -52,6 +52,18 @@
   return NetworkDeviceHandler::kErrorUnknown;
 }
 
+void GetPropertiesCallback(const std::string& device_path,
+                           network_handler::ResultCallback callback,
+                           DBusMethodCallStatus call_status,
+                           base::Value result) {
+  if (call_status != DBUS_METHOD_CALL_SUCCESS) {
+    NET_LOG(ERROR) << "GetProperties failed: " << NetworkPathId(device_path)
+                   << " Status: " << call_status;
+    std::move(callback).Run(device_path, base::nullopt);
+  }
+  std::move(callback).Run(device_path, std::move(result));
+}
+
 void InvokeErrorCallback(const std::string& device_path,
                          const network_handler::ErrorCallback& error_callback,
                          const std::string& error_name) {
@@ -83,142 +95,6 @@
       base::BindOnce(&HandleShillCallFailure, device_path, error_callback));
 }
 
-// Struct containing TDLS Operation parameters.
-struct TDLSOperationParams {
-  TDLSOperationParams() : retry_count(0) {}
-  std::string operation;
-  std::string next_operation;
-  std::string ip_or_mac_address;
-  int retry_count;
-};
-
-// Forward declare for PostDelayedTask.
-void CallPerformTDLSOperation(
-    const std::string& device_path,
-    const TDLSOperationParams& params,
-    const network_handler::StringResultCallback& callback,
-    const network_handler::ErrorCallback& error_callback);
-
-void TDLSSuccessCallback(const std::string& device_path,
-                         const TDLSOperationParams& params,
-                         const network_handler::StringResultCallback& callback,
-                         const network_handler::ErrorCallback& error_callback,
-                         const std::string& result) {
-  std::string event_desc = "TDLSSuccessCallback: " + params.operation;
-  if (!result.empty())
-    event_desc += ": " + result;
-  NET_LOG(EVENT) << event_desc << ": " << device_path;
-
-  if (params.operation != shill::kTDLSStatusOperation && !result.empty()) {
-    NET_LOG(ERROR) << "Unexpected TDLS result: " + result << ": "
-                   << device_path;
-  }
-
-  TDLSOperationParams new_params;
-  const int64_t kRequestStatusDelayMs = 500;
-  int64_t request_delay_ms = 0;
-  if (params.operation == shill::kTDLSStatusOperation) {
-    // If this is the last operation, or the result is 'Nonexistent',
-    // return the result.
-    if (params.next_operation.empty() ||
-        result == shill::kTDLSNonexistentState) {
-      if (!callback.is_null())
-        callback.Run(result);
-      return;
-    }
-    // Otherwise start the next operation.
-    new_params.operation = params.next_operation;
-  } else if (params.operation == shill::kTDLSDiscoverOperation) {
-    // Send a delayed Status request followed by a Setup request.
-    request_delay_ms = kRequestStatusDelayMs;
-    new_params.operation = shill::kTDLSStatusOperation;
-    new_params.next_operation = shill::kTDLSSetupOperation;
-  } else if (params.operation == shill::kTDLSSetupOperation ||
-             params.operation == shill::kTDLSTeardownOperation) {
-    // Send a delayed Status request.
-    request_delay_ms = kRequestStatusDelayMs;
-    new_params.operation = shill::kTDLSStatusOperation;
-  } else {
-    NET_LOG(ERROR) << "Unexpected TDLS operation: " + params.operation;
-    return;
-  }
-
-  new_params.ip_or_mac_address = params.ip_or_mac_address;
-
-  base::TimeDelta request_delay;
-  if (!ShillDeviceClient::Get()->GetTestInterface())
-    request_delay = base::TimeDelta::FromMilliseconds(request_delay_ms);
-
-  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
-      FROM_HERE,
-      base::BindOnce(&CallPerformTDLSOperation, device_path, new_params,
-                     callback, error_callback),
-      request_delay);
-}
-
-void TDLSErrorCallback(const std::string& device_path,
-                       const TDLSOperationParams& params,
-                       const network_handler::StringResultCallback& callback,
-                       const network_handler::ErrorCallback& error_callback,
-                       const std::string& dbus_error_name,
-                       const std::string& dbus_error_message) {
-  // If a Setup operation receives an InProgress error, retry.
-  const int kMaxRetries = 5;
-  if ((params.operation == shill::kTDLSDiscoverOperation ||
-       params.operation == shill::kTDLSSetupOperation) &&
-      dbus_error_name == shill::kErrorResultInProgress &&
-      params.retry_count < kMaxRetries) {
-    TDLSOperationParams retry_params = params;
-    ++retry_params.retry_count;
-    NET_LOG(EVENT) << "TDLS Retry: " << params.retry_count << ": "
-                   << device_path;
-    const int64_t kReRequestDelayMs = 1000;
-    base::TimeDelta request_delay;
-    if (!ShillDeviceClient::Get()->GetTestInterface())
-      request_delay = base::TimeDelta::FromMilliseconds(kReRequestDelayMs);
-
-    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
-        FROM_HERE,
-        base::BindOnce(&CallPerformTDLSOperation, device_path, retry_params,
-                       callback, error_callback),
-        request_delay);
-    return;
-  }
-
-  NET_LOG(ERROR) << "TDLS Operation: " << params.operation
-                 << " Error: " << dbus_error_name << ": " << dbus_error_message
-                 << ": " << device_path;
-  if (error_callback.is_null())
-    return;
-
-  const std::string error_name =
-      dbus_error_name == shill::kErrorResultInProgress
-          ? NetworkDeviceHandler::kErrorTimeout
-          : NetworkDeviceHandler::kErrorUnknown;
-  const std::string& error_detail = params.ip_or_mac_address;
-  std::unique_ptr<base::DictionaryValue> error_data(
-      network_handler::CreateDBusErrorData(device_path, error_name,
-                                           error_detail, dbus_error_name,
-                                           dbus_error_message));
-  error_callback.Run(error_name, std::move(error_data));
-}
-
-void CallPerformTDLSOperation(
-    const std::string& device_path,
-    const TDLSOperationParams& params,
-    const network_handler::StringResultCallback& callback,
-    const network_handler::ErrorCallback& error_callback) {
-  LOG(ERROR) << "TDLS: " << params.operation;
-  NET_LOG(EVENT) << "CallPerformTDLSOperation: " << params.operation << ": "
-                 << device_path;
-  ShillDeviceClient::Get()->PerformTDLSOperation(
-      dbus::ObjectPath(device_path), params.operation, params.ip_or_mac_address,
-      base::BindOnce(&TDLSSuccessCallback, device_path, params, callback,
-                     error_callback),
-      base::BindOnce(&TDLSErrorCallback, device_path, params, callback,
-                     error_callback));
-}
-
 }  // namespace
 
 NetworkDeviceHandlerImpl::NetworkDeviceHandlerImpl() = default;
@@ -230,12 +106,10 @@
 
 void NetworkDeviceHandlerImpl::GetDeviceProperties(
     const std::string& device_path,
-    network_handler::DictionaryResultCallback callback,
-    const network_handler::ErrorCallback& error_callback) const {
+    network_handler::ResultCallback callback) const {
   ShillDeviceClient::Get()->GetProperties(
       dbus::ObjectPath(device_path),
-      base::BindOnce(&network_handler::GetPropertiesCallback,
-                     std::move(callback), error_callback, device_path));
+      base::BindOnce(&GetPropertiesCallback, device_path, std::move(callback)));
 }
 
 void NetworkDeviceHandlerImpl::SetDeviceProperty(
@@ -343,38 +217,6 @@
   ApplyUsbEthernetMacAddressSourceToShill();
 }
 
-void NetworkDeviceHandlerImpl::SetWifiTDLSEnabled(
-    const std::string& ip_or_mac_address,
-    bool enabled,
-    const network_handler::StringResultCallback& callback,
-    const network_handler::ErrorCallback& error_callback) {
-  const DeviceState* device_state = GetWifiDeviceState(error_callback);
-  if (!device_state)
-    return;
-
-  TDLSOperationParams params;
-  params.operation =
-      enabled ? shill::kTDLSDiscoverOperation : shill::kTDLSTeardownOperation;
-  params.ip_or_mac_address = ip_or_mac_address;
-  CallPerformTDLSOperation(device_state->path(), params, callback,
-                           error_callback);
-}
-
-void NetworkDeviceHandlerImpl::GetWifiTDLSStatus(
-    const std::string& ip_or_mac_address,
-    const network_handler::StringResultCallback& callback,
-    const network_handler::ErrorCallback& error_callback) {
-  const DeviceState* device_state = GetWifiDeviceState(error_callback);
-  if (!device_state)
-    return;
-
-  TDLSOperationParams params;
-  params.operation = shill::kTDLSStatusOperation;
-  params.ip_or_mac_address = ip_or_mac_address;
-  CallPerformTDLSOperation(device_state->path(), params, callback,
-                           error_callback);
-}
-
 void NetworkDeviceHandlerImpl::AddWifiWakeOnPacketConnection(
     const net::IPEndPoint& ip_endpoint,
     const base::Closure& callback,
@@ -517,8 +359,7 @@
           device_state->path(),
           base::BindOnce(
               &NetworkDeviceHandlerImpl::HandleMACAddressRandomization,
-              weak_ptr_factory_.GetWeakPtr()),
-          network_handler::ErrorCallback());
+              weak_ptr_factory_.GetWeakPtr()));
       return;
     case MACAddressRandomizationSupport::SUPPORTED:
       SetDevicePropertyInternal(
@@ -665,10 +506,12 @@
 
 void NetworkDeviceHandlerImpl::HandleMACAddressRandomization(
     const std::string& device_path,
-    const base::DictionaryValue& properties) {
-  bool supported;
-  if (!properties.GetBooleanWithoutPathExpansion(
-          shill::kMacAddressRandomizationSupportedProperty, &supported)) {
+    base::Optional<base::Value> properties) {
+  if (!properties)
+    return;
+  base::Optional<bool> supported =
+      properties->FindBoolKey(shill::kMacAddressRandomizationSupportedProperty);
+  if (!supported.has_value()) {
     if (base::SysInfo::IsRunningOnChromeOS()) {
       NET_LOG(ERROR) << "Failed to determine if device " << device_path
                      << " supports MAC address randomization";
@@ -677,7 +520,7 @@
   }
 
   // Try to set MAC address randomization if it's supported.
-  if (supported) {
+  if (*supported) {
     mac_addr_randomization_supported_ =
         MACAddressRandomizationSupport::SUPPORTED;
     ApplyMACAddressRandomizationToShill();
diff --git a/chromeos/network/network_device_handler_impl.h b/chromeos/network/network_device_handler_impl.h
index f9250a7..26a53ed 100644
--- a/chromeos/network/network_device_handler_impl.h
+++ b/chromeos/network/network_device_handler_impl.h
@@ -32,8 +32,7 @@
   // NetworkDeviceHandler overrides
   void GetDeviceProperties(
       const std::string& device_path,
-      network_handler::DictionaryResultCallback callback,
-      const network_handler::ErrorCallback& error_callback) const override;
+      network_handler::ResultCallback callback) const override;
 
   void SetDeviceProperty(
       const std::string& device_path,
@@ -79,17 +78,6 @@
 
   void SetUsbEthernetMacAddressSource(const std::string& source) override;
 
-  void SetWifiTDLSEnabled(
-      const std::string& ip_or_mac_address,
-      bool enabled,
-      const network_handler::StringResultCallback& callback,
-      const network_handler::ErrorCallback& error_callback) override;
-
-  void GetWifiTDLSStatus(
-      const std::string& ip_or_mac_address,
-      const network_handler::StringResultCallback& callback,
-      const network_handler::ErrorCallback& error_callback) override;
-
   void AddWifiWakeOnPacketConnection(
       const net::IPEndPoint& ip_endpoint,
       const base::Closure& callback,
@@ -178,7 +166,7 @@
   // supported, also apply |mac_addr_randomization_enabled_| to the
   // shill device.
   void HandleMACAddressRandomization(const std::string& device_path,
-                                     const base::DictionaryValue& properties);
+                                     base::Optional<base::Value> properties);
 
   // Get the DeviceState for the wifi device, if any.
   const DeviceState* GetWifiDeviceState(
diff --git a/chromeos/network/network_device_handler_unittest.cc b/chromeos/network/network_device_handler_unittest.cc
index be52833..d60aee9 100644
--- a/chromeos/network/network_device_handler_unittest.cc
+++ b/chromeos/network/network_device_handler_unittest.cc
@@ -27,6 +27,7 @@
 const char kDefaultCellularDevicePath[] = "stub_cellular_device";
 const char kUnknownCellularDevicePath[] = "unknown_cellular_device";
 const char kDefaultWifiDevicePath[] = "stub_wifi_device";
+const char kResultFailure[] = "failure";
 const char kResultSuccess[] = "success";
 const char kDefaultPin[] = "1111";
 
@@ -88,10 +89,15 @@
 
   void SuccessCallback() { result_ = kResultSuccess; }
 
-  void PropertiesSuccessCallback(const std::string& device_path,
-                                 const base::DictionaryValue& properties) {
+  void GetPropertiesCallback(const std::string& device_path,
+                             base::Optional<base::Value> properties) {
+    if (!properties) {
+      result_ = kResultFailure;
+      return;
+    }
     result_ = kResultSuccess;
-    properties_.reset(properties.DeepCopy());
+    properties_ = base::DictionaryValue::From(
+        std::make_unique<base::Value>(std::move(*properties)));
   }
 
   void StringSuccessCallback(const std::string& result) {
@@ -103,9 +109,8 @@
                            const std::string& expected_result) {
     network_device_handler_->GetDeviceProperties(
         device_path,
-        base::BindOnce(&NetworkDeviceHandlerTest::PropertiesSuccessCallback,
-                       base::Unretained(this)),
-        error_callback_);
+        base::BindOnce(&NetworkDeviceHandlerTest::GetPropertiesCallback,
+                       base::Unretained(this)));
     base::RunLoop().RunUntilIdle();
     ASSERT_EQ(expected_result, result_);
   }
@@ -423,74 +428,6 @@
       kSource3));
 }
 
-TEST_F(NetworkDeviceHandlerTest, SetWifiTDLSEnabled) {
-  // We add a wifi device by default, initial call should succeed.
-  fake_device_client_->GetTestInterface()->SetTDLSState(
-      shill::kTDLSConnectedState);
-  network_device_handler_->SetWifiTDLSEnabled(
-      "fake_ip_address", true, string_success_callback_, error_callback_);
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(shill::kTDLSConnectedState, result_);
-}
-
-TEST_F(NetworkDeviceHandlerTest, SetWifiTDLSEnabledNonexistent) {
-  // Set the TDLS state to 'Nonexistant'. Call should fail with 'Nonexistant'
-  // result.
-  fake_device_client_->GetTestInterface()->SetTDLSState(
-      shill::kTDLSNonexistentState);
-  network_device_handler_->SetWifiTDLSEnabled(
-      "fake_ip_address", true, string_success_callback_, error_callback_);
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(shill::kTDLSNonexistentState, result_);
-}
-
-TEST_F(NetworkDeviceHandlerTest, SetWifiTDLSEnabledMissing) {
-  // Remove the wifi device. Call should fail with "device missing" error.
-  fake_device_client_->GetTestInterface()->RemoveDevice(kDefaultWifiDevicePath);
-  base::RunLoop().RunUntilIdle();
-  network_device_handler_->SetWifiTDLSEnabled(
-      "fake_ip_address", true, string_success_callback_, error_callback_);
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(NetworkDeviceHandler::kErrorDeviceMissing, result_);
-}
-
-TEST_F(NetworkDeviceHandlerTest, SetWifiTDLSEnabledBusy) {
-  // Set the busy count, call should succeed after repeat attempt.
-  fake_device_client_->GetTestInterface()->SetTDLSState(
-      shill::kTDLSConnectedState);
-  fake_device_client_->GetTestInterface()->SetTDLSBusyCount(1);
-  network_device_handler_->SetWifiTDLSEnabled(
-      "fake_ip_address", true, string_success_callback_, error_callback_);
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(shill::kTDLSConnectedState, result_);
-
-  // Set the busy count to a large number, call should fail after max number
-  // of repeat attempt.
-  fake_device_client_->GetTestInterface()->SetTDLSBusyCount(100000);
-  network_device_handler_->SetWifiTDLSEnabled(
-      "fake_ip_address", true, string_success_callback_, error_callback_);
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(NetworkDeviceHandler::kErrorTimeout, result_);
-}
-
-TEST_F(NetworkDeviceHandlerTest, GetWifiTDLSStatus) {
-  // We add a wifi device by default, initial call should succeed.
-  fake_device_client_->GetTestInterface()->SetTDLSState(
-      shill::kTDLSConnectedState);
-  network_device_handler_->GetWifiTDLSStatus(
-      "fake_ip_address", string_success_callback_, error_callback_);
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(shill::kTDLSConnectedState, result_);
-
-  // Remove the wifi device. Call should fail with "device missing" error.
-  fake_device_client_->GetTestInterface()->RemoveDevice(kDefaultWifiDevicePath);
-  base::RunLoop().RunUntilIdle();
-  network_device_handler_->GetWifiTDLSStatus(
-      "fake_ip_address", string_success_callback_, error_callback_);
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(NetworkDeviceHandler::kErrorDeviceMissing, result_);
-}
-
 TEST_F(NetworkDeviceHandlerTest, RequirePin) {
   // Test that the success callback gets called.
   network_device_handler_->RequirePin(kDefaultCellularDevicePath, true,
diff --git a/chromeos/network/network_handler_callbacks.h b/chromeos/network/network_handler_callbacks.h
index a0c8b95..0675d5b 100644
--- a/chromeos/network/network_handler_callbacks.h
+++ b/chromeos/network/network_handler_callbacks.h
@@ -23,6 +23,13 @@
 COMPONENT_EXPORT(CHROMEOS_NETWORK) extern const char kDbusErrorName[];
 COMPONENT_EXPORT(CHROMEOS_NETWORK) extern const char kDbusErrorMessage[];
 
+// Modern callback for updated methods using a single OnceCallback with an
+// optional result. This can be used when error logging is handled at the point
+// of failure and there is no need to pass additional details to the caller,
+// other than a nullopt to indicate failure.
+using ResultCallback = base::OnceCallback<void(const std::string& service_path,
+                                               base::Optional<base::Value>)>;
+
 // An error callback used by both the configuration handler and the state
 // handler to receive error results from the API.
 typedef base::RepeatingCallback<void(
diff --git a/chromeos/network/onc/onc_certificate_importer_impl_unittest.cc b/chromeos/network/onc/onc_certificate_importer_impl_unittest.cc
index 647fef7..1c4191d 100644
--- a/chromeos/network/onc/onc_certificate_importer_impl_unittest.cc
+++ b/chromeos/network/onc/onc_certificate_importer_impl_unittest.cc
@@ -74,16 +74,13 @@
                                bool expected_import_success) {
     std::unique_ptr<base::DictionaryValue> onc =
         test_utils::ReadTestDictionary(filename);
-    std::unique_ptr<base::Value> certificates_value;
-    base::ListValue* certificates = NULL;
-    onc->RemoveWithoutPathExpansion(::onc::toplevel_config::kCertificates,
-                                    &certificates_value);
-    certificates_value.release()->GetAsList(&certificates);
-    onc_certificates_.reset(certificates);
+    base::Optional<base::Value> certificates_value =
+        onc->ExtractKey(::onc::toplevel_config::kCertificates);
+    onc_certificates_ = std::move(*certificates_value);
 
     CertificateImporterImpl importer(task_runner_, test_nssdb_.get());
     auto onc_parsed_certificates =
-        std::make_unique<OncParsedCertificates>(*certificates);
+        std::make_unique<OncParsedCertificates>(onc_certificates_);
     EXPECT_EQ(expected_parse_success, !onc_parsed_certificates->has_error());
     switch (import_type) {
       case ImportType::kClientCertificatesOnly:
@@ -132,9 +129,10 @@
                 certificate::GetCertType(private_list_[0].get()));
     }
 
-    base::DictionaryValue* certificate = NULL;
-    onc_certificates_->GetDictionary(0, &certificate);
-    certificate->GetStringWithoutPathExpansion(::onc::certificate::kGUID, guid);
+    const base::Value& certificate = onc_certificates_.GetList()[0];
+    const std::string* guid_value =
+        certificate.FindStringKey(::onc::certificate::kGUID);
+    *guid = *guid_value;
   }
 
   // Certificates and the NSSCertDatabase depend on these test DBs. Destroy them
@@ -145,7 +143,7 @@
   scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
   std::unique_ptr<base::ThreadTaskRunnerHandle> thread_task_runner_handle_;
   std::unique_ptr<net::NSSCertDatabaseChromeOS> test_nssdb_;
-  std::unique_ptr<base::ListValue> onc_certificates_;
+  base::Value onc_certificates_;
   // List of certs in the nssdb's public slot.
   net::ScopedCERTCertificateList public_list_;
   // List of certs in the nssdb's "private" slot.
@@ -184,7 +182,7 @@
   AddCertificatesFromFile("managed_toplevel2.onc", ImportType::kAllCertificates,
                           true /* expected_parse_success */,
                           true /* expected_import_success */);
-  EXPECT_EQ(onc_certificates_->GetSize(), public_list_.size());
+  EXPECT_EQ(onc_certificates_.GetList().size(), public_list_.size());
   EXPECT_TRUE(private_list_.empty());
   EXPECT_EQ(2ul, public_list_.size());
 }
@@ -204,7 +202,7 @@
   AddCertificatesFromFile(
       "toplevel_partially_invalid.onc", ImportType::kAllCertificates,
       false /* expected_parse_success */, true /* expected_import_success */);
-  EXPECT_EQ(3ul, onc_certificates_->GetSize());
+  EXPECT_EQ(3ul, onc_certificates_.GetList().size());
   EXPECT_EQ(1ul, private_list_.size());
   EXPECT_TRUE(public_list_.empty());
 }
diff --git a/chromeos/network/onc/onc_normalizer.cc b/chromeos/network/onc/onc_normalizer.cc
index e05efa4..a4bac43 100644
--- a/chromeos/network/onc/onc_normalizer.cc
+++ b/chromeos/network/onc/onc_normalizer.cc
@@ -44,7 +44,7 @@
     return std::unique_ptr<base::DictionaryValue>();
 
   if (remove_recommended_fields_)
-    normalized->RemoveWithoutPathExpansion(::onc::kRecommended, nullptr);
+    normalized->RemoveKey(::onc::kRecommended);
 
   if (&signature == &kCertificateSignature)
     NormalizeCertificate(normalized.get());
@@ -169,12 +169,10 @@
   bool remove = false;
   network->GetBooleanWithoutPathExpansion(::onc::kRemove, &remove);
   if (remove) {
-    network->RemoveWithoutPathExpansion(::onc::network_config::kStaticIPConfig,
-                                        nullptr);
-    network->RemoveWithoutPathExpansion(::onc::network_config::kName, nullptr);
-    network->RemoveWithoutPathExpansion(::onc::network_config::kProxySettings,
-                                        nullptr);
-    network->RemoveWithoutPathExpansion(::onc::network_config::kType, nullptr);
+    network->RemoveKey(::onc::network_config::kStaticIPConfig);
+    network->RemoveKey(::onc::network_config::kName);
+    network->RemoveKey(::onc::network_config::kProxySettings);
+    network->RemoveKey(::onc::network_config::kType);
     // Fields dependent on kType are removed afterwards, too.
   }
 
diff --git a/chromeos/network/onc/onc_utils.cc b/chromeos/network/onc/onc_utils.cc
index 68b7d0c..ee51632f 100644
--- a/chromeos/network/onc/onc_utils.cc
+++ b/chromeos/network/onc/onc_utils.cc
@@ -240,7 +240,7 @@
   if (!GUIDRefToPEMEncoding(certs_by_guid, guid_ref, &pem_encoded))
     return false;
 
-  onc_object->RemoveWithoutPathExpansion(key_guid_ref, nullptr);
+  onc_object->RemoveKey(key_guid_ref);
   onc_object->SetKey(key_pem, base::Value(pem_encoded));
   return true;
 }
@@ -278,7 +278,7 @@
     pem_list->AppendString(pem_encoded);
   }
 
-  onc_object->RemoveWithoutPathExpansion(key_guid_ref_list, nullptr);
+  onc_object->RemoveKey(key_guid_ref_list);
   onc_object->SetWithoutPathExpansion(key_pem_list, std::move(pem_list));
   return true;
 }
@@ -299,7 +299,7 @@
 
   std::unique_ptr<base::ListValue> pem_list(new base::ListValue);
   pem_list->AppendString(pem_encoded);
-  onc_object->RemoveWithoutPathExpansion(key_guid_ref, nullptr);
+  onc_object->RemoveKey(key_guid_ref);
   onc_object->SetWithoutPathExpansion(key_pem_list, std::move(pem_list));
   return true;
 }
@@ -316,7 +316,7 @@
     if (onc_object->HasKey(key_guid_ref)) {
       LOG(ERROR) << "Found both " << key_guid_refs << " and " << key_guid_ref
                  << ". Ignoring and removing the latter.";
-      onc_object->RemoveWithoutPathExpansion(key_guid_ref, nullptr);
+      onc_object->RemoveKey(key_guid_ref);
     }
     return ResolveCertRefList(certs_by_guid, key_guid_refs, key_pem_list,
                               onc_object);
diff --git a/chromeos/network/onc/onc_validator.cc b/chromeos/network/onc/onc_validator.cc
index 3dfedb2b..a31a431 100644
--- a/chromeos/network/onc/onc_validator.cc
+++ b/chromeos/network/onc/onc_validator.cc
@@ -269,10 +269,10 @@
     base::DictionaryValue* result) {
   CHECK(result);
 
-  std::unique_ptr<base::Value> recommended_value;
+  base::Optional<base::Value> recommended_value =
+      result->ExtractKey(::onc::kRecommended);
   // This remove passes ownership to |recommended_value|.
-  if (!result->RemoveWithoutPathExpansion(::onc::kRecommended,
-                                          &recommended_value)) {
+  if (!recommended_value) {
     return true;
   }
 
@@ -567,7 +567,7 @@
             << ::onc::wifi::kHexSSID << "' contain inconsistent values.";
         AddValidationIssue(false /* is_error */, msg.str());
         path_.pop_back();
-        object->RemoveWithoutPathExpansion(::onc::wifi::kSSID, nullptr);
+        object->RemoveKey(::onc::wifi::kSSID);
       }
     }
   }
diff --git a/chromeos/network/policy_util.cc b/chromeos/network/policy_util.cc
index 8f656c9..c3c9c800 100644
--- a/chromeos/network/policy_util.cc
+++ b/chromeos/network/policy_util.cc
@@ -68,7 +68,7 @@
     }
   }
   for (auto field_name : entries_to_remove)
-    onc_object->RemoveWithoutPathExpansion(field_name, nullptr);
+    onc_object->RemoveKey(field_name);
 }
 
 // Returns true if |policy| matches |actual_network|, which must be part of a
diff --git a/chromeos/printing/ppd_provider.h b/chromeos/printing/ppd_provider.h
index 4d71605..df346bb 100644
--- a/chromeos/printing/ppd_provider.h
+++ b/chromeos/printing/ppd_provider.h
@@ -188,6 +188,11 @@
   // localized in the default browser locale or the closest available fallback.
   //
   // |cb| will be called on the invoking thread, and will be sequenced.
+  //
+  // PpdProvider will enqueue calls to this method and answer them in
+  // the order received; it will opt to invoke |cb| with failure if the
+  // queue grows overlong, failing the oldest calls first. The exact
+  // queue length at which this occurs is unspecified.
   virtual void ResolveManufacturers(ResolveManufacturersCallback cb) = 0;
 
   // Get all models from a given manufacturer, localized in the
@@ -223,6 +228,11 @@
 
   // For a given PpdReference, retrieve the make and model strings used to
   // construct that reference.
+  //
+  // PpdProvider will enqueue calls to this method and answer them in
+  // the order received; it will opt to invoke |cb| with failure if the
+  // queue grows overlong, failing the oldest calls first. The exact
+  // queue length at which this occurs is unspecified.
   virtual void ReverseLookup(const std::string& effective_make_and_model,
                              ReverseLookupCallback cb) = 0;
 
diff --git a/chromeos/printing/ppd_provider_v3.cc b/chromeos/printing/ppd_provider_v3.cc
index 5ca614d..5932849 100644
--- a/chromeos/printing/ppd_provider_v3.cc
+++ b/chromeos/printing/ppd_provider_v3.cc
@@ -4,7 +4,9 @@
 
 #include <string>
 #include <utility>
+#include <vector>
 
+#include "base/containers/queue.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/notreached.h"
 #include "base/task/post_task.h"
@@ -18,9 +20,69 @@
 namespace chromeos {
 namespace {
 
+// The exact queue length at which PpdProvider will begin to post
+// failure callbacks in response to its queue-able public methods.
+// Arbitrarily chosen.
+// See also: struct MethodDeferralContext
+constexpr size_t kMethodDeferralLimit = 20;
+
+// Helper struct for PpdProviderImpl. Allows PpdProviderImpl to defer
+// its public method calls, which PpdProviderImpl will do when the
+// PpdMetadataManager is not ready to deal with locale-sensitive PPD
+// metadata.
+//
+// Note that the semantics of this struct demand two things of the
+// deferable public methods of PpdProviderImpl:
+// 1. that they check for its presence and
+// 2. that they check its |current_method_is_being_failed| member to
+//    prevent infinite re-enqueueing of public methods once the queue
+//    is full.
+struct MethodDeferralContext {
+  MethodDeferralContext() = default;
+  ~MethodDeferralContext() = default;
+
+  // This struct is not copyable.
+  MethodDeferralContext(const MethodDeferralContext&) = delete;
+  MethodDeferralContext& operator=(const MethodDeferralContext&) = delete;
+
+  // Pops the first entry from |deferred_methods| and synchronously runs
+  // it with the intent to fail it.
+  void FailOneEnqueuedMethod() {
+    DCHECK(!current_method_is_being_failed);
+
+    // Explicitly activates the failure codepath for whatever public
+    // method of PpdProviderImpl that we'll now Run().
+    current_method_is_being_failed = true;
+
+    std::move(deferred_methods.front()).Run();
+    deferred_methods.pop();
+    current_method_is_being_failed = false;
+  }
+
+  // Dequeues and posts all |deferred_methods| onto our sequence.
+  void FlushAndPostAll() {
+    while (!deferred_methods.empty()) {
+      base::SequencedTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, std::move(deferred_methods.front()));
+      deferred_methods.pop();
+    }
+  }
+
+  bool IsFull() { return deferred_methods.size() >= kMethodDeferralLimit; }
+
+  // This bool is checked during execution of a queue-able public method
+  // of PpdProviderImpl. If it is true, then
+  // 1. the current queue-able public method was previously enqueued,
+  // 2. the deferral queue is full, and so
+  // 3. the current queue-able public method was posted for the sole
+  //    purpose of being _failed_, and should not be re-enqueued.
+  bool current_method_is_being_failed = false;
+
+  base::queue<base::OnceCallback<void()>> deferred_methods;
+};
+
 // This class implements the PpdProvider interface for the v3 metadata
 // (https://crbug.com/888189).
-
 class PpdProviderImpl : public PpdProvider {
  public:
   PpdProviderImpl(base::StringPiece browser_locale,
@@ -31,6 +93,7 @@
       : browser_locale_(std::string(browser_locale)),
         version_(current_version),
         cache_(cache),
+        deferral_context_(std::make_unique<MethodDeferralContext>()),
         metadata_manager_(std::move(metadata_manager)),
         config_cache_(std::move(config_cache)),
         file_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
@@ -40,19 +103,60 @@
   }
 
   void ResolveManufacturers(ResolveManufacturersCallback cb) override {
+    // Do we need
+    // 1. to defer this method?
+    // 2. to fail this method (which was already previously deferred)?
+    if (deferral_context_) {
+      if (deferral_context_->current_method_is_being_failed) {
+        auto failure_cb = base::BindOnce(
+            std::move(cb), PpdProvider::CallbackResultCode::INTERNAL_ERROR,
+            std::vector<std::string>());
+        base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                         std::move(failure_cb));
+        return;
+      }
+
+      if (deferral_context_->IsFull()) {
+        deferral_context_->FailOneEnqueuedMethod();
+        DCHECK(!deferral_context_->IsFull());
+      }
+      base::OnceCallback<void()> this_method =
+          base::BindOnce(&PpdProviderImpl::ResolveManufacturers,
+                         weak_factory_.GetWeakPtr(), std::move(cb));
+      deferral_context_->deferred_methods.push(std::move(this_method));
+      return;
+    }
+
     // TODO(crbug.com/888189): implement this.
   }
 
   void ResolvePrinters(const std::string& manufacturer,
                        ResolvePrintersCallback cb) override {
+    // Caller must not call ResolvePrinters() before a successful reply
+    // from ResolveManufacturers(). ResolveManufacturers() cannot have
+    // been successful if the |deferral_context_| still exists.
+    if (deferral_context_) {
+      auto failure_cb = base::BindOnce(
+          std::move(cb), PpdProvider::CallbackResultCode::INTERNAL_ERROR,
+          ResolvedPrintersList());
+      base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                       std::move(failure_cb));
+    }
+
     // TODO(crbug.com/888189): implement this.
   }
 
+  // This method depends on
+  // 1. forward indices and
+  // 2. USB indices,
+  // neither of which are locale-sensitive.
   void ResolvePpdReference(const PrinterSearchData& search_data,
                            ResolvePpdReferenceCallback cb) override {
     // TODO(crbug.com/888189): implement this.
   }
 
+  // This method depends on a successful prior call to
+  // ResolvePpdReference().
   void ResolvePpd(const Printer::PpdReference& reference,
                   ResolvePpdCallback cb) override {
     // TODO(crbug.com/888189): implement this.
@@ -60,9 +164,35 @@
 
   void ReverseLookup(const std::string& effective_make_and_model,
                      ReverseLookupCallback cb) override {
+    // Do we need
+    // 1. to defer this method?
+    // 2. to fail this method (which was already previously deferred)?
+    if (deferral_context_) {
+      if (deferral_context_->current_method_is_being_failed) {
+        auto failure_cb = base::BindOnce(
+            std::move(cb), PpdProvider::CallbackResultCode::INTERNAL_ERROR, "",
+            "");
+        base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                         std::move(failure_cb));
+        return;
+      }
+
+      if (deferral_context_->IsFull()) {
+        deferral_context_->FailOneEnqueuedMethod();
+        DCHECK(!deferral_context_->IsFull());
+      }
+      base::OnceCallback<void()> this_method = base::BindOnce(
+          &PpdProviderImpl::ReverseLookup, weak_factory_.GetWeakPtr(),
+          effective_make_and_model, std::move(cb));
+      deferral_context_->deferred_methods.push(std::move(this_method));
+      return;
+    }
+
     // TODO(crbug.com/888189): implement this.
   }
 
+  // This method depends on forward indices, which are not
+  // locale-sensitive.
   void ResolvePpdLicense(base::StringPiece effective_make_and_model,
                          ResolvePpdLicenseCallback cb) override {
     // TODO(crbug.com/888189): implement this.
@@ -72,15 +202,6 @@
   ~PpdProviderImpl() override = default;
 
  private:
-  // Called when |this| is totally ready to go; dequeues
-  // locale-sensitive method calls, including
-  // *  ResolveManufacturers(),
-  // *  ResolvePpdReference(), and
-  // *  ReverseLookup().
-  void FlushQueuedMethodCalls() {
-    // TODO(crbug.com/888189): implement this method.
-  }
-
   // Readies |metadata_manager_| to call methods which require a
   // successful callback from PpdMetadataManager::GetLocale().
   //
@@ -100,8 +221,10 @@
       TryToGetMetadataManagerLocale();
       return;
     }
-    metadata_manager_has_gotten_locale_ = true;
-    FlushQueuedMethodCalls();
+    deferral_context_->FlushAndPostAll();
+
+    // It is no longer necessary to defer public method calls.
+    deferral_context_.reset();
   }
 
   // Locale of the browser, as returned by
@@ -114,13 +237,18 @@
   // Provides PPD storage on-device.
   scoped_refptr<PpdCache> cache_;
 
+  // Used to
+  // 1. to determine if |this| should defer locale-sensitive public
+  //    method calls and
+  // 2. to defer those method calls, if necessary.
+  // These deferrals are only necessary before the |metadata_manager_|
+  // is ready to deal with locale-sensitive PPD metadata. This member is
+  // reset once deferrals are unnecessary.
+  std::unique_ptr<MethodDeferralContext> deferral_context_;
+
   // Interacts with and controls PPD metadata.
   std::unique_ptr<PpdMetadataManager> metadata_manager_;
 
-  // Denotes whether the |metadata_manager_| has successfully completed
-  // a call to its GetLocale() method.
-  bool metadata_manager_has_gotten_locale_ = false;
-
   // Fetches PPDs from the Chrome OS Printing team's serving root.
   std::unique_ptr<PrinterConfigCache> config_cache_;
 
diff --git a/chromeos/printing/ppd_provider_v3_unittest.cc b/chromeos/printing/ppd_provider_v3_unittest.cc
index 148551f..ff117e3 100644
--- a/chromeos/printing/ppd_provider_v3_unittest.cc
+++ b/chromeos/printing/ppd_provider_v3_unittest.cc
@@ -64,6 +64,13 @@
 More random contents that we don't care about.
 )";
 
+// Known number of public method calls that the PpdProvider will defer
+// before posting failures directly.
+// *  This value is left unspecified in the header.
+// *  This value must be kept in sync with the exact value in the
+//    implementation of PpdProvider.
+constexpr int kMethodDeferralLimitForTesting = 20;
+
 // Unowned raw pointers to helper classes composed into the
 // PpdProvider at construct time. Used throughout to activate testing
 // codepaths.
@@ -332,6 +339,38 @@
   base::SimpleTestClock clock_;
 };
 
+// Tests that PpdProvider enqueues a bounded number of calls to
+// ResolveManufacturers() and fails the oldest call when the queue is
+// deemed full (implementation-specified detail).
+TEST_F(PpdProviderTest, FailsOldestQueuedResolveManufacturers) {
+  auto provider = CreateProvider("en", false);
+  for (int i = kMethodDeferralLimitForTesting; i >= 0; i--) {
+    provider->ResolveManufacturers(base::BindOnce(
+        &PpdProviderTest::CaptureResolveManufacturers, base::Unretained(this)));
+  }
+  task_environment_.RunUntilIdle();
+  ASSERT_EQ(1UL, captured_resolve_manufacturers_.size());
+  EXPECT_EQ(PpdProvider::CallbackResultCode::INTERNAL_ERROR,
+            captured_resolve_manufacturers_[0].first);
+}
+
+// Tests that PpdProvider enqueues a bounded number of calls to
+// ReverseLookup() and fails the oldest call when the queue is deemed
+// full (implementation-specified detail).
+TEST_F(PpdProviderTest, FailsOldestQueuedReverseLookup) {
+  auto provider = CreateProvider("en", false);
+  for (int i = kMethodDeferralLimitForTesting; i >= 0; i--) {
+    provider->ReverseLookup(
+        "some effective-make-and-model string",
+        base::BindOnce(&PpdProviderTest::CaptureReverseLookup,
+                       base::Unretained(this)));
+  }
+  task_environment_.RunUntilIdle();
+  ASSERT_EQ(1UL, captured_reverse_lookup_.size());
+  EXPECT_EQ(PpdProvider::CallbackResultCode::INTERNAL_ERROR,
+            captured_reverse_lookup_[0].code);
+}
+
 // Test that we get back manufacturer maps as expected.
 TEST_F(PpdProviderTest, ManufacturersFetch) {
   auto provider = CreateProvider("en", false);
diff --git a/chromeos/printing/printer_configuration.cc b/chromeos/printing/printer_configuration.cc
index e4f2e4d..ed262af 100644
--- a/chromeos/printing/printer_configuration.cc
+++ b/chromeos/printing/printer_configuration.cc
@@ -4,108 +4,103 @@
 
 #include "chromeos/printing/printer_configuration.h"
 
+#include "base/containers/flat_set.h"
 #include "base/guid.h"
 #include "base/logging.h"
 #include "base/optional.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
 #include "chromeos/printing/printing_constants.h"
-#include "chromeos/printing/uri_components.h"
+#include "chromeos/printing/uri.h"
 #include "net/base/ip_endpoint.h"
 #include "url/third_party/mozilla/url_parse.h"
 #include "url/url_constants.h"
 
 namespace chromeos {
 
+namespace {
+std::string ToString(Uri::ParserStatus status) {
+  switch (status) {
+    case Uri::ParserStatus::kInvalidPercentEncoding:
+      return "invalid percent encoding";
+    case Uri::ParserStatus::kDisallowedASCIICharacter:
+      return "disallowed ASCII character";
+    case Uri::ParserStatus::kInvalidUTF8Character:
+      return "invalid UTF-8 character";
+    case Uri::ParserStatus::kInvalidScheme:
+      return "invalid scheme";
+    case Uri::ParserStatus::kInvalidPortNumber:
+      return "invalid port number";
+    case Uri::ParserStatus::kRelativePathsNotAllowed:
+      return "relative paths not allowed";
+    case Uri::ParserStatus::kEmptySegmentInPath:
+      return "empty segment in path";
+    case Uri::ParserStatus::kEmptyParameterNameInQuery:
+      return "empty parameter name in query";
+    case Uri::ParserStatus::kNoErrors:
+      return "no errors";
+  }
+  return "unknown error";
+}
+}  // namespace
+
+bool IsValidPrinterUri(const Uri& uri, std::string* error_message) {
+  static const base::flat_set<std::string> kKnownSchemes = {
+      "http", "https", "ipp", "ipps", "ippusb", "lpd", "socket", "usb"};
+  static const std::string kPrefix = "Malformed printer URI: ";
+
+  if (kKnownSchemes.count(uri.GetScheme()) == 0) {
+    if (error_message)
+      *error_message = kPrefix + "unknown or missing scheme";
+    return false;
+  }
+
+  // Only printer URIs with the lpd scheme are allowed to have Userinfo.
+  if (!uri.GetUserinfo().empty() && uri.GetScheme() != "lpd") {
+    if (error_message)
+      *error_message = kPrefix + "user info is not allowed for this scheme";
+    return false;
+  }
+
+  if (uri.GetHost().empty()) {
+    if (error_message)
+      *error_message = kPrefix + "missing host";
+    return false;
+  }
+
+  if (uri.GetScheme() == "ippusb" || uri.GetScheme() == "usb") {
+    if (uri.GetPort() > -1) {
+      if (error_message)
+        *error_message = kPrefix + "port is not allowed for this scheme";
+      return false;
+    }
+    if (uri.GetPath().empty()) {
+      if (error_message)
+        *error_message = kPrefix + "path is required for this scheme";
+      return false;
+    }
+  }
+
+  if (uri.GetScheme() == "socket" && !uri.GetPath().empty()) {
+    if (error_message)
+      *error_message = kPrefix + "path is not allowed for this scheme";
+    return false;
+  }
+
+  if (!uri.GetFragment().empty()) {
+    if (error_message)
+      *error_message = kPrefix + "fragment is not allowed";
+    return false;
+  }
+
+  return true;
+}
+
 bool Printer::PpdReference::IsFilled() const {
   return autoconf || !user_supplied_ppd_url.empty() ||
          !effective_make_and_model.empty();
 }
 
-// Returns true if the scheme is both valid and non-empty.
-bool IsSchemeValid(const url::Parsed& parsed) {
-  return parsed.scheme.is_valid() && parsed.scheme.is_nonempty();
-}
-
-// Returns true if |parsed| contains a valid port. A valid port is one that
-// either contains a valid value or is completely missing.
-bool IsPortValid(const url::Parsed& parsed) {
-  // A length of -1 indicates that the port is missing.
-  return parsed.port.len == -1 ||
-         (parsed.port.is_valid() && parsed.port.is_nonempty());
-}
-
-// Returns |printer_uri| broken into components if it represents a valid uri. A
-// valid uri contains a scheme, host, and a valid or missing port number.
-// Optionally, the uri contains a path.
-base::Optional<UriComponents> ParseUri(const std::string& printer_uri) {
-  const char* uri_ptr = printer_uri.c_str();
-  url::Parsed parsed;
-  url::ParseStandardURL(uri_ptr, printer_uri.length(), &parsed);
-  if (!IsSchemeValid(parsed) || !parsed.host.is_valid() ||
-      !IsPortValid(parsed)) {
-    LOG(WARNING) << "Could not parse printer uri";
-    return {};
-  }
-  base::StringPiece scheme(&uri_ptr[parsed.scheme.begin], parsed.scheme.len);
-  base::StringPiece host(&uri_ptr[parsed.host.begin], parsed.host.len);
-  base::StringPiece path =
-      parsed.path.is_valid()
-          ? base::StringPiece(&uri_ptr[parsed.path.begin], parsed.path.len)
-          : "";
-
-  int port = ParsePort(uri_ptr, parsed.port);
-  if (port == url::SpecialPort::PORT_INVALID) {
-    LOG(WARNING) << "Port is invalid";
-    return {};
-  }
-  // Port not specified.
-  if (port == url::SpecialPort::PORT_UNSPECIFIED) {
-    if (scheme == kIppScheme) {
-      port = kIppPort;
-    } else if (scheme == kIppsScheme) {
-      port = kIppsPort;
-    }
-  }
-
-  bool encrypted = scheme != kIppScheme;
-  return UriComponents(encrypted, scheme.as_string(), host.as_string(), port,
-                       path.as_string());
-}
-
-namespace {
-
-// Returns the index of the first character representing the hostname in |uri|.
-// Returns npos if the start of the hostname is not found.
-//
-// We should use GURL to do this except that uri could start with ipp:// which
-// is not a standard url scheme (according to GURL).
-size_t HostnameStart(base::StringPiece uri) {
-  size_t scheme_separator_start = uri.find(url::kStandardSchemeSeparator);
-  if (scheme_separator_start == base::StringPiece::npos) {
-    return base::StringPiece::npos;
-  }
-  return scheme_separator_start + strlen(url::kStandardSchemeSeparator);
-}
-
-base::StringPiece HostAndPort(base::StringPiece uri) {
-  size_t hostname_start = HostnameStart(uri);
-  if (hostname_start == base::StringPiece::npos) {
-    return "";
-  }
-
-  size_t hostname_end = uri.find("/", hostname_start);
-  if (hostname_end == base::StringPiece::npos) {
-    // No trailing slash.  Use end of string.
-    hostname_end = uri.size();
-  }
-
-  CHECK_GE(hostname_end, hostname_start);
-  return uri.substr(hostname_start, hostname_end - hostname_start);
-}
-
-}  // namespace
-
 Printer::Printer() : id_(base::GenerateGUID()), source_(SRC_USER_PREFS) {}
 
 Printer::Printer(const std::string& id) : id_(id), source_(SRC_USER_PREFS) {
@@ -119,58 +114,75 @@
 
 Printer::~Printer() = default;
 
+bool Printer::SetUri(const Uri& uri, std::string* error_message) {
+  if (!IsValidPrinterUri(uri, error_message))
+    return false;
+  uri_ = uri;
+  return true;
+}
+
+bool Printer::SetUri(const std::string& uri, std::string* error_message) {
+  Uri parsed_uri(uri);
+  const Uri::ParserError& parser_status = parsed_uri.GetLastParsingError();
+  if (parser_status.status == Uri::ParserStatus::kNoErrors)
+    return SetUri(parsed_uri, error_message);
+  if (error_message) {
+    *error_message = "Malformed URI: " + ToString(parser_status.status);
+  }
+  return false;
+}
+
 bool Printer::IsIppEverywhere() const {
   return ppd_reference_.autoconf;
 }
 
 net::HostPortPair Printer::GetHostAndPort() const {
-  if (uri_.empty()) {
+  if (!HasUri()) {
     return net::HostPortPair();
   }
 
-  return net::HostPortPair::FromString(HostAndPort(uri_).as_string());
+  return net::HostPortPair(uri_.GetHost(), uri_.GetPort());
 }
 
-std::string Printer::ReplaceHostAndPort(const net::IPEndPoint& ip) const {
-  if (uri_.empty()) {
-    return "";
+Uri Printer::ReplaceHostAndPort(const net::IPEndPoint& ip) const {
+  if (!HasUri()) {
+    return Uri();
   }
 
-  size_t hostname_start = HostnameStart(uri_);
-  if (hostname_start == base::StringPiece::npos) {
-    return "";
+  const std::string host = ip.ToStringWithoutPort();
+  if (host.empty()) {
+    return Uri();
   }
-  size_t host_port_len = HostAndPort(uri_).length();
-  return base::JoinString({uri_.substr(0, hostname_start), ip.ToString(),
-                           uri_.substr(hostname_start + host_port_len)},
-                          "");
+  Uri uri = uri_;
+  uri.SetHost(host);
+  uri.SetPort(ip.port());
+
+  return uri;
 }
 
 Printer::PrinterProtocol Printer::GetProtocol() const {
-  const base::StringPiece uri(uri_);
-
-  if (uri.starts_with("usb:"))
+  if (uri_.GetScheme() == "usb")
     return PrinterProtocol::kUsb;
 
-  if (uri.starts_with("ipp:"))
+  if (uri_.GetScheme() == "ipp")
     return PrinterProtocol::kIpp;
 
-  if (uri.starts_with("ipps:"))
+  if (uri_.GetScheme() == "ipps")
     return PrinterProtocol::kIpps;
 
-  if (uri.starts_with("http:"))
+  if (uri_.GetScheme() == "http")
     return PrinterProtocol::kHttp;
 
-  if (uri.starts_with("https:"))
+  if (uri_.GetScheme() == "https")
     return PrinterProtocol::kHttps;
 
-  if (uri.starts_with("socket:"))
+  if (uri_.GetScheme() == "socket")
     return PrinterProtocol::kSocket;
 
-  if (uri.starts_with("lpd:"))
+  if (uri_.GetScheme() == "lpd")
     return PrinterProtocol::kLpd;
 
-  if (uri.starts_with("ippusb:"))
+  if (uri_.GetScheme() == "ippusb")
     return PrinterProtocol::kIppUsb;
 
   return PrinterProtocol::kUnknown;
@@ -215,8 +227,4 @@
   }
 }
 
-base::Optional<UriComponents> Printer::GetUriComponents() const {
-  return chromeos::ParseUri(uri_);
-}
-
 }  // namespace chromeos
diff --git a/chromeos/printing/printer_configuration.h b/chromeos/printing/printer_configuration.h
index e83918b4..ce576ce 100644
--- a/chromeos/printing/printer_configuration.h
+++ b/chromeos/printing/printer_configuration.h
@@ -10,6 +10,7 @@
 #include "base/optional.h"
 #include "chromeos/chromeos_export.h"
 #include "chromeos/printing/cups_printer_status.h"
+#include "chromeos/printing/uri.h"
 #include "net/base/host_port_pair.h"
 
 namespace net {
@@ -18,14 +19,6 @@
 
 namespace chromeos {
 
-class UriComponents;
-
-// Parses |printer_uri| into its components and returns an optional
-// UriComponents depending on whether or not |printer_uri| was parsed
-// successfully.
-CHROMEOS_EXPORT base::Optional<UriComponents> ParseUri(
-    const std::string& printer_uri);
-
 // Classes of printers tracked.  See doc/cups_printer_management.md for
 // details on what these mean.
 enum class CHROMEOS_EXPORT PrinterClass {
@@ -35,6 +28,27 @@
   kSaved
 };
 
+// This function checks if the given URI is a valid printer URI. |uri| is
+// considered to be a valid printer URI if it has one of the scheme listed in
+// the table below and meets the criteria defined there.
+//
+//  scheme  | userinfo |   host   |   port   |   path   |  query   | fragment
+// ---------+----------+----------+----------+----------+----------+----------
+//   http   |    NO    | required | optional | optional | optional |    NO
+//   https  |    NO    | required | optional | optional | optional |    NO
+//   ipp    |    NO    | required | optional | optional | optional |    NO
+//   ipps   |    NO    | required | optional | optional | optional |    NO
+//   lpd    | optional | required | optional | optional | optional |    NO
+//   socket |    NO    | required | optional |    NO    | optional |    NO
+//   ippusb |    NO    | required |    NO    | required | optional |    NO
+//   usb    |    NO    | required |    NO    | required | optional |    NO
+//
+// If the given |uri| does not meet the criteria the function returns false and
+// set an error message in |error_message| (if it is not nullptr). The message
+// has the prefix "Malformed printer URI: ".
+bool CHROMEOS_EXPORT IsValidPrinterUri(const Uri& uri,
+                                       std::string* error_message = nullptr);
+
 class CHROMEOS_EXPORT Printer {
  public:
   // Information needed to find the PPD file for this printer.
@@ -131,8 +145,14 @@
     make_and_model_ = make_and_model;
   }
 
-  const std::string& uri() const { return uri_; }
-  void set_uri(const std::string& uri) { uri_ = uri; }
+  const Uri& uri() const { return uri_; }
+
+  // These methods set |uri| as a new uri. If |uri| is incorrect or does not
+  // pass the IsValidPrinterUri(...) function defined above, no changes are made
+  // to the object and false is returned. If |error_message| is not nullptr,
+  // the error message is written there when the methods return false.
+  bool SetUri(const Uri& uri, std::string* error_message = nullptr);
+  bool SetUri(const std::string& uri, std::string* error_message = nullptr);
 
   const PpdReference& ppd_reference() const { return ppd_reference_; }
   PpdReference* mutable_ppd_reference() { return &ppd_reference_; }
@@ -150,9 +170,8 @@
   const std::string& uuid() const { return uuid_; }
   void set_uuid(const std::string& uuid) { uuid_ = uuid; }
 
-  // Returns true if the printer should be automatically configured using
-  // IPP Everywhere.  Computed using information from |ppd_reference_| and
-  // |uri_|.
+  // Returns true if the printer should be automatically configured using IPP
+  // Everywhere.  Computed using information from |ppd_reference_| and |uri_|.
   bool IsIppEverywhere() const;
 
   // Returns the hostname and port for |uri_|.  Assumes that the uri is
@@ -160,15 +179,14 @@
   net::HostPortPair GetHostAndPort() const;
 
   // Returns the |uri_| with the host and port replaced with |ip|.  Returns an
-  // empty string if |uri_| is empty.
-  std::string ReplaceHostAndPort(const net::IPEndPoint& ip) const;
+  // empty Uri if |uri_| is empty.
+  Uri ReplaceHostAndPort(const net::IPEndPoint& ip) const;
 
   // Returns the printer protocol the printer is configured with.
   Printer::PrinterProtocol GetProtocol() const;
 
   // Returns true if the current protocol of the printer is one of the following
-  // "network protocols":
-  //   [kIpp, kIpps, kHttp, kHttps, kSocket, kLpd]
+  // "network protocols": [kIpp, kIpps, kHttp, kHttps, kSocket, kLpd]
   bool HasNetworkProtocol() const;
 
   // Returns true if the current protocol of the printer is either kUSb or
@@ -179,14 +197,12 @@
   // secure (kIpps or kHttps).
   bool HasSecureProtocol() const;
 
+  // Returns true if the printer uri is set and false when the uri is empty.
+  bool HasUri() const { return !uri_.GetScheme().empty(); }
+
   Source source() const { return source_; }
   void set_source(const Source source) { source_ = source; }
 
-  // Parses the printers's uri into its components and returns an optional
-  // containing a UriComponents object depending on whether or not the uri was
-  // successfully parsed.
-  base::Optional<UriComponents> GetUriComponents() const;
-
   const CupsPrinterStatus& printer_status() const { return printer_status_; }
   void set_printer_status(const chromeos::CupsPrinterStatus& printer_status) {
     printer_status_ = printer_status;
@@ -219,7 +235,7 @@
 
   // The full path for the printer. Suitable for configuration in CUPS.
   // Contains protocol, hostname, port, and queue.
-  std::string uri_;
+  Uri uri_;
 
   // When non-empty, the uri to use with cups instead of uri_.  This field
   // is ephemeral, and not saved to sync service.  This allows us to do
diff --git a/chromeos/printing/printer_configuration_unittest.cc b/chromeos/printing/printer_configuration_unittest.cc
index 7d2184bb..b77e5de 100644
--- a/chromeos/printing/printer_configuration_unittest.cc
+++ b/chromeos/printing/printer_configuration_unittest.cc
@@ -3,138 +3,124 @@
 // found in the LICENSE file.
 
 #include "chromeos/printing/printer_configuration.h"
-#include "chromeos/printing/uri_components.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace {
 
 TEST(PrinterConfigurationTest, EmptyScheme) {
-  auto result = chromeos::ParseUri("://hostname.com/");
-  EXPECT_FALSE(result.has_value());
+  chromeos::Printer printer;
+  EXPECT_FALSE(printer.SetUri("://hostname.com/"));
 }
 
 TEST(PrinterConfigurationTest, JustScheme) {
-  auto result = chromeos::ParseUri("ipps://");
-  EXPECT_FALSE(result.has_value());
-}
-
-TEST(PrinterConfigurationTest, InvalidUriDanglingPort) {
-  auto result = chromeos::ParseUri("ipp://192.168.1.1:");
-  EXPECT_FALSE(result.has_value());
-}
-
-TEST(PrinterConfigurationTest, InvalidUriEmptyPort) {
-  auto result = chromeos::ParseUri("ipp://192.168.1.1:/printer");
-  EXPECT_FALSE(result.has_value());
+  chromeos::Printer printer;
+  EXPECT_FALSE(printer.SetUri("ipps://"));
 }
 
 TEST(PrinterConfigurationTest, InvalidPort) {
-  auto result = chromeos::ParseUri("ipp://1.2.3.4:abcd");
-  EXPECT_FALSE(result.has_value());
+  chromeos::Printer printer;
+  EXPECT_FALSE(printer.SetUri("ipp://1.2.3.4:abcd"));
 }
 
 TEST(PrinterConfigurationTest, MissingScheme) {
-  auto result = chromeos::ParseUri("/10.2.12.3/");
-  EXPECT_FALSE(result.has_value());
+  chromeos::Printer printer;
+  EXPECT_FALSE(printer.SetUri("/10.2.12.3/"));
 }
 
 TEST(PrinterConfigurationTest, ParseUriIp) {
-  auto result = chromeos::ParseUri("ipp://192.168.1.5");
-
-  ASSERT_TRUE(result.has_value());
-  EXPECT_EQ(result->scheme(), "ipp");
-  EXPECT_EQ(result->host(), "192.168.1.5");
-  EXPECT_TRUE(result->path().empty());
+  chromeos::Printer printer;
+  EXPECT_TRUE(printer.SetUri("ipp://192.168.1.5"));
+  EXPECT_EQ(printer.uri().GetScheme(), "ipp");
+  EXPECT_EQ(printer.uri().GetHost(), "192.168.1.5");
+  EXPECT_TRUE(printer.uri().GetPathEncodedAsString().empty());
 }
 
 TEST(PrinterConfigurationTest, ParseUriPort) {
-  auto result = chromeos::ParseUri("ipp://1.2.3.4:4444");
-
-  ASSERT_TRUE(result.has_value());
-  EXPECT_EQ(result->port(), 4444);
+  chromeos::Printer printer;
+  EXPECT_TRUE(printer.SetUri("ipp://1.2.3.4:4444"));
+  EXPECT_EQ(printer.uri().GetPort(), 4444);
 }
 
 TEST(PrinterConfigurationTest, ParseTrailingSlash) {
-  auto result = chromeos::ParseUri("ipp://1.2.3.4/");
-  ASSERT_TRUE(result.has_value());
-  EXPECT_EQ(result->path(), "/");
-  EXPECT_EQ(result->host(), "1.2.3.4");
+  chromeos::Printer printer;
+  EXPECT_TRUE(printer.SetUri("ipp://1.2.3.4/"));
+  EXPECT_EQ(printer.uri().GetPathEncodedAsString(), "");
+  EXPECT_EQ(printer.uri().GetHost(), "1.2.3.4");
 }
 
 TEST(PrinterConfigurationTest, ParseUriHostNameAndPort) {
-  auto result = chromeos::ParseUri("ipp://chromium.org:8");
-  ASSERT_TRUE(result.has_value());
-  EXPECT_EQ(result->port(), 8);
-  EXPECT_EQ(result->host(), "chromium.org");
+  chromeos::Printer printer;
+  EXPECT_TRUE(printer.SetUri("ipp://chromium.org:8"));
+  EXPECT_EQ(printer.uri().GetPort(), 8);
+  EXPECT_EQ(printer.uri().GetHost(), "chromium.org");
 }
 
 TEST(PrinterConfigurationTest, ParseUriPathNoPort) {
-  auto result = chromeos::ParseUri("ipps://chromium.org/printers/printprint");
-  ASSERT_TRUE(result.has_value());
-  EXPECT_EQ(result->host(), "chromium.org");
-  EXPECT_EQ(result->path(), "/printers/printprint");
+  chromeos::Printer printer;
+  EXPECT_TRUE(printer.SetUri("ipps://chromium.org/printers/printprint"));
+  EXPECT_EQ(printer.uri().GetHost(), "chromium.org");
+  EXPECT_EQ(printer.uri().GetPathEncodedAsString(), "/printers/printprint");
 }
 
 TEST(PrinterConfigurationTest, ParseUriSubdomainQueueAndPort) {
-  auto result =
-      chromeos::ParseUri("ipp://codesearch.chromium.org:1234/ipp/print");
-  ASSERT_TRUE(result.has_value());
-  EXPECT_EQ(result->host(), "codesearch.chromium.org");
-  EXPECT_EQ(result->port(), 1234);
-  EXPECT_EQ(result->path(), "/ipp/print");
+  chromeos::Printer printer;
+  EXPECT_TRUE(printer.SetUri("ipp://codesearch.chromium.org:1234/ipp/print"));
+  EXPECT_EQ(printer.uri().GetHost(), "codesearch.chromium.org");
+  EXPECT_EQ(printer.uri().GetPort(), 1234);
+  EXPECT_EQ(printer.uri().GetPathEncodedAsString(), "/ipp/print");
 }
 
 TEST(PrinterConfigurationTest, SecureProtocolIpps) {
   chromeos::Printer printer;
-  printer.set_uri("ipps://1.2.3.4");
+  EXPECT_TRUE(printer.SetUri("ipps://1.2.3.4"));
   EXPECT_TRUE(printer.HasSecureProtocol());
 }
 
 TEST(PrinterConfigurationTest, SecureProtocolHttps) {
   chromeos::Printer printer;
-  printer.set_uri("https://1.2.3.4");
+  EXPECT_TRUE(printer.SetUri("https://1.2.3.4"));
   EXPECT_TRUE(printer.HasSecureProtocol());
 }
 
 TEST(PrinterConfigurationTest, SecureProtocolUsb) {
   chromeos::Printer printer;
-  printer.set_uri("usb://");
+  EXPECT_TRUE(printer.SetUri("usb://host/path"));
   EXPECT_TRUE(printer.HasSecureProtocol());
 }
 
 TEST(PrinterConfigurationTest, SecureProtocolIppusb) {
   chromeos::Printer printer;
-  printer.set_uri("ippusb://");
+  EXPECT_TRUE(printer.SetUri("ippusb://host/path"));
   EXPECT_TRUE(printer.HasSecureProtocol());
 }
 
 TEST(PrinterConfigurationTest, NonSecureProtocolIpp) {
   chromeos::Printer printer;
-  printer.set_uri("ipp://1.2.3.4");
+  EXPECT_TRUE(printer.SetUri("ipp://1.2.3.4"));
   EXPECT_FALSE(printer.HasSecureProtocol());
 }
 
 TEST(PrinterConfigurationTest, NonSecureProtocolHttp) {
   chromeos::Printer printer;
-  printer.set_uri("http://1.2.3.4");
+  EXPECT_TRUE(printer.SetUri("http://1.2.3.4"));
   EXPECT_FALSE(printer.HasSecureProtocol());
 }
 
 TEST(PrinterConfigurationTest, NonSecureProtocolSocket) {
   chromeos::Printer printer;
-  printer.set_uri("socket://1.2.3.4");
+  EXPECT_TRUE(printer.SetUri("socket://1.2.3.4"));
   EXPECT_FALSE(printer.HasSecureProtocol());
 }
 
 TEST(PrinterConfigurationTest, NonSecureProtocolLpd) {
   chromeos::Printer printer;
-  printer.set_uri("lpd://1.2.3.4");
+  EXPECT_TRUE(printer.SetUri("lpd://1.2.3.4"));
   EXPECT_FALSE(printer.HasSecureProtocol());
 }
 
 TEST(PrinterConfigurationTest, NonSecureProtocolUnknown) {
   chromeos::Printer printer;
-  printer.set_uri("foobar");
+  EXPECT_FALSE(printer.SetUri("foobar"));
   EXPECT_FALSE(printer.HasSecureProtocol());
 }
 
diff --git a/chromeos/printing/printer_translator.cc b/chromeos/printing/printer_translator.cc
index 96e9528c..65af201 100644
--- a/chromeos/printing/printer_translator.cc
+++ b/chromeos/printing/printer_translator.cc
@@ -14,7 +14,7 @@
 #include "base/values.h"
 #include "chromeos/printing/cups_printer_status.h"
 #include "chromeos/printing/printer_configuration.h"
-#include "chromeos/printing/uri_components.h"
+#include "chromeos/printing/uri.h"
 
 using base::DictionaryValue;
 
@@ -37,24 +37,6 @@
 const char kAutoconf[] = "autoconf";
 const char kGuid[] = "guid";
 
-// Returns true if the uri was retrieved, is valid, and was set on |printer|.
-// Returns false otherwise.
-bool SetUri(const DictionaryValue& dict, Printer* printer) {
-  std::string uri;
-  if (!dict.GetString(kUri, &uri)) {
-    LOG(WARNING) << "Uri required";
-    return false;
-  }
-
-  if (!chromeos::ParseUri(uri).has_value()) {
-    LOG(WARNING) << "Uri is malformed";
-    return false;
-  }
-
-  printer->set_uri(uri);
-  return true;
-}
-
 // Populates the |printer| object with corresponding fields from |value|.
 // Returns false if |value| is missing a required field.
 bool DictionaryToPrinter(const DictionaryValue& value, Printer* printer) {
@@ -67,7 +49,15 @@
     return false;
   }
 
-  if (!SetUri(value, printer)) {
+  std::string uri;
+  if (value.GetString(kUri, &uri)) {
+    std::string message;
+    if (!printer->SetUri(uri, &message)) {
+      LOG(WARNING) << message;
+      return false;
+    }
+  } else {
+    LOG(WARNING) << "Uri required";
     return false;
   }
 
@@ -122,12 +112,12 @@
 
 // Formats a host and port string. The |port| portion is omitted if it is
 // unspecified or invalid.
-std::string PrinterAddress(const std::string& host, int port) {
-  if (port != url::PORT_UNSPECIFIED && port != url::PORT_INVALID) {
-    return base::StringPrintf("%s:%d", host.c_str(), port);
+std::string PrinterAddress(const Uri& uri) {
+  const int port = uri.GetPort();
+  if (port > -1) {
+    return base::StringPrintf("%s:%d", uri.GetHostEncoded().c_str(), port);
   }
-
-  return host;
+  return uri.GetHostEncoded();
 }
 
 }  // namespace
@@ -199,8 +189,7 @@
                           printer.ppd_reference().user_supplied_ppd_url);
   printer_info->SetString("printServerUri", printer.print_server_uri());
 
-  auto optional = printer.GetUriComponents();
-  if (!optional.has_value()) {
+  if (!printer.HasUri()) {
     // Uri is invalid so we set default values.
     LOG(WARNING) << "Could not parse uri.  Defaulting values";
     printer_info->SetString("printerAddress", "");
@@ -210,24 +199,23 @@
     return printer_info;
   }
 
-  UriComponents uri = optional.value();
-
-  if (base::ToLowerASCII(uri.scheme()) == "usb") {
+  if (printer.uri().GetScheme() == "usb") {
     // USB has URI path (and, maybe, query) components that aren't really
     // associated with a queue -- the mapping between printing semantics and URI
     // semantics breaks down a bit here.  From the user's point of view, the
     // entire host/path/query block is the printer address for USB.
-    printer_info->SetString("printerAddress",
-                            printer.uri().substr(strlen("usb://")));
+    printer_info->SetString(
+        "printerAddress",
+        printer.uri().GetNormalized().substr(strlen("usb://")));
     printer_info->SetString("ppdManufacturer", printer.manufacturer());
   } else {
-    printer_info->SetString("printerAddress",
-                            PrinterAddress(uri.host(), uri.port()));
-    if (!uri.path().empty()) {
-      printer_info->SetString("printerQueue", uri.path().substr(1));
+    printer_info->SetString("printerAddress", PrinterAddress(printer.uri()));
+    if (!printer.uri().GetPath().empty()) {
+      printer_info->SetString("printerQueue",
+                              printer.uri().GetPathEncodedAsString().substr(1));
     }
   }
-  printer_info->SetString("printerProtocol", base::ToLowerASCII(uri.scheme()));
+  printer_info->SetString("printerProtocol", printer.uri().GetScheme());
 
   return printer_info;
 }
diff --git a/chromeos/printing/printer_translator_unittest.cc b/chromeos/printing/printer_translator_unittest.cc
index 7e83c80..76276a0 100644
--- a/chromeos/printing/printer_translator_unittest.cc
+++ b/chromeos/printing/printer_translator_unittest.cc
@@ -153,8 +153,8 @@
   preference.SetString("display_name", kName);
   preference.SetString("ppd_resource.effective_model", kEffectiveMakeAndModel);
 
-  // uri with dangling colon
-  preference.SetString("uri", "ipp://hostname.tld:");
+  // uri with incorrect port
+  preference.SetString("uri", "ipp://hostname.tld:-1");
 
   std::unique_ptr<Printer> printer = RecommendedPrinterToPrinter(preference);
   EXPECT_FALSE(printer);
@@ -197,7 +197,7 @@
   EXPECT_EQ(kMake, printer->manufacturer());
   EXPECT_EQ(kModel, printer->model());
   EXPECT_EQ(kMakeAndModel, printer->make_and_model());
-  EXPECT_EQ(kUri, printer->uri());
+  EXPECT_EQ(kUri, printer->uri().GetNormalized());
   EXPECT_EQ(kUUID, printer->uuid());
 
   EXPECT_EQ(kEffectiveMakeAndModel,
@@ -218,7 +218,7 @@
 
   EXPECT_EQ(kHash, printer->id());
   EXPECT_EQ(kName, printer->display_name());
-  EXPECT_EQ(kUri, printer->uri());
+  EXPECT_EQ(kUri, printer->uri().GetNormalized());
 
   EXPECT_EQ(true, printer->ppd_reference().autoconf);
 }
@@ -281,7 +281,7 @@
 
 TEST(PrinterTranslatorTest, GetCupsPrinterInfoGenericPrinterWithUri) {
   Printer printer = CreateGenericPrinter();
-  printer.set_uri(kUri);
+  ASSERT_TRUE(printer.SetUri(kUri));
 
   std::unique_ptr<base::DictionaryValue> printer_info =
       GetCupsPrinterInfo(printer);
@@ -295,7 +295,7 @@
 
 TEST(PrinterTranslatorTest, GetCupsPrinterInfoGenericPrinterWithUsbUri) {
   Printer printer = CreateGenericPrinter();
-  printer.set_uri(kUsbUri);
+  ASSERT_TRUE(printer.SetUri(kUsbUri));
 
   std::unique_ptr<base::DictionaryValue> printer_info =
       GetCupsPrinterInfo(printer);
diff --git a/chromeos/printing/uri_components.cc b/chromeos/printing/uri_components.cc
deleted file mode 100644
index fe22f2e..0000000
--- a/chromeos/printing/uri_components.cc
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chromeos/printing/uri_components.h"
-
-#include "url/third_party/mozilla/url_parse.h"
-
-namespace chromeos {
-
-UriComponents::UriComponents(bool encrypted,
-                             const std::string& scheme,
-                             const std::string& host,
-                             int port,
-                             const std::string& path)
-    : encrypted_(encrypted),
-      scheme_(scheme),
-      host_(host),
-      port_(port),
-      path_(path) {}
-
-UriComponents::UriComponents(const UriComponents&) = default;
-
-}  // namespace chromeos
diff --git a/chromeos/printing/uri_components.h b/chromeos/printing/uri_components.h
deleted file mode 100644
index 96a351f..0000000
--- a/chromeos/printing/uri_components.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMEOS_PRINTING_URI_COMPONENTS_H_
-#define CHROMEOS_PRINTING_URI_COMPONENTS_H_
-
-#include <string>
-
-#include "chromeos/chromeos_export.h"
-#include "url/third_party/mozilla/url_parse.h"
-
-namespace chromeos {
-
-class CHROMEOS_EXPORT UriComponents {
- public:
-  UriComponents(bool encrypted,
-                const std::string& scheme,
-                const std::string& host,
-                int port,
-                const std::string& path);
-
-  UriComponents(const UriComponents&);
-
-  bool encrypted() const { return encrypted_; }
-  std::string scheme() const { return scheme_; }
-  std::string host() const { return host_; }
-  int port() const { return port_; }
-  std::string path() const { return path_; }
-
- private:
-  const bool encrypted_;
-  const std::string scheme_;
-  const std::string host_;
-  const int port_;
-  const std::string path_;
-};
-
-}  // namespace chromeos
-
-#endif  // CHROMEOS_PRINTING_URI_COMPONENTS_H_
diff --git a/chromeos/profiles/airmont.afdo.newest.txt b/chromeos/profiles/airmont.afdo.newest.txt
index 4344ca55..8b76862 100644
--- a/chromeos/profiles/airmont.afdo.newest.txt
+++ b/chromeos/profiles/airmont.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-airmont-86-4181.3-1594034553-benchmark-86.0.4194.0-r1-redacted.afdo.xz
+chromeos-chrome-amd64-airmont-86-4181.3-1594633354-benchmark-86.0.4194.0-r1-redacted.afdo.xz
diff --git a/chromeos/services/assistant/assistant_manager_service_impl.cc b/chromeos/services/assistant/assistant_manager_service_impl.cc
index da87c77..1250880 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl.cc
+++ b/chromeos/services/assistant/assistant_manager_service_impl.cc
@@ -149,6 +149,33 @@
   });
 }
 
+const char* ToTriggerSource(AssistantEntryPoint entry_point) {
+  switch (entry_point) {
+    case AssistantEntryPoint::kUnspecified:
+      return kEntryPointUnspecified;
+    case AssistantEntryPoint::kDeepLink:
+      return kEntryPointDeepLink;
+    case AssistantEntryPoint::kHotkey:
+      return kEntryPointHotkey;
+    case AssistantEntryPoint::kHotword:
+      return kEntryPointHotword;
+    case AssistantEntryPoint::kLongPressLauncher:
+      return kEntryPointLongPressLauncher;
+    case AssistantEntryPoint::kSetup:
+      return kEntryPointSetup;
+    case AssistantEntryPoint::kStylus:
+      return kEntryPointStylus;
+    case AssistantEntryPoint::kLauncherSearchResult:
+      return kEntryPointLauncherSearchResult;
+    case AssistantEntryPoint::kLauncherSearchBoxIcon:
+      return kEntryPointLauncherSearchBoxIcon;
+    case AssistantEntryPoint::kProactiveSuggestions:
+      return kEntryPointProactiveSuggestions;
+    case AssistantEntryPoint::kLauncherChip:
+      return kEntryPointLauncherChip;
+  }
+}
+
 }  // namespace
 
 AssistantManagerServiceImpl::AssistantManagerServiceImpl(
@@ -1288,7 +1315,7 @@
 void AssistantManagerServiceImpl::NotifyEntryIntoAssistantUi(
     AssistantEntryPoint entry_point) {
   base::AutoLock lock(last_trigger_source_lock_);
-  last_trigger_source_ = ToTriggerSource(static_cast<int>(entry_point));
+  last_trigger_source_ = ToTriggerSource(entry_point);
 }
 
 std::string AssistantManagerServiceImpl::ConsumeLastTriggerSource() {
diff --git a/chromeos/services/assistant/public/cpp/assistant_service.h b/chromeos/services/assistant/public/cpp/assistant_service.h
index d5b536b..2b796f03 100644
--- a/chromeos/services/assistant/public/cpp/assistant_service.h
+++ b/chromeos/services/assistant/public/cpp/assistant_service.h
@@ -58,6 +58,8 @@
   std::string text;
 
   // Optional URL for icon. e.g. "https://www.gstatic.com/icon.png".
+  // NOTE: This may be an icon resource link. e.g.
+  // "googleassistant://resource?type=icon&name=assistant".
   GURL icon_url;
 
   // Optional URL for action. e.g.
diff --git a/chromeos/services/secure_channel/ble_characteristics_finder_unittest.cc b/chromeos/services/secure_channel/ble_characteristics_finder_unittest.cc
index 7ff2014..c3179e715 100644
--- a/chromeos/services/secure_channel/ble_characteristics_finder_unittest.cc
+++ b/chromeos/services/secure_channel/ble_characteristics_finder_unittest.cc
@@ -137,8 +137,7 @@
                              bool valid = true) {
     std::unique_ptr<device::MockBluetoothGattCharacteristic> characteristic(
         new NiceMock<device::MockBluetoothGattCharacteristic>(
-            /* service */ nullptr, id, uuid, /* is_local */ false,
-            kCharacteristicProperties,
+            /*service=*/nullptr, id, uuid, kCharacteristicProperties,
             device::BluetoothRemoteGattCharacteristic::PERMISSION_NONE));
 
     ON_CALL(*characteristic.get(), GetUUID()).WillByDefault(Return(uuid));
@@ -185,7 +184,7 @@
       bool is_discovery_complete) {
     auto service = std::make_unique<NiceMock<device::MockBluetoothGattService>>(
         device_.get(), service_id, device::BluetoothUUID(kServiceUUID),
-        /* is_primary */ true, /* is_local */ false);
+        /*is_primary=*/true);
     device::MockBluetoothGattService* service_ptr = service.get();
     services_.push_back(std::move(service));
     ON_CALL(*device_, GetGattServices())
diff --git a/chromeos/services/secure_channel/ble_weave_client_connection_unittest.cc b/chromeos/services/secure_channel/ble_weave_client_connection_unittest.cc
index d0a0359..ed4c9da 100644
--- a/chromeos/services/secure_channel/ble_weave_client_connection_unittest.cc
+++ b/chromeos/services/secure_channel/ble_weave_client_connection_unittest.cc
@@ -350,15 +350,16 @@
             adapter_.get(), 0, multidevice::kTestRemoteDeviceName,
             kTestRemoteDeviceBluetoothAddress, false, false);
     service_ = std::make_unique<NiceMock<device::MockBluetoothGattService>>(
-        mock_bluetooth_device_.get(), kServiceID, service_uuid_, true, false);
+        mock_bluetooth_device_.get(), kServiceID, service_uuid_,
+        /*is_primary=*/true);
     tx_characteristic_ =
         std::make_unique<NiceMock<device::MockBluetoothGattCharacteristic>>(
-            service_.get(), kTXCharacteristicID, tx_characteristic_uuid_, false,
+            service_.get(), kTXCharacteristicID, tx_characteristic_uuid_,
             kCharacteristicProperties,
             device::BluetoothRemoteGattCharacteristic::PERMISSION_NONE);
     rx_characteristic_ =
         std::make_unique<NiceMock<device::MockBluetoothGattCharacteristic>>(
-            service_.get(), kRXCharacteristicID, rx_characteristic_uuid_, false,
+            service_.get(), kRXCharacteristicID, rx_characteristic_uuid_,
             kCharacteristicProperties,
             device::BluetoothRemoteGattCharacteristic::PERMISSION_NONE);
 
diff --git a/components/BUILD.gn b/components/BUILD.gn
index 5586921..f3e9fd4 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -541,7 +541,6 @@
 
     sources = [
       "autofill/content/browser/risk/fingerprint_browsertest.cc",
-      "autofill/content/renderer/field_data_manager_browsertest.cc",
       "autofill/content/renderer/form_autofill_util_browsertest.cc",
       "autofill/content/renderer/form_cache_browsertest.cc",
       "autofill/content/renderer/html_based_username_detector_browsertest.cc",
diff --git a/components/autofill/content/browser/content_autofill_driver_unittest.cc b/components/autofill/content/browser/content_autofill_driver_unittest.cc
index 675ebf6..1906bc9 100644
--- a/components/autofill/content/browser/content_autofill_driver_unittest.cc
+++ b/components/autofill/content/browser/content_autofill_driver_unittest.cc
@@ -289,8 +289,8 @@
  public:
   void SetUp() override {
     content::RenderViewHostTestHarness::SetUp();
-    // This needed to keep the WebContentsObserverSequenceChecker checks happy
-    // for when AppendChild is called.
+    // This needed to keep the WebContentsObserverConsistencyChecker checks
+    // happy for when AppendChild is called.
     NavigateAndCommit(GURL("about:blank"));
 
     test_autofill_client_.reset(new MockAutofillClient());
diff --git a/components/autofill/content/renderer/BUILD.gn b/components/autofill/content/renderer/BUILD.gn
index 6ee2bee9..c8d8440 100644
--- a/components/autofill/content/renderer/BUILD.gn
+++ b/components/autofill/content/renderer/BUILD.gn
@@ -10,8 +10,6 @@
     "autofill_agent.h",
     "autofill_assistant_agent.cc",
     "autofill_assistant_agent.h",
-    "field_data_manager.cc",
-    "field_data_manager.h",
     "form_autofill_util.cc",
     "form_autofill_util.h",
     "form_cache.cc",
diff --git a/components/autofill/content/renderer/field_data_manager.cc b/components/autofill/content/renderer/field_data_manager.cc
deleted file mode 100644
index 769184ffb..0000000
--- a/components/autofill/content/renderer/field_data_manager.cc
+++ /dev/null
@@ -1,89 +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 "components/autofill/content/renderer/field_data_manager.h"
-
-#include "base/check.h"
-#include "base/i18n/case_conversion.h"
-#include "third_party/blink/public/web/web_form_control_element.h"
-
-namespace autofill {
-
-FieldDataManager::FieldDataManager() = default;
-
-void FieldDataManager::ClearData() {
-  field_value_and_properties_map_.clear();
-}
-
-bool FieldDataManager::HasFieldData(FieldRendererId id) const {
-  return field_value_and_properties_map_.find(id) !=
-         field_value_and_properties_map_.end();
-}
-
-base::string16 FieldDataManager::GetUserTypedValue(FieldRendererId id) const {
-  DCHECK(HasFieldData(id));
-  return field_value_and_properties_map_.at(id).first.value_or(
-      base::string16());
-}
-
-FieldPropertiesMask FieldDataManager::GetFieldPropertiesMask(
-    FieldRendererId id) const {
-  DCHECK(HasFieldData(id));
-  return field_value_and_properties_map_.at(id).second;
-}
-
-bool FieldDataManager::FindMachedValue(const base::string16& value) const {
-  constexpr size_t kMinMatchSize = 3u;
-  const auto lowercase = base::i18n::ToLower(value);
-  for (const auto& map_key : field_value_and_properties_map_) {
-    const base::string16 typed_from_key =
-        map_key.second.first.value_or(base::string16());
-    if (typed_from_key.empty())
-      continue;
-    if (typed_from_key.size() >= kMinMatchSize &&
-        lowercase.find(base::i18n::ToLower(typed_from_key)) !=
-            base::string16::npos)
-      return true;
-  }
-  return false;
-}
-
-void FieldDataManager::UpdateFieldDataMap(
-    const blink::WebFormControlElement& element,
-    const base::string16& value,
-    FieldPropertiesMask mask) {
-  FieldRendererId id(element.UniqueRendererFormControlId());
-  if (HasFieldData(id)) {
-    field_value_and_properties_map_.at(id).first =
-        base::Optional<base::string16>(value);
-    field_value_and_properties_map_.at(id).second |= mask;
-  } else {
-    field_value_and_properties_map_[id] =
-        std::make_pair(base::Optional<base::string16>(value), mask);
-  }
-  // Reset kUserTyped and kAutofilled flags if the value is empty.
-  if (value.empty()) {
-    field_value_and_properties_map_.at(id).second &=
-        ~(FieldPropertiesFlags::kUserTyped | FieldPropertiesFlags::kAutofilled);
-  }
-}
-
-void FieldDataManager::UpdateFieldDataMapWithNullValue(
-    const blink::WebFormControlElement& element,
-    FieldPropertiesMask mask) {
-  FieldRendererId id(element.UniqueRendererFormControlId());
-  if (HasFieldData(id))
-    field_value_and_properties_map_.at(id).second |= mask;
-  else
-    field_value_and_properties_map_[id] = std::make_pair(base::nullopt, mask);
-}
-
-bool FieldDataManager::DidUserType(FieldRendererId id) const {
-  return HasFieldData(id) &&
-         (GetFieldPropertiesMask(id) & FieldPropertiesFlags::kUserTyped);
-}
-
-FieldDataManager::~FieldDataManager() = default;
-
-}  // namespace autofill
diff --git a/components/autofill/content/renderer/field_data_manager.h b/components/autofill/content/renderer/field_data_manager.h
deleted file mode 100644
index 839773c..0000000
--- a/components/autofill/content/renderer/field_data_manager.h
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_AUTOFILL_CONTENT_RENDERER_FIELD_DATA_MANAGER_H_
-#define COMPONENTS_AUTOFILL_CONTENT_RENDERER_FIELD_DATA_MANAGER_H_
-
-#include <map>
-
-#include "base/optional.h"
-#include "base/strings/string16.h"
-#include "components/autofill/core/common/form_field_data.h"
-#include "components/autofill/core/common/renderer_id.h"
-
-namespace blink {
-class WebFormControlElement;
-}
-
-namespace autofill {
-
-// This class provides the methods to update and get the field data (the pair
-// of user typed value and field properties mask).
-class FieldDataManager : public base::RefCounted<FieldDataManager> {
- public:
-  using FieldDataMap =
-      std::map<FieldRendererId,
-               std::pair<base::Optional<base::string16>, FieldPropertiesMask>>;
-
-  FieldDataManager();
-
-  void ClearData();
-  bool HasFieldData(FieldRendererId id) const;
-
-  // Updates the field value associated with the key |element| in
-  // |field_value_and_properties_map_|.
-  // Flags in |mask| are added with bitwise OR operation.
-  // If |value| is empty, kUserTyped and kAutofilled should be cleared.
-  void UpdateFieldDataMap(const blink::WebFormControlElement& element,
-                          const base::string16& value,
-                          FieldPropertiesMask mask);
-  // Only update FieldPropertiesMask when value is null.
-  void UpdateFieldDataMapWithNullValue(
-      const blink::WebFormControlElement& element,
-      FieldPropertiesMask mask);
-
-  base::string16 GetUserTypedValue(FieldRendererId id) const;
-  FieldPropertiesMask GetFieldPropertiesMask(FieldRendererId id) const;
-
-  // Check if the string |value| is saved in |field_value_and_properties_map_|.
-  bool FindMachedValue(const base::string16& value) const;
-
-  bool DidUserType(FieldRendererId id) const;
-
-  const FieldDataMap& field_data_map() const {
-    return field_value_and_properties_map_;
-  }
-
- private:
-  friend class base::RefCounted<FieldDataManager>;
-
-  ~FieldDataManager();
-
-  FieldDataMap field_value_and_properties_map_;
-};
-
-}  // namespace autofill
-
-#endif  // COMPONENTS_AUTOFILL_CONTENT_RENDERER_FIELD_DATA_MANAGER_H_
diff --git a/components/autofill/content/renderer/field_data_manager_browsertest.cc b/components/autofill/content/renderer/field_data_manager_browsertest.cc
deleted file mode 100644
index 5c896c9..0000000
--- a/components/autofill/content/renderer/field_data_manager_browsertest.cc
+++ /dev/null
@@ -1,116 +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 "components/autofill/content/renderer/field_data_manager.h"
-
-#include "base/strings/utf_string_conversions.h"
-#include "components/autofill/core/common/renderer_id.h"
-#include "content/public/test/render_view_test.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/public/web/web_document.h"
-#include "third_party/blink/public/web/web_element_collection.h"
-#include "third_party/blink/public/web/web_form_control_element.h"
-#include "third_party/blink/public/web/web_local_frame.h"
-
-using base::UTF8ToUTF16;
-
-namespace autofill {
-
-class FieldDataManagerTest : public content::RenderViewTest {
- public:
-  FieldDataManagerTest() {}
-  ~FieldDataManagerTest() override {}
-
- protected:
-  void SetUp() override {
-    RenderViewTest::SetUp();
-
-    LoadHTML(
-        "<input type='text' id='name1' value='first'>"
-        "<input type='password' id='name2' value=''>");
-    blink::WebLocalFrame* web_frame = GetMainFrame();
-    blink::WebElementCollection inputs =
-        web_frame->GetDocument().GetElementsByHTMLTagName("input");
-    for (blink::WebElement element = inputs.FirstItem(); !element.IsNull();
-         element = inputs.NextItem()) {
-      control_elements_.push_back(element.To<blink::WebFormControlElement>());
-    }
-  }
-
-  void TearDown() override {
-    control_elements_.clear();
-    RenderViewTest::TearDown();
-  }
-
-  std::vector<blink::WebFormControlElement> control_elements_;
-};
-
-TEST_F(FieldDataManagerTest, UpdateFieldDataMap) {
-  const scoped_refptr<FieldDataManager> field_data_manager =
-      base::MakeRefCounted<FieldDataManager>();
-  field_data_manager->UpdateFieldDataMap(control_elements_[0],
-                                         control_elements_[0].Value().Utf16(),
-                                         FieldPropertiesFlags::kUserTyped);
-  const FieldRendererId id(control_elements_[0].UniqueRendererFormControlId());
-  EXPECT_TRUE(field_data_manager->HasFieldData(id));
-  EXPECT_EQ(UTF8ToUTF16("first"), field_data_manager->GetUserTypedValue(id));
-  EXPECT_EQ(FieldPropertiesFlags::kUserTyped,
-            field_data_manager->GetFieldPropertiesMask(id));
-
-  field_data_manager->UpdateFieldDataMap(control_elements_[0],
-                                         UTF8ToUTF16("newvalue"),
-                                         FieldPropertiesFlags::kAutofilled);
-  EXPECT_EQ(UTF8ToUTF16("newvalue"), field_data_manager->GetUserTypedValue(id));
-  FieldPropertiesMask mask =
-      FieldPropertiesFlags::kUserTyped | FieldPropertiesFlags::kAutofilled;
-  EXPECT_EQ(mask, field_data_manager->GetFieldPropertiesMask(id));
-
-  field_data_manager->UpdateFieldDataMap(control_elements_[1],
-                                         control_elements_[1].Value().Utf16(),
-                                         FieldPropertiesFlags::kAutofilled);
-  EXPECT_EQ(FieldPropertiesFlags::kNoFlags,
-            field_data_manager->GetFieldPropertiesMask(FieldRendererId(
-                control_elements_[1].UniqueRendererFormControlId())));
-
-  field_data_manager->ClearData();
-  EXPECT_FALSE(field_data_manager->HasFieldData(id));
-}
-
-TEST_F(FieldDataManagerTest, UpdateFieldDataMapWithNullValue) {
-  const scoped_refptr<FieldDataManager> field_data_manager =
-      base::MakeRefCounted<FieldDataManager>();
-  field_data_manager->UpdateFieldDataMapWithNullValue(
-      control_elements_[0], FieldPropertiesFlags::kUserTyped);
-  const FieldRendererId id(control_elements_[0].UniqueRendererFormControlId());
-  EXPECT_TRUE(field_data_manager->HasFieldData(id));
-  EXPECT_EQ(base::string16(), field_data_manager->GetUserTypedValue(id));
-  EXPECT_EQ(FieldPropertiesFlags::kUserTyped,
-            field_data_manager->GetFieldPropertiesMask(id));
-
-  field_data_manager->UpdateFieldDataMapWithNullValue(
-      control_elements_[0], FieldPropertiesFlags::kAutofilled);
-  EXPECT_EQ(base::string16(), field_data_manager->GetUserTypedValue(id));
-  FieldPropertiesMask mask =
-      FieldPropertiesFlags::kUserTyped | FieldPropertiesFlags::kAutofilled;
-  EXPECT_EQ(mask, field_data_manager->GetFieldPropertiesMask(id));
-
-  field_data_manager->UpdateFieldDataMap(control_elements_[0],
-                                         control_elements_[0].Value().Utf16(),
-                                         FieldPropertiesFlags::kAutofilled);
-  EXPECT_EQ(UTF8ToUTF16("first"), field_data_manager->GetUserTypedValue(id));
-}
-
-TEST_F(FieldDataManagerTest, FindMachedValue) {
-  const scoped_refptr<FieldDataManager> field_data_manager =
-      base::MakeRefCounted<FieldDataManager>();
-  field_data_manager->UpdateFieldDataMap(control_elements_[0],
-                                         control_elements_[0].Value().Utf16(),
-                                         FieldPropertiesFlags::kUserTyped);
-  EXPECT_TRUE(
-      field_data_manager->FindMachedValue(UTF8ToUTF16("first_element")));
-  EXPECT_FALSE(
-      field_data_manager->FindMachedValue(UTF8ToUTF16("second_element")));
-}
-
-}  // namespace autofill
diff --git a/components/autofill/content/renderer/form_autofill_util.cc b/components/autofill/content/renderer/form_autofill_util.cc
index 35f247f2..03a6f6a 100644
--- a/components/autofill/content/renderer/form_autofill_util.cc
+++ b/components/autofill/content/renderer/form_autofill_util.cc
@@ -26,12 +26,12 @@
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
-#include "components/autofill/content/renderer/field_data_manager.h"
 #include "components/autofill/core/common/autofill_data_validation.h"
 #include "components/autofill/core/common/autofill_features.h"
 #include "components/autofill/core/common/autofill_regexes.h"
 #include "components/autofill/core/common/autofill_switches.h"
 #include "components/autofill/core/common/autofill_util.h"
+#include "components/autofill/core/common/field_data_manager.h"
 #include "components/autofill/core/common/form_data.h"
 #include "components/autofill/core/common/form_field_data.h"
 #include "content/public/renderer/render_frame.h"
diff --git a/components/autofill/content/renderer/form_cache.h b/components/autofill/content/renderer/form_cache.h
index eb1b4a7..be608bb 100644
--- a/components/autofill/content/renderer/form_cache.h
+++ b/components/autofill/content/renderer/form_cache.h
@@ -15,7 +15,7 @@
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/strings/string16.h"
-#include "components/autofill/content/renderer/field_data_manager.h"
+#include "components/autofill/core/common/field_data_manager.h"
 #include "components/autofill/core/common/form_data.h"
 #include "components/autofill/core/common/renderer_id.h"
 
diff --git a/components/autofill/content/renderer/password_autofill_agent.cc b/components/autofill/content/renderer/password_autofill_agent.cc
index 4d283f72..d95148a3 100644
--- a/components/autofill/content/renderer/password_autofill_agent.cc
+++ b/components/autofill/content/renderer/password_autofill_agent.cc
@@ -558,7 +558,8 @@
   WebInputElement mutable_element = element;  // We need a non-const.
 
   const base::string16 element_value = element.Value().Utf16();
-  field_data_manager_->UpdateFieldDataMap(element, element_value,
+  const FieldRendererId element_id(element.UniqueRendererFormControlId());
+  field_data_manager_->UpdateFieldDataMap(element_id, element_value,
                                           FieldPropertiesFlags::kUserTyped);
 
   ProvisionallySavePassword(element.Form(), element, RESTRICTION_NONE);
@@ -645,8 +646,9 @@
   DCHECK(!input->IsNull());
   input->SetAutofillValue(WebString::FromUTF16(credential));
   input->SetAutofillState(WebAutofillState::kAutofilled);
+  const FieldRendererId input_id(input->UniqueRendererFormControlId());
   field_data_manager_->UpdateFieldDataMap(
-      *input, credential, FieldPropertiesFlags::kAutofilledOnUserTrigger);
+      input_id, credential, FieldPropertiesFlags::kAutofilledOnUserTrigger);
 }
 
 void PasswordAutofillAgent::FillPasswordFieldAndSave(
@@ -1360,8 +1362,9 @@
   }
 
   focus_state_notifier_.FocusedInputChanged(focused_field_type);
+  const FieldRendererId input_id(input_element->UniqueRendererFormControlId());
   field_data_manager_->UpdateFieldDataMapWithNullValue(
-      *input_element, FieldPropertiesFlags::kHadFocus);
+      input_id, FieldPropertiesFlags::kHadFocus);
 }
 
 std::unique_ptr<FormData> PasswordAutofillAgent::GetFormDataFromWebForm(
@@ -1858,7 +1861,7 @@
   // intentionally interacting with the page.
   gatekeeper_.RegisterElement(&field);
   field_data_manager_.get()->UpdateFieldDataMap(
-      field, value, FieldPropertiesFlags::kAutofilledOnPageLoad);
+      field_id, value, FieldPropertiesFlags::kAutofilledOnPageLoad);
   autofilled_elements_cache_.emplace(field_id, WebString::FromUTF16(value));
   all_autofilled_elements_.insert(field_id);
 }
diff --git a/components/autofill/content/renderer/password_autofill_agent.h b/components/autofill/content/renderer/password_autofill_agent.h
index 4852ec2..31268fa 100644
--- a/components/autofill/content/renderer/password_autofill_agent.h
+++ b/components/autofill/content/renderer/password_autofill_agent.h
@@ -19,10 +19,10 @@
 #include "components/autofill/content/common/mojom/autofill_agent.mojom.h"
 #include "components/autofill/content/common/mojom/autofill_driver.mojom.h"
 #include "components/autofill/content/renderer/autofill_agent.h"
-#include "components/autofill/content/renderer/field_data_manager.h"
 #include "components/autofill/content/renderer/form_autofill_util.h"
 #include "components/autofill/content/renderer/form_tracker.h"
 #include "components/autofill/content/renderer/html_based_username_detector.h"
+#include "components/autofill/core/common/field_data_manager.h"
 #include "components/autofill/core/common/mojom/autofill_types.mojom.h"
 #include "components/autofill/core/common/password_form.h"
 #include "components/autofill/core/common/password_form_fill_data.h"
diff --git a/components/autofill/core/common/BUILD.gn b/components/autofill/core/common/BUILD.gn
index 23db71d..c08ff0e 100644
--- a/components/autofill/core/common/BUILD.gn
+++ b/components/autofill/core/common/BUILD.gn
@@ -33,6 +33,8 @@
     "autofill_tick_clock.h",
     "autofill_util.cc",
     "autofill_util.h",
+    "field_data_manager.cc",
+    "field_data_manager.h",
     "form_data.cc",
     "form_data.h",
     "form_data_predictions.cc",
@@ -88,6 +90,7 @@
     "autofill_prefs_unittest.cc",
     "autofill_regexes_unittest.cc",
     "autofill_util_unittest.cc",
+    "field_data_manager_unittest.cc",
     "form_data_unittest.cc",
     "form_field_data_unittest.cc",
     "gaia_id_hash_unittest.cc",
diff --git a/components/autofill/core/common/field_data_manager.cc b/components/autofill/core/common/field_data_manager.cc
new file mode 100644
index 0000000..76a92f4
--- /dev/null
+++ b/components/autofill/core/common/field_data_manager.cc
@@ -0,0 +1,90 @@
+// 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 "components/autofill/core/common/field_data_manager.h"
+
+#include "base/check.h"
+#include "base/i18n/case_conversion.h"
+
+namespace autofill {
+
+FieldDataManager::FieldDataManager() = default;
+
+void FieldDataManager::ClearData() {
+  field_value_and_properties_map_.clear();
+}
+
+bool FieldDataManager::HasFieldData(FieldRendererId id) const {
+  return field_value_and_properties_map_.find(id) !=
+         field_value_and_properties_map_.end();
+}
+
+base::string16 FieldDataManager::GetUserTypedValue(FieldRendererId id) const {
+  DCHECK(HasFieldData(id));
+  return field_value_and_properties_map_.at(id).first.value_or(
+      base::string16());
+}
+
+FieldPropertiesMask FieldDataManager::GetFieldPropertiesMask(
+    FieldRendererId id) const {
+  DCHECK(HasFieldData(id));
+  return field_value_and_properties_map_.at(id).second;
+}
+
+bool FieldDataManager::FindMachedValue(const base::string16& value) const {
+  constexpr size_t kMinMatchSize = 3u;
+  const auto lowercase = base::i18n::ToLower(value);
+  for (const auto& map_key : field_value_and_properties_map_) {
+    const base::string16 typed_from_key =
+        map_key.second.first.value_or(base::string16());
+    if (typed_from_key.empty())
+      continue;
+    if (typed_from_key.size() >= kMinMatchSize &&
+        lowercase.find(base::i18n::ToLower(typed_from_key)) !=
+            base::string16::npos)
+      return true;
+  }
+  return false;
+}
+
+void FieldDataManager::UpdateFieldDataMap(FieldRendererId id,
+                                          const base::string16& value,
+                                          FieldPropertiesMask mask) {
+  if (HasFieldData(id)) {
+    field_value_and_properties_map_.at(id).first =
+        base::Optional<base::string16>(value);
+    field_value_and_properties_map_.at(id).second |= mask;
+  } else {
+    field_value_and_properties_map_[id] =
+        std::make_pair(base::Optional<base::string16>(value), mask);
+  }
+  // Reset kUserTyped and kAutofilled flags if the value is empty.
+  if (value.empty()) {
+    field_value_and_properties_map_.at(id).second &=
+        ~(FieldPropertiesFlags::kUserTyped | FieldPropertiesFlags::kAutofilled);
+  }
+}
+
+void FieldDataManager::UpdateFieldDataMapWithNullValue(
+    FieldRendererId id,
+    FieldPropertiesMask mask) {
+  if (HasFieldData(id))
+    field_value_and_properties_map_.at(id).second |= mask;
+  else
+    field_value_and_properties_map_[id] = std::make_pair(base::nullopt, mask);
+}
+
+bool FieldDataManager::DidUserType(FieldRendererId id) const {
+  return HasFieldData(id) &&
+         (GetFieldPropertiesMask(id) & FieldPropertiesFlags::kUserTyped);
+}
+
+bool FieldDataManager::WasAutofilledOnUserTrigger(FieldRendererId id) const {
+  return HasFieldData(id) && (GetFieldPropertiesMask(id) &
+                              FieldPropertiesFlags::kAutofilledOnUserTrigger);
+}
+
+FieldDataManager::~FieldDataManager() = default;
+
+}  // namespace autofill
diff --git a/components/autofill/core/common/field_data_manager.h b/components/autofill/core/common/field_data_manager.h
new file mode 100644
index 0000000..b9f8adcb
--- /dev/null
+++ b/components/autofill/core/common/field_data_manager.h
@@ -0,0 +1,65 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_AUTOFILL_CORE_COMMON_FIELD_DATA_MANAGER_H_
+#define COMPONENTS_AUTOFILL_CORE_COMMON_FIELD_DATA_MANAGER_H_
+
+#include <map>
+
+#include "base/optional.h"
+#include "base/strings/string16.h"
+#include "components/autofill/core/common/form_field_data.h"
+#include "components/autofill/core/common/renderer_id.h"
+
+namespace autofill {
+
+// This class provides the methods to update and get the field data (the pair
+// of user typed value and field properties mask).
+class FieldDataManager : public base::RefCounted<FieldDataManager> {
+ public:
+  using FieldDataMap =
+      std::map<FieldRendererId,
+               std::pair<base::Optional<base::string16>, FieldPropertiesMask>>;
+
+  FieldDataManager();
+
+  void ClearData();
+  bool HasFieldData(FieldRendererId id) const;
+
+  // Updates the field value associated with the key |element| in
+  // |field_value_and_properties_map_|.
+  // Flags in |mask| are added with bitwise OR operation.
+  // If |value| is empty, kUserTyped and kAutofilled should be cleared.
+  void UpdateFieldDataMap(FieldRendererId id,
+                          const base::string16& value,
+                          FieldPropertiesMask mask);
+  // Only update FieldPropertiesMask when value is null.
+  void UpdateFieldDataMapWithNullValue(FieldRendererId id,
+                                       FieldPropertiesMask mask);
+
+  base::string16 GetUserTypedValue(FieldRendererId id) const;
+  FieldPropertiesMask GetFieldPropertiesMask(FieldRendererId id) const;
+
+  // Check if the string |value| is saved in |field_value_and_properties_map_|.
+  bool FindMachedValue(const base::string16& value) const;
+
+  bool DidUserType(FieldRendererId id) const;
+
+  bool WasAutofilledOnUserTrigger(FieldRendererId id) const;
+
+  const FieldDataMap& field_data_map() const {
+    return field_value_and_properties_map_;
+  }
+
+ private:
+  friend class base::RefCounted<FieldDataManager>;
+
+  ~FieldDataManager();
+
+  FieldDataMap field_value_and_properties_map_;
+};
+
+}  // namespace autofill
+
+#endif  // COMPONENTS_AUTOFILL_CORE_COMMON_FIELD_DATA_MANAGER_H_
diff --git a/components/autofill/core/common/field_data_manager_unittest.cc b/components/autofill/core/common/field_data_manager_unittest.cc
new file mode 100644
index 0000000..cbc5b30d
--- /dev/null
+++ b/components/autofill/core/common/field_data_manager_unittest.cc
@@ -0,0 +1,108 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill/core/common/field_data_manager.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "components/autofill/core/common/renderer_id.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using autofill::FormFieldData;
+using base::ASCIIToUTF16;
+using base::UTF8ToUTF16;
+
+namespace autofill {
+
+class FieldDataManagerTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    FormFieldData field1;
+    field1.id_attribute = ASCIIToUTF16("name1");
+    field1.value = ASCIIToUTF16("first");
+    field1.form_control_type = "text";
+    field1.unique_renderer_id = FieldRendererId(1);
+    control_elements_.push_back(field1);
+
+    FormFieldData field2;
+    field2.id_attribute = ASCIIToUTF16("name2");
+    field2.form_control_type = "password";
+    field2.unique_renderer_id = FieldRendererId(2);
+    control_elements_.push_back(field2);
+  }
+
+  void TearDown() override { control_elements_.clear(); }
+
+  std::vector<FormFieldData> control_elements_;
+};
+
+TEST_F(FieldDataManagerTest, UpdateFieldDataMap) {
+  const scoped_refptr<FieldDataManager> field_data_manager =
+      base::MakeRefCounted<FieldDataManager>();
+  field_data_manager->UpdateFieldDataMap(
+      control_elements_[0].unique_renderer_id, control_elements_[0].value,
+      FieldPropertiesFlags::kUserTyped);
+  const FieldRendererId id(control_elements_[0].unique_renderer_id);
+  EXPECT_TRUE(field_data_manager->HasFieldData(id));
+  EXPECT_EQ(UTF8ToUTF16("first"), field_data_manager->GetUserTypedValue(id));
+  EXPECT_EQ(FieldPropertiesFlags::kUserTyped,
+            field_data_manager->GetFieldPropertiesMask(id));
+
+  field_data_manager->UpdateFieldDataMap(
+      control_elements_[0].unique_renderer_id, UTF8ToUTF16("newvalue"),
+      FieldPropertiesFlags::kAutofilled);
+  EXPECT_EQ(UTF8ToUTF16("newvalue"), field_data_manager->GetUserTypedValue(id));
+  FieldPropertiesMask mask =
+      FieldPropertiesFlags::kUserTyped | FieldPropertiesFlags::kAutofilled;
+  EXPECT_EQ(mask, field_data_manager->GetFieldPropertiesMask(id));
+
+  field_data_manager->UpdateFieldDataMap(
+      control_elements_[1].unique_renderer_id, control_elements_[1].value,
+      FieldPropertiesFlags::kAutofilled);
+  EXPECT_EQ(FieldPropertiesFlags::kNoFlags,
+            field_data_manager->GetFieldPropertiesMask(
+                FieldRendererId(control_elements_[1].unique_renderer_id)));
+
+  field_data_manager->ClearData();
+  EXPECT_FALSE(field_data_manager->HasFieldData(id));
+}
+
+TEST_F(FieldDataManagerTest, UpdateFieldDataMapWithNullValue) {
+  const scoped_refptr<FieldDataManager> field_data_manager =
+      base::MakeRefCounted<FieldDataManager>();
+  field_data_manager->UpdateFieldDataMapWithNullValue(
+      control_elements_[0].unique_renderer_id,
+      FieldPropertiesFlags::kUserTyped);
+  const FieldRendererId id(control_elements_[0].unique_renderer_id);
+  EXPECT_TRUE(field_data_manager->HasFieldData(id));
+  EXPECT_EQ(base::string16(), field_data_manager->GetUserTypedValue(id));
+  EXPECT_EQ(FieldPropertiesFlags::kUserTyped,
+            field_data_manager->GetFieldPropertiesMask(id));
+
+  field_data_manager->UpdateFieldDataMapWithNullValue(
+      control_elements_[0].unique_renderer_id,
+      FieldPropertiesFlags::kAutofilled);
+  EXPECT_EQ(base::string16(), field_data_manager->GetUserTypedValue(id));
+  FieldPropertiesMask mask =
+      FieldPropertiesFlags::kUserTyped | FieldPropertiesFlags::kAutofilled;
+  EXPECT_EQ(mask, field_data_manager->GetFieldPropertiesMask(id));
+
+  field_data_manager->UpdateFieldDataMap(
+      control_elements_[0].unique_renderer_id, control_elements_[0].value,
+      FieldPropertiesFlags::kAutofilled);
+  EXPECT_EQ(UTF8ToUTF16("first"), field_data_manager->GetUserTypedValue(id));
+}
+
+TEST_F(FieldDataManagerTest, FindMachedValue) {
+  const scoped_refptr<FieldDataManager> field_data_manager =
+      base::MakeRefCounted<FieldDataManager>();
+  field_data_manager->UpdateFieldDataMap(
+      control_elements_[0].unique_renderer_id, control_elements_[0].value,
+      FieldPropertiesFlags::kUserTyped);
+  EXPECT_TRUE(
+      field_data_manager->FindMachedValue(UTF8ToUTF16("first_element")));
+  EXPECT_FALSE(
+      field_data_manager->FindMachedValue(UTF8ToUTF16("second_element")));
+}
+
+}  // namespace autofill
diff --git a/components/browser_ui/android/bottomsheet/BUILD.gn b/components/browser_ui/android/bottomsheet/BUILD.gn
index f43f294..fb729e7 100644
--- a/components/browser_ui/android/bottomsheet/BUILD.gn
+++ b/components/browser_ui/android/bottomsheet/BUILD.gn
@@ -9,12 +9,14 @@
   sources = [
     "java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetContent.java",
     "java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetController.java",
+    "java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetControllerProvider.java",
     "java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetObserver.java",
     "java/src/org/chromium/components/browser_ui/bottomsheet/EmptyBottomSheetObserver.java",
   ]
 
   deps = [
     ":java_resources",
+    "//base:base_java",
     "//components/browser_ui/widget/android:java",
     "//third_party/android_deps:androidx_annotation_annotation_java",
     "//ui/android:ui_java",
diff --git a/components/browser_ui/android/bottomsheet/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetControllerFactory.java b/components/browser_ui/android/bottomsheet/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetControllerFactory.java
index 732588d..35d78d58 100644
--- a/components/browser_ui/android/bottomsheet/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetControllerFactory.java
+++ b/components/browser_ui/android/bottomsheet/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetControllerFactory.java
@@ -12,6 +12,7 @@
 import org.chromium.base.supplier.Supplier;
 import org.chromium.components.browser_ui.widget.scrim.ScrimCoordinator;
 import org.chromium.ui.KeyboardVisibilityDelegate;
+import org.chromium.ui.base.WindowAndroid;
 
 /** A factory for producing a {@link BottomSheetController}. */
 public class BottomSheetControllerFactory {
@@ -21,7 +22,6 @@
      * @param window The activity's window.
      * @param keyboardDelegate A means of hiding the keyboard.
      * @param root The view that should contain the sheet.
-     * @param inflater A mechanism for building views from XML.
      * @return A new instance of the {@link BottomSheetController}.
      */
     public static ManagedBottomSheetController createBottomSheetController(
@@ -30,4 +30,26 @@
         return new BottomSheetControllerImpl(
                 scrim, initializedCallback, window, keyboardDelegate, root);
     }
+
+    // Redirect methods to provider to make them only accessible to classes that have access to the
+    // factory.
+
+    /**
+     * Attach a shared {@link BottomSheetController} to a {@link WindowAndroid}.
+     * @param windowAndroid The window to attach the sheet's controller to.
+     * @param controller The controller to attach.
+     */
+    public static void attach(
+            WindowAndroid windowAndroid, ManagedBottomSheetController controller) {
+        BottomSheetControllerProvider.attach(windowAndroid, controller);
+    }
+
+    /**
+     * Detach the specified {@link BottomSheetController} from any {@link WindowAndroid}s it is
+     * associated with.
+     * @param controller The controller to remove from any associated windows.
+     */
+    public static void detach(ManagedBottomSheetController controller) {
+        BottomSheetControllerProvider.detach(controller);
+    }
 }
diff --git a/components/browser_ui/android/bottomsheet/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetControllerProvider.java b/components/browser_ui/android/bottomsheet/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetControllerProvider.java
new file mode 100644
index 0000000..053c2fd
--- /dev/null
+++ b/components/browser_ui/android/bottomsheet/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetControllerProvider.java
@@ -0,0 +1,38 @@
+// Copyright 2020 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.components.browser_ui.bottomsheet;
+
+import org.chromium.base.UnownedUserData;
+import org.chromium.base.UnownedUserDataKey;
+import org.chromium.ui.base.WindowAndroid;
+
+/**
+ * This class manages the details associated with binding a {@link BottomSheetController} to user
+ * data on a {@link WindowAndroid}.
+ */
+public class BottomSheetControllerProvider {
+    /** An interface that allows a controller to be associated with an unowned data host. */
+    interface Unowned extends BottomSheetController, UnownedUserData {}
+
+    /** The key used to bind the controller to the unowned data host. */
+    private static final UnownedUserDataKey<Unowned> KEY = new UnownedUserDataKey<>(Unowned.class);
+
+    /**
+     * Get the shared {@link BottomSheetController} from the provided {@link WindowAndroid}.
+     * @param windowAndroid The window to pull the controller from.
+     * @return A shared instance of a {@link BottomSheetController}.
+     */
+    public static BottomSheetController from(WindowAndroid windowAndroid) {
+        return KEY.retrieveDataFromHost(windowAndroid.getUnownedUserDataHost());
+    }
+
+    static void attach(WindowAndroid windowAndroid, Unowned controller) {
+        KEY.attachToHost(windowAndroid.getUnownedUserDataHost(), controller);
+    }
+
+    static void detach(Unowned controller) {
+        KEY.detachFromAllHosts(controller);
+    }
+}
diff --git a/components/browser_ui/android/bottomsheet/java/src/org/chromium/components/browser_ui/bottomsheet/ManagedBottomSheetController.java b/components/browser_ui/android/bottomsheet/java/src/org/chromium/components/browser_ui/bottomsheet/ManagedBottomSheetController.java
index aa045e5..60cd9b4 100644
--- a/components/browser_ui/android/bottomsheet/java/src/org/chromium/components/browser_ui/bottomsheet/ManagedBottomSheetController.java
+++ b/components/browser_ui/android/bottomsheet/java/src/org/chromium/components/browser_ui/bottomsheet/ManagedBottomSheetController.java
@@ -10,7 +10,8 @@
  * An interface for the owning object to manage interaction between the bottom sheet and the rest
  * of the system.
  */
-public interface ManagedBottomSheetController extends BottomSheetController {
+public interface ManagedBottomSheetController
+        extends BottomSheetController, BottomSheetControllerProvider.Unowned {
     /**
      * Temporarily suppress the bottom sheet while other UI is showing. This will not itself change
      * the content displayed by the sheet.
diff --git a/components/browser_ui/styles/android/BUILD.gn b/components/browser_ui/styles/android/BUILD.gn
index d1ee52c..67ef9142 100644
--- a/components/browser_ui/styles/android/BUILD.gn
+++ b/components/browser_ui/styles/android/BUILD.gn
@@ -93,12 +93,17 @@
     "java/res/drawable-xxxhdpi/plus.png",
     "java/res/drawable-xxxhdpi/settings_all_sites.png",
     "java/res/drawable-xxxhdpi/top_round.9.png",
+    "java/res/drawable/ic_done_blue.xml",
     "java/res/drawable/ic_eye_crossed.xml",
     "java/res/drawable/ic_help_and_feedback.xml",
+    "java/res/drawable/ic_info_outline_grey_16dp.xml",
+    "java/res/drawable/ic_info_outline_grey_24dp.xml",
     "java/res/drawable/ic_offline_pin_24dp_on_light_bg.xml",
     "java/res/drawable/ic_security_grey.xml",
     "java/res/drawable/ic_update_grey.xml",
     "java/res/drawable/ic_vpn_key_grey.xml",
+    "java/res/drawable/ic_warning_red_16dp.xml",
+    "java/res/drawable/ic_warning_red_24dp.xml",
     "java/res/drawable/permission_location.xml",
     "java/res/values-night/colors.xml",
     "java/res/values/colors.xml",
diff --git a/components/browser_ui/styles/android/java/res/drawable/ic_done_blue.xml b/components/browser_ui/styles/android/java/res/drawable/ic_done_blue.xml
new file mode 100644
index 0000000..92d989d
--- /dev/null
+++ b/components/browser_ui/styles/android/java/res/drawable/ic_done_blue.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2020 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"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="@color/default_icon_color_blue"
+        android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>
+</vector>
diff --git a/chrome/android/java/res/drawable/ic_info_outline_grey.xml b/components/browser_ui/styles/android/java/res/drawable/ic_info_outline_grey_16dp.xml
similarity index 100%
rename from chrome/android/java/res/drawable/ic_info_outline_grey.xml
rename to components/browser_ui/styles/android/java/res/drawable/ic_info_outline_grey_16dp.xml
diff --git a/components/browser_ui/styles/android/java/res/drawable/ic_info_outline_grey_24dp.xml b/components/browser_ui/styles/android/java/res/drawable/ic_info_outline_grey_24dp.xml
new file mode 100644
index 0000000..b07e14a
--- /dev/null
+++ b/components/browser_ui/styles/android/java/res/drawable/ic_info_outline_grey_24dp.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2020 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"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="@color/default_icon_color"
+        android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
+</vector>
diff --git a/chrome/android/java/res/drawable/ic_warning_red.xml b/components/browser_ui/styles/android/java/res/drawable/ic_warning_red_16dp.xml
similarity index 100%
rename from chrome/android/java/res/drawable/ic_warning_red.xml
rename to components/browser_ui/styles/android/java/res/drawable/ic_warning_red_16dp.xml
diff --git a/components/browser_ui/styles/android/java/res/drawable/ic_warning_red_24dp.xml b/components/browser_ui/styles/android/java/res/drawable/ic_warning_red_24dp.xml
new file mode 100644
index 0000000..2590af7a
--- /dev/null
+++ b/components/browser_ui/styles/android/java/res/drawable/ic_warning_red_24dp.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2020 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"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="@color/default_red"
+        android:pathData="M1,21h22L12,2 1,21zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z"/>
+</vector>
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/PromoDialog.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/PromoDialog.java
index 56cf042..1711645 100644
--- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/PromoDialog.java
+++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/PromoDialog.java
@@ -103,10 +103,9 @@
      * Force the promo dialog to have a fully opaque background hiding any underlying content.
      */
     protected void forceOpaqueBackground() {
-        LayerDrawable background = ApiCompatibilityUtils.createLayerDrawable(
-                new Drawable[] {new ColorDrawable(Color.WHITE),
-                        new ColorDrawable(ApiCompatibilityUtils.getColor(
-                                getContext().getResources(), R.color.modal_dialog_scrim_color))});
+        LayerDrawable background = new LayerDrawable(new Drawable[] {new ColorDrawable(Color.WHITE),
+                new ColorDrawable(ApiCompatibilityUtils.getColor(
+                        getContext().getResources(), R.color.modal_dialog_scrim_color))});
         mScrimView.setBackground(background);
     }
 
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/highlight/ViewHighlighter.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/highlight/ViewHighlighter.java
index c0b6982..c8a398be 100644
--- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/highlight/ViewHighlighter.java
+++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/highlight/ViewHighlighter.java
@@ -11,7 +11,6 @@
 import android.graphics.drawable.LayerDrawable;
 import android.view.View;
 
-import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.ContextUtils;
 import org.chromium.components.browser_ui.widget.R;
 
@@ -95,9 +94,9 @@
             background = background.getConstantState().newDrawable(resources);
         }
 
-        LayerDrawable drawable = ApiCompatibilityUtils.createLayerDrawable(background == null
-                        ? new Drawable[] {pulseDrawable}
-                        : new Drawable[] {background, pulseDrawable});
+        Drawable[] layers = background == null ? new Drawable[] {pulseDrawable}
+                                               : new Drawable[] {background, pulseDrawable};
+        LayerDrawable drawable = new LayerDrawable(layers);
         view.setBackground(drawable);
         view.setTag(R.id.highlight_state, true);
 
diff --git a/components/browsing_data/content/mock_service_worker_helper.cc b/components/browsing_data/content/mock_service_worker_helper.cc
index 9b4104d3..1a9a0458 100644
--- a/components/browsing_data/content/mock_service_worker_helper.cc
+++ b/components/browsing_data/content/mock_service_worker_helper.cc
@@ -29,19 +29,19 @@
   callback_ = std::move(callback);
 }
 
-void MockServiceWorkerHelper::DeleteServiceWorkers(const GURL& origin) {
+void MockServiceWorkerHelper::DeleteServiceWorkers(const url::Origin& origin) {
   ASSERT_TRUE(base::Contains(origins_, origin));
   origins_[origin] = false;
 }
 
 void MockServiceWorkerHelper::AddServiceWorkerSamples() {
-  const GURL kOrigin1("https://swhost1:1/");
-  const GURL kOrigin2("https://swhost2:2/");
+  const url::Origin kOrigin1 = url::Origin::Create(GURL("https://swhost1:1/"));
+  const url::Origin kOrigin2 = url::Origin::Create(GURL("https://swhost2:2/"));
 
-  response_.emplace_back(url::Origin::Create(kOrigin1), 1, base::Time());
+  response_.emplace_back(kOrigin1, 1, base::Time());
   origins_[kOrigin1] = true;
 
-  response_.emplace_back(url::Origin::Create(kOrigin2), 2, base::Time());
+  response_.emplace_back(kOrigin2, 2, base::Time());
   origins_[kOrigin2] = true;
 }
 
diff --git a/components/browsing_data/content/mock_service_worker_helper.h b/components/browsing_data/content/mock_service_worker_helper.h
index e53bbc4c..08417175 100644
--- a/components/browsing_data/content/mock_service_worker_helper.h
+++ b/components/browsing_data/content/mock_service_worker_helper.h
@@ -40,13 +40,13 @@
 
   // ServiceWorkerHelper.
   void StartFetching(FetchCallback callback) override;
-  void DeleteServiceWorkers(const GURL& origin) override;
+  void DeleteServiceWorkers(const url::Origin& origin) override;
 
  private:
   ~MockServiceWorkerHelper() override;
 
   FetchCallback callback_;
-  std::map<GURL, bool> origins_;
+  std::map<url::Origin, bool> origins_;
   std::list<content::StorageUsageInfo> response_;
 
   DISALLOW_COPY_AND_ASSIGN(MockServiceWorkerHelper);
diff --git a/components/browsing_data/content/service_worker_helper.cc b/components/browsing_data/content/service_worker_helper.cc
index d6243cc..34ba37c 100644
--- a/components/browsing_data/content/service_worker_helper.cc
+++ b/components/browsing_data/content/service_worker_helper.cc
@@ -61,7 +61,7 @@
           std::move(callback)));
 }
 
-void ServiceWorkerHelper::DeleteServiceWorkers(const GURL& origin) {
+void ServiceWorkerHelper::DeleteServiceWorkers(const url::Origin& origin) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   content::RunOrPostTaskOnThread(
       FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
@@ -78,7 +78,8 @@
       &GetAllOriginsInfoForServiceWorkerCallback, std::move(callback)));
 }
 
-void ServiceWorkerHelper::DeleteServiceWorkersOnCoreThread(const GURL& origin) {
+void ServiceWorkerHelper::DeleteServiceWorkersOnCoreThread(
+    const url::Origin& origin) {
   DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
   service_worker_context_->DeleteForOrigin(origin, base::DoNothing());
 }
@@ -124,8 +125,9 @@
       FROM_HERE, base::BindOnce(std::move(callback), result));
 }
 
-void CannedServiceWorkerHelper::DeleteServiceWorkers(const GURL& origin) {
-  pending_origins_.erase(url::Origin::Create(origin));
+void CannedServiceWorkerHelper::DeleteServiceWorkers(
+    const url::Origin& origin) {
+  pending_origins_.erase(origin);
   ServiceWorkerHelper::DeleteServiceWorkers(origin);
 }
 
diff --git a/components/browsing_data/content/service_worker_helper.h b/components/browsing_data/content/service_worker_helper.h
index 32475ad..8333cd2d 100644
--- a/components/browsing_data/content/service_worker_helper.h
+++ b/components/browsing_data/content/service_worker_helper.h
@@ -44,7 +44,7 @@
   // |callback|. This must be called only in the UI thread.
   virtual void StartFetching(FetchCallback callback);
   // Requests the Service Worker data for an origin be deleted.
-  virtual void DeleteServiceWorkers(const GURL& origin);
+  virtual void DeleteServiceWorkers(const url::Origin& origin);
 
  protected:
   virtual ~ServiceWorkerHelper();
@@ -59,7 +59,7 @@
   void FetchServiceWorkerUsageInfoOnCoreThread(FetchCallback callback);
 
   // Deletes Service Workers for an origin on the service worker core thread.
-  void DeleteServiceWorkersOnCoreThread(const GURL& origin);
+  void DeleteServiceWorkersOnCoreThread(const url::Origin& origin);
 
   DISALLOW_COPY_AND_ASSIGN(ServiceWorkerHelper);
 };
@@ -89,7 +89,7 @@
 
   // ServiceWorkerHelper methods.
   void StartFetching(FetchCallback callback) override;
-  void DeleteServiceWorkers(const GURL& origin) override;
+  void DeleteServiceWorkers(const url::Origin& origin) override;
 
  private:
   ~CannedServiceWorkerHelper() override;
diff --git a/components/browsing_data/content/service_worker_helper_unittest.cc b/components/browsing_data/content/service_worker_helper_unittest.cc
index 39441c65..9bb8cce 100644
--- a/components/browsing_data/content/service_worker_helper_unittest.cc
+++ b/components/browsing_data/content/service_worker_helper_unittest.cc
@@ -62,7 +62,7 @@
   helper->Add(url::Origin::Create(origin1));
   helper->Add(url::Origin::Create(origin2));
   EXPECT_EQ(2u, helper->GetCount());
-  helper->DeleteServiceWorkers(origin2);
+  helper->DeleteServiceWorkers(url::Origin::Create(origin2));
   EXPECT_EQ(1u, helper->GetCount());
 }
 
diff --git a/components/content_capture/browser/content_capture_receiver_test.cc b/components/content_capture/browser/content_capture_receiver_test.cc
index 712fd88..7858233 100644
--- a/components/content_capture/browser/content_capture_receiver_test.cc
+++ b/components/content_capture/browser/content_capture_receiver_test.cc
@@ -160,8 +160,8 @@
     content_capture_receiver_manager_helper_ =
         static_cast<ContentCaptureReceiverManagerHelper*>(
             ContentCaptureReceiverManager::FromWebContents(web_contents()));
-    // This needed to keep the WebContentsObserverSequenceChecker checks happy
-    // for when AppendChild is called.
+    // This needed to keep the WebContentsObserverConsistencyChecker checks
+    // happy for when AppendChild is called.
     NavigateAndCommit(GURL(kMainFrameUrl));
     content_capture_sender_ = std::make_unique<FakeContentCaptureSender>();
     main_frame_ = web_contents()->GetMainFrame();
@@ -644,8 +644,8 @@
   void SetUp() override {
     // Setup multiple frames before creates ContentCaptureReceiverManager.
     content::RenderViewHostTestHarness::SetUp();
-    // This needed to keep the WebContentsObserverSequenceChecker checks happy
-    // for when AppendChild is called.
+    // This needed to keep the WebContentsObserverConsistencyChecker checks
+    // happy for when AppendChild is called.
     NavigateAndCommit(GURL("about:blank"));
     content::RenderFrameHostTester::For(web_contents()->GetMainFrame())
         ->AppendChild("child");
diff --git a/components/download/internal/common/download_file_impl.cc b/components/download/internal/common/download_file_impl.cc
index ce2f689..33bb7d1 100644
--- a/components/download/internal/common/download_file_impl.cc
+++ b/components/download/internal/common/download_file_impl.cc
@@ -693,23 +693,7 @@
 
     // All the stream reader are completed, shut down file IO processing.
     if (IsDownloadCompleted()) {
-      RecordFileBandwidth(bytes_seen_,
-                          base::TimeTicks::Now() - download_start_);
-      if (record_stream_bandwidth_) {
-        RecordParallelizableDownloadStats(
-            bytes_seen_with_parallel_streams_,
-            download_time_with_parallel_streams_,
-            bytes_seen_without_parallel_streams_,
-            download_time_without_parallel_streams_, IsSparseFile());
-      }
-      weak_factory_.InvalidateWeakPtrs();
-      std::unique_ptr<crypto::SecureHash> hash_state = file_.Finish();
-      update_timer_.reset();
-      main_task_runner_->PostTask(
-          FROM_HERE,
-          base::BindOnce(&DownloadDestinationObserver::DestinationCompleted,
-                         observer_, TotalBytesReceived(),
-                         std::move(hash_state)));
+      OnDownloadCompleted();
     } else {
       // If all the stream completes and we still not able to complete, trigger
       // a content length mismatch error so auto resumption will be performed.
@@ -719,6 +703,23 @@
   }
 }
 
+void DownloadFileImpl::OnDownloadCompleted() {
+  RecordFileBandwidth(bytes_seen_, base::TimeTicks::Now() - download_start_);
+  if (record_stream_bandwidth_) {
+    RecordParallelizableDownloadStats(
+        bytes_seen_with_parallel_streams_, download_time_with_parallel_streams_,
+        bytes_seen_without_parallel_streams_,
+        download_time_without_parallel_streams_, IsSparseFile());
+  }
+  weak_factory_.InvalidateWeakPtrs();
+  std::unique_ptr<crypto::SecureHash> hash_state = file_.Finish();
+  update_timer_.reset();
+  main_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&DownloadDestinationObserver::DestinationCompleted,
+                     observer_, TotalBytesReceived(), std::move(hash_state)));
+}
+
 void DownloadFileImpl::RegisterAndActivateStream(SourceStream* source_stream) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
@@ -839,8 +840,12 @@
 
   SendUpdate();  // Make info up to date before error.
 
+  // If the download can recover from error, check if it already finishes.
+  // Otherwise, send an error update when all streams are finished.
   if (!can_recover_from_error)
     SendErrorUpdateIfFinished(reason);
+  else if (IsDownloadCompleted())
+    OnDownloadCompleted();
 }
 
 void DownloadFileImpl::SendErrorUpdateIfFinished(
diff --git a/components/download/internal/common/download_item_impl.cc b/components/download/internal/common/download_item_impl.cc
index 9171cf40..b4c6ecb 100644
--- a/components/download/internal/common/download_item_impl.cc
+++ b/components/download/internal/common/download_item_impl.cc
@@ -973,17 +973,17 @@
 }
 
 bool DownloadItemImpl::IsDangerous() const {
-  return (danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE ||
-          danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_URL ||
-          danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT ||
-          danger_type_ == DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT ||
-          danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST ||
-          danger_type_ == DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED ||
-          danger_type_ == DOWNLOAD_DANGER_TYPE_BLOCKED_TOO_LARGE ||
-          danger_type_ == DOWNLOAD_DANGER_TYPE_BLOCKED_PASSWORD_PROTECTED ||
-          danger_type_ == DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_BLOCK ||
-          danger_type_ == DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_WARNING ||
-          danger_type_ == DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING);
+  return danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE ||
+         danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_URL ||
+         danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT ||
+         danger_type_ == DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT ||
+         danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST ||
+         danger_type_ == DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED ||
+         danger_type_ == DOWNLOAD_DANGER_TYPE_BLOCKED_PASSWORD_PROTECTED ||
+         danger_type_ == DOWNLOAD_DANGER_TYPE_BLOCKED_TOO_LARGE ||
+         danger_type_ == DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_WARNING ||
+         danger_type_ == DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_BLOCK ||
+         danger_type_ == DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING;
 }
 
 bool DownloadItemImpl::IsMixedContent() const {
diff --git a/components/download/public/common/download_features.cc b/components/download/public/common/download_features.cc
index 0a285685..41ec09e2 100644
--- a/components/download/public/common/download_features.cc
+++ b/components/download/public/common/download_features.cc
@@ -66,4 +66,10 @@
 
 }  // namespace features
 
+namespace switches {
+
+const char kDownloadLaterDebugOnWifi[] = "download-later-debug-on-wifi";
+
+}  // namespace switches
+
 }  // namespace download
diff --git a/components/download/public/common/download_features.h b/components/download/public/common/download_features.h
index 82a9895c..55a1097 100644
--- a/components/download/public/common/download_features.h
+++ b/components/download/public/common/download_features.h
@@ -62,6 +62,14 @@
 
 }  // namespace features
 
+namespace switches {
+
+// If set, show the download later dialog without the requirement of being on
+// cellular network.
+COMPONENTS_DOWNLOAD_EXPORT extern const char kDownloadLaterDebugOnWifi[];
+
+}  // namespace switches
+
 }  // namespace download
 
 #endif  // COMPONENTS_DOWNLOAD_PUBLIC_COMMON_DOWNLOAD_FEATURES_H_
diff --git a/components/download/public/common/download_file_impl.h b/components/download/public/common/download_file_impl.h
index 4a77764..6d2a0fe7 100644
--- a/components/download/public/common/download_file_impl.h
+++ b/components/download/public/common/download_file_impl.h
@@ -324,6 +324,9 @@
   // See |cancel_request_callback_|.
   void CancelRequest(int64_t offset);
 
+  // Called when the download is completed.
+  void OnDownloadCompleted();
+
   // Print the internal states for debugging.
   void DebugStates() const;
 
diff --git a/components/enterprise/browser/reporting/profile_report_generator.cc b/components/enterprise/browser/reporting/profile_report_generator.cc
index be3fc13..1c6e46e 100644
--- a/components/enterprise/browser/reporting/profile_report_generator.cc
+++ b/components/enterprise/browser/reporting/profile_report_generator.cc
@@ -8,6 +8,7 @@
 
 #include "base/files/file_path.h"
 #include "components/enterprise/browser/reporting/policy_info.h"
+#include "components/enterprise/browser/reporting/reporting_delegate_factory.h"
 #include "components/policy/core/browser/policy_conversions.h"
 
 namespace em = enterprise_management;
@@ -15,8 +16,8 @@
 namespace enterprise_reporting {
 
 ProfileReportGenerator::ProfileReportGenerator(
-    std::unique_ptr<ProfileReportGenerator::Delegate> delegate)
-    : delegate_(std::move(delegate)) {}
+    ReportingDelegateFactory* delegate_factory)
+    : delegate_(delegate_factory->GetProfileReportGeneratorDelegate()) {}
 
 ProfileReportGenerator::~ProfileReportGenerator() = default;
 
diff --git a/components/enterprise/browser/reporting/profile_report_generator.h b/components/enterprise/browser/reporting/profile_report_generator.h
index d473783c..63ce13d 100644
--- a/components/enterprise/browser/reporting/profile_report_generator.h
+++ b/components/enterprise/browser/reporting/profile_report_generator.h
@@ -22,6 +22,8 @@
 
 namespace enterprise_reporting {
 
+class ReportingDelegateFactory;
+
 /**
  * A report generator that collects Profile related information that is selected
  * by policies.
@@ -57,11 +59,7 @@
     GetCloudPolicyManager() = 0;
   };
 
-  // TODO(crbug/1091916): When this class is moved to components, it should use
-  // the reporting delegate factory to get its delegate instead of requiring it
-  // in the constructor.
-  explicit ProfileReportGenerator(
-      std::unique_ptr<ProfileReportGenerator::Delegate> delegate);
+  explicit ProfileReportGenerator(ReportingDelegateFactory* delegate_factory);
   ProfileReportGenerator(const ProfileReportGenerator&) = delete;
   ProfileReportGenerator& operator=(const ProfileReportGenerator&) = delete;
   ~ProfileReportGenerator();
diff --git a/components/enterprise/browser/reporting/reporting_delegate_factory.h b/components/enterprise/browser/reporting/reporting_delegate_factory.h
index 4b7960a8..23489fd 100644
--- a/components/enterprise/browser/reporting/reporting_delegate_factory.h
+++ b/components/enterprise/browser/reporting/reporting_delegate_factory.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "components/enterprise/browser/reporting/browser_report_generator.h"
+#include "components/enterprise/browser/reporting/profile_report_generator.h"
 
 namespace enterprise_reporting {
 
@@ -21,6 +22,9 @@
 
   virtual std::unique_ptr<BrowserReportGenerator::Delegate>
   GetBrowserReportGeneratorDelegate() = 0;
+
+  virtual std::unique_ptr<ProfileReportGenerator::Delegate>
+  GetProfileReportGeneratorDelegate() = 0;
 };
 
 }  // namespace enterprise_reporting
diff --git a/components/exo/wayland/wayland_display_observer.cc b/components/exo/wayland/wayland_display_observer.cc
index 850781e..473b9bf 100644
--- a/components/exo/wayland/wayland_display_observer.cc
+++ b/components/exo/wayland/wayland_display_observer.cc
@@ -26,16 +26,11 @@
   display::Screen::GetScreen()->RemoveObserver(this);
 }
 
-void WaylandDisplayObserver::SetScaleObserver(
-    base::WeakPtr<ScaleObserver> scale_observer) {
-  scale_observer_ = scale_observer;
+void WaylandDisplayObserver::AddScaleObserver(ScaleObserver* scale_observer) {
+  scale_observers_.AddObserver(scale_observer);
   SendDisplayMetrics();
 }
 
-bool WaylandDisplayObserver::HasScaleObserver() const {
-  return !!scale_observer_;
-}
-
 void WaylandDisplayObserver::OnDisplayMetricsChanged(
     const display::Display& display,
     uint32_t changed_metrics) {
@@ -101,8 +96,8 @@
                       WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED,
                       bounds.width(), bounds.height(), static_cast<int>(60000));
 
-  if (HasScaleObserver())
-    scale_observer_->OnDisplayScalesChanged(display);
+  for (auto& observer : scale_observers_)
+    observer.OnDisplayScalesChanged(display);
 
   if (wl_resource_get_version(output_resource_) >=
       WL_OUTPUT_DONE_SINCE_VERSION) {
diff --git a/components/exo/wayland/wayland_display_observer.h b/components/exo/wayland/wayland_display_observer.h
index 9bd1266..7bf54bd3 100644
--- a/components/exo/wayland/wayland_display_observer.h
+++ b/components/exo/wayland/wayland_display_observer.h
@@ -8,6 +8,7 @@
 #include <stdint.h>
 #include <wayland-server-protocol-core.h>
 
+#include "base/observer_list.h"
 #include "ui/display/display.h"
 #include "ui/display/display_observer.h"
 
@@ -18,20 +19,20 @@
 
 class WaylandDisplayObserver : public display::DisplayObserver {
  public:
-  class ScaleObserver : public base::SupportsWeakPtr<ScaleObserver> {
+  class ScaleObserver : public base::CheckedObserver {
    public:
     ScaleObserver() {}
 
     virtual void OnDisplayScalesChanged(const display::Display& display) = 0;
 
    protected:
-    virtual ~ScaleObserver() {}
+    ~ScaleObserver() override {}
   };
 
   WaylandDisplayObserver(int64_t id, wl_resource* output_resource);
   ~WaylandDisplayObserver() override;
-  void SetScaleObserver(base::WeakPtr<ScaleObserver> scale_observer);
-  bool HasScaleObserver() const;
+  void AddScaleObserver(ScaleObserver* scale_observer);
+  bool HasScaleObserver(ScaleObserver* scale_observer) const;
 
   // Overridden from display::DisplayObserver:
   void OnDisplayMetricsChanged(const display::Display& display,
@@ -49,7 +50,7 @@
   // The output resource associated with the display.
   wl_resource* const output_resource_;
 
-  base::WeakPtr<ScaleObserver> scale_observer_;
+  base::ObserverList<ScaleObserver> scale_observers_;
 
   DISALLOW_COPY_AND_ASSIGN(WaylandDisplayObserver);
 };
diff --git a/components/exo/wayland/zaura_shell.cc b/components/exo/wayland/zaura_shell.cc
index d29cedf..3b439ed 100644
--- a/components/exo/wayland/zaura_shell.cc
+++ b/components/exo/wayland/zaura_shell.cc
@@ -471,18 +471,12 @@
                                 wl_resource* output_resource) {
   WaylandDisplayObserver* display_observer =
       GetUserDataAs<WaylandDisplayObserver>(output_resource);
-  if (display_observer->HasScaleObserver()) {
-    wl_resource_post_error(
-        resource, ZAURA_SHELL_ERROR_AURA_OUTPUT_EXISTS,
-        "an aura output object for that output already exists");
-    return;
-  }
 
   wl_resource* aura_output_resource = wl_resource_create(
       client, &zaura_output_interface, wl_resource_get_version(resource), id);
 
   auto aura_output = std::make_unique<AuraOutput>(aura_output_resource);
-  display_observer->SetScaleObserver(aura_output->AsWeakPtr());
+  display_observer->AddScaleObserver(aura_output.get());
 
   SetImplementation(aura_output_resource, nullptr, std::move(aura_output));
 }
diff --git a/components/exo/wayland/zcr_remote_shell.cc b/components/exo/wayland/zcr_remote_shell.cc
index ce7c991..590603bb 100644
--- a/components/exo/wayland/zcr_remote_shell.cc
+++ b/components/exo/wayland/zcr_remote_shell.cc
@@ -34,6 +34,7 @@
 #include "components/exo/surface_delegate.h"
 #include "components/exo/toast_surface.h"
 #include "components/exo/wayland/server_util.h"
+#include "components/exo/wayland/wayland_display_observer.h"
 #include "components/exo/wm_helper_chromeos.h"
 #include "ui/display/display_observer.h"
 #include "ui/display/screen.h"
@@ -131,23 +132,6 @@
   return gfx::Rect(new_x, new_y, new_right - new_x, new_bottom - new_y);
 }
 
-// Create the insets make sure that work area will be within the chrome's
-// work area when converted to the pixel on client side.
-gfx::Insets GetAdjustedInsets(const display::Display& display) {
-  float scale = display.device_scale_factor();
-  gfx::Size size_in_pixel = display.GetSizeInPixel();
-  gfx::Rect work_area_in_display = display.work_area();
-  work_area_in_display.Offset(-display.bounds().x(), -display.bounds().y());
-  gfx::Rect work_area_in_pixel = ScaleBoundsToPixelSnappedToParent(
-      size_in_pixel, display.bounds().size(), scale, work_area_in_display);
-  gfx::Insets insets_in_pixel =
-      gfx::Rect(size_in_pixel).InsetsFrom(work_area_in_pixel);
-  return gfx::Insets(base::Ceil(insets_in_pixel.top() / scale),
-                     base::Ceil(insets_in_pixel.left() / scale),
-                     base::Ceil(insets_in_pixel.bottom() / scale),
-                     base::Ceil(insets_in_pixel.right() / scale));
-}
-
 ash::ShelfLayoutManager* GetShelfLayoutManagerForDisplay(
     const display::Display& display) {
   auto* root = ash::Shell::GetRootWindowForDisplayId(display.id());
@@ -726,7 +710,92 @@
 }
 
 const struct zcr_toast_surface_v1_interface toast_surface_implementation = {
-    toast_surface_destroy, toast_surface_set_position, toast_surface_set_size};
+    toast_surface_destroy,
+    toast_surface_set_position,
+    toast_surface_set_size,
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// remote_output_interface:
+
+void remote_output_destroy(wl_client* client, wl_resource* resource) {
+  wl_resource_destroy(resource);
+}
+
+const struct zcr_remote_output_v1_interface remote_output_implementation = {
+    remote_output_destroy,
+};
+
+class WaylandRemoteOutput : public WaylandDisplayObserver::ScaleObserver {
+ public:
+  explicit WaylandRemoteOutput(wl_resource* resource) : resource_(resource) {}
+
+  // Overridden from WaylandDisplayObserver::ScaleObserver:
+  void OnDisplayScalesChanged(const display::Display& display) override {
+    if (wl_resource_get_version(resource_) < 29)
+      return;
+
+    if (!initial_config_sent_) {
+      initial_config_sent_ = true;
+
+      uint32_t display_id_hi = static_cast<uint32_t>(display.id() >> 32);
+      uint32_t display_id_lo = static_cast<uint32_t>(display.id());
+      zcr_remote_output_v1_send_display_id(resource_, display_id_hi,
+                                           display_id_lo);
+
+      constexpr int64_t DISPLAY_ID_PORT_MASK = 0xff;
+      uint32_t port =
+          static_cast<uint32_t>(display.id() & DISPLAY_ID_PORT_MASK);
+      zcr_remote_output_v1_send_port(resource_, port);
+
+      wl_array data;
+      wl_array_init(&data);
+
+      const auto& bytes =
+          WMHelper::GetInstance()->GetDisplayIdentificationData(display.id());
+      for (uint8_t byte : bytes) {
+        uint8_t* ptr =
+            static_cast<uint8_t*>(wl_array_add(&data, sizeof(uint8_t)));
+        DCHECK(ptr);
+        *ptr = byte;
+      }
+
+      zcr_remote_output_v1_send_identification_data(resource_, &data);
+      wl_array_release(&data);
+    }
+
+    float device_scale_factor = display.device_scale_factor();
+    gfx::Size size_in_pixel = display.GetSizeInPixel();
+
+    gfx::Insets insets_in_pixel = GetWorkAreaInsetsInPixel(
+        display, device_scale_factor, size_in_pixel, display.work_area());
+    zcr_remote_output_v1_send_insets(
+        resource_, insets_in_pixel.left(), insets_in_pixel.top(),
+        insets_in_pixel.right(), insets_in_pixel.bottom());
+
+    gfx::Insets stable_insets_in_pixel =
+        GetWorkAreaInsetsInPixel(display, device_scale_factor, size_in_pixel,
+                                 GetStableWorkArea(display));
+    zcr_remote_output_v1_send_stable_insets(
+        resource_, stable_insets_in_pixel.left(), stable_insets_in_pixel.top(),
+        stable_insets_in_pixel.right(), stable_insets_in_pixel.bottom());
+
+    auto* shelf_layout_manager = GetShelfLayoutManagerForDisplay(display);
+    int systemui_visibility =
+        shelf_layout_manager->visibility_state() == ash::SHELF_AUTO_HIDE
+            ? ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_AUTOHIDE_NON_STICKY
+            : ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_VISIBLE;
+    zcr_remote_output_v1_send_systemui_visibility(resource_,
+                                                  systemui_visibility);
+  }
+
+ private:
+  wl_resource* const resource_;
+
+  bool initial_config_sent_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(WaylandRemoteOutput);
+};
 
 ////////////////////////////////////////////////////////////////////////////////
 // remote_shell_interface:
@@ -792,6 +861,10 @@
     return display_->CreateToastSurface(surface, default_device_scale_factor);
   }
 
+  void SetUseDefaultScaleCancellation(bool use_default_scale) {
+    use_default_scale_cancellation_ = use_default_scale;
+  }
+
   // TODO(mukai, oshima): rewrite this through delegate-style instead of
   // creating callbacks.
   ClientControlledShellSurface::BoundsChangedCallback
@@ -846,10 +919,18 @@
   // Overridden from ash::TabletModeObserver:
   void OnTabletModeStarted() override {
     layout_mode_ = ZCR_REMOTE_SHELL_V1_LAYOUT_MODE_TABLET;
+    if (wl_resource_get_version(remote_shell_resource_) >=
+        ZCR_REMOTE_SHELL_V1_LAYOUT_MODE_SINCE_VERSION)
+      zcr_remote_shell_v1_send_layout_mode(remote_shell_resource_,
+                                           layout_mode_);
     ScheduleSendDisplayMetrics(kConfigureDelayAfterLayoutSwitchMs);
   }
   void OnTabletModeEnding() override {
     layout_mode_ = ZCR_REMOTE_SHELL_V1_LAYOUT_MODE_WINDOWED;
+    if (wl_resource_get_version(remote_shell_resource_) >=
+        ZCR_REMOTE_SHELL_V1_LAYOUT_MODE_SINCE_VERSION)
+      zcr_remote_shell_v1_send_layout_mode(remote_shell_resource_,
+                                           layout_mode_);
     ScheduleSendDisplayMetrics(kConfigureDelayAfterLayoutSwitchMs);
   }
   void OnTabletModeEnded() override {}
@@ -896,8 +977,6 @@
     double default_dsf = GetDefaultDeviceScaleFactor();
 
     for (const auto& display : screen->GetAllDisplays()) {
-      const gfx::Rect& bounds = display.bounds();
-
       double device_scale_factor = display.device_scale_factor();
 
       uint32_t display_id_hi = static_cast<uint32_t>(display.id() >> 32);
@@ -933,13 +1012,12 @@
         gfx::Size size_in_client_pixel = gfx::ScaleToRoundedSize(
             size_in_pixel, server_to_client_pixel_scale);
 
-        gfx::Insets insets_in_client_pixel = GetWorkAreaInsetsInClientPixel(
+        gfx::Insets insets_in_client_pixel = GetWorkAreaInsetsInPixel(
             display, default_dsf, size_in_client_pixel, display.work_area());
 
         gfx::Insets stable_insets_in_client_pixel =
-            GetWorkAreaInsetsInClientPixel(display, default_dsf,
-                                           size_in_client_pixel,
-                                           GetStableWorkArea(display));
+            GetWorkAreaInsetsInPixel(display, default_dsf, size_in_client_pixel,
+                                     GetStableWorkArea(display));
 
         // TODO(b/148977363): Fix the issue and remove the hack.
         MaybeApplyCTSHack(layout_mode_, size_in_pixel, &insets_in_client_pixel,
@@ -961,19 +1039,9 @@
             stable_insets_in_client_pixel.bottom(), systemui_visibility,
             DisplayTransform(display.rotation()), display.IsInternal(), &data);
       } else {
-        const gfx::Insets& insets = GetAdjustedInsets(display);
-        zcr_remote_shell_v1_send_workspace(
-            remote_shell_resource_, display_id_hi, display_id_lo, bounds.x(),
-            bounds.y(), bounds.width(), bounds.height(), insets.left(),
-            insets.top(), insets.right(), insets.bottom(),
-            DisplayTransform(display.rotation()),
-            wl_fixed_from_double(device_scale_factor), display.IsInternal());
-
-        if (wl_resource_get_version(remote_shell_resource_) == 19) {
-          zcr_remote_shell_v1_send_display_info(
-              remote_shell_resource_, display_id_hi, display_id_lo,
-              size_in_pixel.width(), size_in_pixel.height(), &data);
-        }
+        NOTREACHED() << "The remote shell resource version being used ("
+                     << wl_resource_get_version(remote_shell_resource_)
+                     << ") is not supported.";
       }
 
       wl_array_release(&data);
@@ -1186,6 +1254,11 @@
   // The remote shell resource associated with observer.
   wl_resource* const remote_shell_resource_;
 
+  // When true, the compositor should use the default_device_scale_factor to
+  // undo the scaling on the client buffers. When false, the compositor should
+  // use the device_scale_factor for the display for this scaling cancellation.
+  bool use_default_scale_cancellation_ = true;
+
   bool needs_send_display_metrics_ = true;
 
   int layout_mode_ = ZCR_REMOTE_SHELL_V1_LAYOUT_MODE_WINDOWED;
@@ -1370,10 +1443,43 @@
                     std::move(toast_surface));
 }
 
+void remote_shell_get_remote_output(wl_client* client,
+                                    wl_resource* resource,
+                                    uint32_t id,
+                                    wl_resource* output_resource) {
+  WaylandDisplayObserver* display_observer =
+      GetUserDataAs<WaylandDisplayObserver>(output_resource);
+
+  wl_resource* remote_output_resource =
+      wl_resource_create(client, &zcr_remote_output_v1_interface,
+                         wl_resource_get_version(resource), id);
+
+  auto remote_output =
+      std::make_unique<WaylandRemoteOutput>(remote_output_resource);
+  display_observer->AddScaleObserver(remote_output.get());
+
+  SetImplementation(remote_output_resource, &remote_output_implementation,
+                    std::move(remote_output));
+}
+
+void remote_shell_set_use_default_scale_cancellation(
+    wl_client*,
+    wl_resource* resource,
+    int32_t use_default_scale_cancellation) {
+  if (wl_resource_get_version(resource) < 29)
+    return;
+  GetUserDataAs<WaylandRemoteShell>(resource)->SetUseDefaultScaleCancellation(
+      use_default_scale_cancellation != 0);
+}
+
 const struct zcr_remote_shell_v1_interface remote_shell_implementation = {
-    remote_shell_destroy, remote_shell_get_remote_surface,
+    remote_shell_destroy,
+    remote_shell_get_remote_surface,
     remote_shell_get_notification_surface,
-    remote_shell_get_input_method_surface, remote_shell_get_toast_surface};
+    remote_shell_get_input_method_surface,
+    remote_shell_get_toast_surface,
+    remote_shell_get_remote_output,
+    remote_shell_set_use_default_scale_cancellation};
 
 }  // namespace
 
@@ -1390,32 +1496,31 @@
                         static_cast<Display*>(data), resource));
 }
 
-gfx::Insets GetWorkAreaInsetsInClientPixel(
-    const display::Display& display,
-    float default_dsf,
-    const gfx::Size& size_in_client_pixel,
-    const gfx::Rect& work_area_in_dp) {
+gfx::Insets GetWorkAreaInsetsInPixel(const display::Display& display,
+                                     float device_scale_factor,
+                                     const gfx::Size& size_in_pixel,
+                                     const gfx::Rect& work_area_in_dp) {
   gfx::Rect local_work_area_in_dp = work_area_in_dp;
   local_work_area_in_dp.Offset(-display.bounds().x(), -display.bounds().y());
-  gfx::Rect work_area_in_client_pixel = ScaleBoundsToPixelSnappedToParent(
-      size_in_client_pixel, display.bounds().size(), default_dsf,
+  gfx::Rect work_area_in_pixel = ScaleBoundsToPixelSnappedToParent(
+      size_in_pixel, display.bounds().size(), device_scale_factor,
       local_work_area_in_dp);
-  gfx::Insets insets_in_client_pixel =
-      gfx::Rect(size_in_client_pixel).InsetsFrom(work_area_in_client_pixel);
+  gfx::Insets insets_in_pixel =
+      gfx::Rect(size_in_pixel).InsetsFrom(work_area_in_pixel);
 
   // TODO(oshima): I think this is more conservative than necessary. The correct
   // way is to use enclosed rect when converting the work area from dp to
   // client pixel, but that led to weird buffer size in overlay detection.
   // (crbug.com/920650). Investigate if we can fix it and use enclosed rect.
   return gfx::Insets(
-      base::Round(base::Ceil(insets_in_client_pixel.top() / default_dsf) *
-                  default_dsf),
-      base::Round(base::Ceil(insets_in_client_pixel.left() / default_dsf) *
-                  default_dsf),
-      base::Round(base::Ceil(insets_in_client_pixel.bottom() / default_dsf) *
-                  default_dsf),
-      base::Round(base::Ceil(insets_in_client_pixel.right() / default_dsf) *
-                  default_dsf));
+      base::Round(base::Ceil(insets_in_pixel.top() / device_scale_factor) *
+                  device_scale_factor),
+      base::Round(base::Ceil(insets_in_pixel.left() / device_scale_factor) *
+                  device_scale_factor),
+      base::Round(base::Ceil(insets_in_pixel.bottom() / device_scale_factor) *
+                  device_scale_factor),
+      base::Round(base::Ceil(insets_in_pixel.right() / device_scale_factor) *
+                  device_scale_factor));
 }
 
 gfx::Rect GetStableWorkArea(const display::Display& display) {
diff --git a/components/exo/wayland/zcr_remote_shell.h b/components/exo/wayland/zcr_remote_shell.h
index 8214a6af..dc8e3ed7 100644
--- a/components/exo/wayland/zcr_remote_shell.h
+++ b/components/exo/wayland/zcr_remote_shell.h
@@ -27,13 +27,12 @@
                        uint32_t version,
                        uint32_t id);
 
-// Create the insets in client's pixel coordinates in such way that
+// Create the insets in pixel coordinates in such way that
 // work area will be within the chrome's work area.
-gfx::Insets GetWorkAreaInsetsInClientPixel(
-    const display::Display& display,
-    float default_dsf,
-    const gfx::Size& size_in_client_pixel,
-    const gfx::Rect& work_area_in_dp);
+gfx::Insets GetWorkAreaInsetsInPixel(const display::Display& display,
+                                     float default_dsf,
+                                     const gfx::Size& size_in_client_pixel,
+                                     const gfx::Rect& work_area_in_dp);
 
 // Returns a work area where the shelf is considered visible.
 gfx::Rect GetStableWorkArea(const display::Display& display);
diff --git a/components/exo/wayland/zcr_remote_shell_unittest.cc b/components/exo/wayland/zcr_remote_shell_unittest.cc
index c780d88..493e910 100644
--- a/components/exo/wayland/zcr_remote_shell_unittest.cc
+++ b/components/exo/wayland/zcr_remote_shell_unittest.cc
@@ -14,23 +14,23 @@
 
 using ZcrRemoteShellTest = test::ExoTestBase;
 
-TEST_F(ZcrRemoteShellTest, GetWorkAreaInsetsInClientPixel) {
+TEST_F(ZcrRemoteShellTest, GetWorkAreaInsetsInPixel) {
   UpdateDisplay("3000x2000*2.25,1920x1080");
 
   auto display = display::Screen::GetScreen()->GetPrimaryDisplay();
   const float device_scale_factor = display.device_scale_factor();
   EXPECT_EQ(2.25f, device_scale_factor);
-  gfx::Insets insets = wayland::GetWorkAreaInsetsInClientPixel(
+  gfx::Insets insets = wayland::GetWorkAreaInsetsInPixel(
       display, device_scale_factor, display.GetSizeInPixel(),
       display.work_area());
   EXPECT_EQ(gfx::Insets(0, 0, 110, 0).ToString(), insets.ToString());
 
   auto secondary_display = GetSecondaryDisplay();
   gfx::Size secondary_size(secondary_display.size());
-  gfx::Size secondary_size_in_client_pixel =
+  gfx::Size secondary_size_in_pixel =
       gfx::ScaleToRoundedSize(secondary_size, device_scale_factor);
-  gfx::Insets secondary_insets = wayland::GetWorkAreaInsetsInClientPixel(
-      secondary_display, device_scale_factor, secondary_size_in_client_pixel,
+  gfx::Insets secondary_insets = wayland::GetWorkAreaInsetsInPixel(
+      secondary_display, device_scale_factor, secondary_size_in_pixel,
       secondary_display.work_area());
   EXPECT_EQ(gfx::Insets(0, 0, 108, 0).ToString(), secondary_insets.ToString());
 
@@ -39,12 +39,12 @@
   widget->SetFullscreen(true);
   display = display::Screen::GetScreen()->GetPrimaryDisplay();
   ASSERT_EQ(display.bounds(), display.work_area());
-  gfx::Insets stable_insets = wayland::GetWorkAreaInsetsInClientPixel(
+  gfx::Insets stable_insets = wayland::GetWorkAreaInsetsInPixel(
       display, device_scale_factor, display.GetSizeInPixel(),
       wayland::GetStableWorkArea(display));
   EXPECT_EQ(gfx::Insets(0, 0, 110, 0).ToString(), stable_insets.ToString());
-  gfx::Insets secondary_stable_insets = wayland::GetWorkAreaInsetsInClientPixel(
-      secondary_display, device_scale_factor, secondary_size_in_client_pixel,
+  gfx::Insets secondary_stable_insets = wayland::GetWorkAreaInsetsInPixel(
+      secondary_display, device_scale_factor, secondary_size_in_pixel,
       wayland::GetStableWorkArea(secondary_display));
   EXPECT_EQ(gfx::Insets(0, 0, 108, 0).ToString(),
             secondary_stable_insets.ToString());
diff --git a/components/external_intents/README.md b/components/external_intents/README.md
index d1ab175..8b087194 100644
--- a/components/external_intents/README.md
+++ b/components/external_intents/README.md
@@ -1 +1,132 @@
-Holds code related to the launching of external intents on Android.
+This directory holds the core logic that Chrome uses to launch external intents
+on Android. Intent launching is surprisingly subtle, with implications on
+privacy, security, and platform integration. This document goes through the
+following aspects:
+- Basic functionality and execution flow
+- Embedding of the component
+- Differences between Chrome and WebLayer intent launching
+- Opportunities for code health improvements in the component
+
+# Basic functionality
+
+Below we give the basic functionality, using Chrome's exercising of this
+component for illustration.
+
+## InterceptNavigationDelegateImpl
+
+The entrypoint to the component is InterceptNavigationDelegateImpl.java, which
+layers on top of //components/navigation_interception's support for
+NavigationThrottles that delegate to Java for their core logic.  Within the
+context of Chrome, InterceptNavigationDelegateImpl is a per-Tab (or Tab-like,
+e.g. OverlayPanel) object that intercepts every navigation made in the given Tab
+and determines whether the navigation should result in an external intent being
+launched. The key method is
+InterceptNavigationDelegateImpl#shouldIgnoreNavigation(). This method sets up
+state related to the current navigation and then invokes
+ExternalNavigationHandler to do the heavy lifting of determining whether the
+navigation should result in an external intent being launched. If so,
+InterceptNavigationDelegateImpl does cleanup in the given Tab, including
+restoring the navigation state to what it was before the navigation chain that
+resulted in this intent being launched and potentially closing the Tab itself if
+opening the Tab led to the intent launch.
+
+## ExternalNavigationHandler
+
+ExternalNavigationHandler is the core of the component. It handles all of the
+intent launching semantics that Chrome has accumulated over 10+ years. This
+includes the following:
+
+- Disallowing launching of external intents for security reasons, e.g. when the
+  app is in the background or non-user-generated navigations within subframes
+- Ensuring that user-typed navigations can result in intents being launched
+  only after a server-side redirect
+- Ensuring that navigations resulting from a link click can result in intents
+  being launched only with an associated user gesture
+- Avoiding launching intents on back/forward navigations
+- Warning the user before an intent is launched when the user is in incognito
+  mode
+- Detection of schemes for which intents should never be launched (e.g.,
+  Chrome-internal schemes)
+- Navigating to fallback URLs within the browser as specified by the given URL
+- Keep same-host navigations within the browser
+- Directing the user to the Play Store to install a given app if not present on
+  the device
+- Integrating with platform and Google features such as SMS, WebAPKs, Android
+  Instant Apps and Google Authenticator
+- The actual launching of external intents.
+
+These are just a few of the highlights, as ExternalNavigationHandler.java is a
+large and complex class. The key external entrypoint is 
+ExternalNavigationHandler#shouldOverrideUrlLoading(), and the method that
+actually holds the core logic for when and how external intents should be
+launched is ExternalNavigationHandler#shouldOverrideUrlLoadingInternal().
+
+## RedirectHandler
+
+This class tracks state across navigations in order to aid
+InterceptNavigationDelegateImpl and ExternalNavigationHandler both in making
+decisions on whether to launch an intent for a given navigation and in properly
+handling the state within a Tab in the event that an intent is launched. Most
+notably, it provides information about the redirect chain (if any) that a given
+navigation is part of and whether the set of apps that can handle an intent
+changes while processing the redirect chain. ExternalNavigationHandlerImpl uses
+this information as part of its determination process (e.g., for determining
+whether an intent can be launched from a user-typed navigation).
+InterceptNavigationDelegateImpl also uses this information to determine how to
+restore the navigation state in its Tab after an intent being launched.
+
+## ExternalNavigationDelegate and InterceptNavigationDelegateClient
+
+These interfaces allow embedders to customize the behavior of the component (see
+the next section for details). Note that they should *not* be used to customize
+the behavior of the components for tests; if that is necessary (e.g., to stub
+out a production method), instead make the method protected and
+@VisibleForTesting and override it as suitable in a test subclass.
+
+# Embedding the Component
+
+To embed the component, it's necessary to install
+InterceptNavigationDelegateImpl for each "tab" of the embedder (where a tab is
+the embedder-level object that holds a WebContents). For an example of a
+relatively straightforward embedding, look at //weblayer's creation of
+InterceptNavigationDelegateImpl.
+
+There are two interfaces that the embedder must implement in order to embed the
+component: InterceptNavigationDelegateClient and ExternalNavigationDelegate.
+Again, //weblayer's implementation of these interfaces provides a good starting
+point to follow. //chrome's implementations are significantly more complex for
+several reasons: handling of differences between Chrome browser and Chrome
+Custom Tabs, integration with handling of incoming intents, an extended set
+of use cases, ....
+
+# Differences between Chrome and WebLayer Embedding
+
+In this section we highlight differences between the Chrome and WebLayer
+embeddings of this component, all of which are encapsulated in the
+implementations of the above-mentioned interfaces:
+
+- Chrome has significant logic for handling interactions with handling of
+  incoming intents (i.e., Chrome itself having been started via an intent).
+  WebLayer's production use cases do not support being launched via intents,
+  which simplifies WebLayer's implementations of
+  InterceptNavigationDelegateClient and ExternalNavigationDelegate.
+- WebLayer does not implement all of the integrations that Chrome does,
+  notably with Android Instant Apps and Google Authenticator.
+- WebLayer and Chrome Custom Tabs both have the notion of an
+  "externally-initiated navigation": WebLayer via its public API surface, and
+  CCT via an intent. However, as these mechanisms are different, the two
+  embedders have different means of achieving similar semantics for these
+  navigations: https://bugs.chromium.org/p/chromium/issues/detail?id=1087434.
+- Chrome and WebLayer have different mechanisms for getting the last user
+  interaction time, as documented here:
+  https://source.chromium.org/chromium/chromium/src/+/master:weblayer/browser/java/org/chromium/weblayer_private/InterceptNavigationDelegateClientImpl.java;l=71?q=InterceptNavigationDelegateClientImpl&ss=chromium&originalUrl=https:%2F%2Fcs.chromium.org%2F
+
+There are almost certainly further smaller differences, but those are the major
+highlights.
+
+# Opportunities for Code Health Improvements
+- For historical reasons, there is overlap between the
+  InterceptNavigationDelegateClient and ExternalNavigationDelegate interfaces.
+  It is likely that the collective API surface could be thinned.
+- It is also potentially even possible that the two interfaces could ultimately
+  be merged into one.
diff --git a/components/external_intents/android/BUILD.gn b/components/external_intents/android/BUILD.gn
index 14acad8..306292b 100644
--- a/components/external_intents/android/BUILD.gn
+++ b/components/external_intents/android/BUILD.gn
@@ -24,6 +24,7 @@
     "//base:jni_java",
     "//components/embedder_support/android:util_java",
     "//components/navigation_interception/android:navigation_interception_java",
+    "//components/webapk/android/libs/client:java",
     "//content/public/android:content_java",
     "//services/network/public/mojom:mojom_java",
     "//third_party/android_deps:androidx_annotation_annotation_java",
diff --git a/components/external_intents/android/DEPS b/components/external_intents/android/DEPS
index d1fbf81..651e42b 100644
--- a/components/external_intents/android/DEPS
+++ b/components/external_intents/android/DEPS
@@ -1,6 +1,7 @@
 include_rules = [
   "+components/embedder_support/android",
   "+components/navigation_interception",
+  "+components/webapk/android/libs/client",
   "+content/public/browser",
 
   "-content/public/android/java",
diff --git a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationDelegate.java b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationDelegate.java
index 5e24dfd9..77a97b23 100644
--- a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationDelegate.java
+++ b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationDelegate.java
@@ -222,12 +222,6 @@
     boolean isIntentToAutofillAssistant(Intent intent);
 
     /**
-     * @param packageName The package to check.
-     * @return Whether the package is a valid WebAPK package.
-     */
-    boolean isValidWebApk(String packageName);
-
-    /**
      * Gives the embedder a chance to handle the intent via the autofill assistant.
      */
     boolean handleWithAutofillAssistant(ExternalNavigationParams params, Intent targetIntent,
diff --git a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
index da96ce6..daad9db 100644
--- a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
+++ b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
@@ -46,6 +46,8 @@
 import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.components.embedder_support.util.UrlUtilities;
 import org.chromium.components.embedder_support.util.UrlUtilitiesJni;
+import org.chromium.components.webapk.lib.client.ChromeWebApkHostSignature;
+import org.chromium.components.webapk.lib.client.WebApkValidator;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.NavigationController;
 import org.chromium.content_public.browser.NavigationEntry;
@@ -1058,6 +1060,19 @@
     }
 
     /**
+     * @param packageName The package to check.
+     * @return Whether the package is a valid WebAPK package.
+     */
+    @VisibleForTesting
+    protected boolean isValidWebApk(String packageName) {
+        // Ensure that WebApkValidator is initialized (note: this method is a no-op after the first
+        // time that it is invoked).
+        WebApkValidator.init(
+                ChromeWebApkHostSignature.EXPECTED_SIGNATURE, ChromeWebApkHostSignature.PUBLIC_KEY);
+        return WebApkValidator.isValidWebApk(ContextUtils.getApplicationContext(), packageName);
+    }
+
+    /**
      * Returns whether the activity belongs to a WebAPK and the URL is within the scope of the
      * WebAPK. The WebAPK's main activity is a bouncer that redirects to the WebAPK Activity in
      * Chrome. In order to avoid bouncing indefinitely, we should not override the navigation if we
@@ -1394,7 +1409,7 @@
     private boolean launchWebApkIfSoleIntentHandler(
             List<ResolveInfo> resolvingInfos, Intent targetIntent) {
         ArrayList<String> packages = getSpecializedHandlers(resolvingInfos);
-        if (packages.size() != 1 || !mDelegate.isValidWebApk(packages.get(0))) return false;
+        if (packages.size() != 1 || !isValidWebApk(packages.get(0))) return false;
         Intent webApkIntent = new Intent(targetIntent);
         webApkIntent.setPackage(packages.get(0));
         try {
diff --git a/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java b/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java
index 84f1bad..6bc9ee2 100644
--- a/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java
+++ b/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java
@@ -127,6 +127,7 @@
     private static final String WEBAPK_SCOPE = "https://www.template.com";
     private static final String WEBAPK_PACKAGE_PREFIX = "org.chromium.webapk";
     private static final String WEBAPK_PACKAGE_NAME = WEBAPK_PACKAGE_PREFIX + ".template";
+    private static final String INVALID_WEBAPK_PACKAGE_NAME = WEBAPK_PACKAGE_PREFIX + ".invalid";
 
     private static final String[] SUPERVISOR_START_ACTIONS = {
             "com.google.android.instantapps.START", "com.google.android.instantapps.nmr1.INSTALL",
@@ -864,8 +865,7 @@
         // IMDB app isn't installed.
         mDelegate.setCanResolveActivityForExternalSchemes(false);
 
-        mDelegate.add(new IntentActivity(IMDB_WEBPAGE_FOR_TOM_HANKS, WEBAPK_PACKAGE_NAME)
-                              .withIsWebApk(true));
+        mDelegate.add(new IntentActivity(IMDB_WEBPAGE_FOR_TOM_HANKS, WEBAPK_PACKAGE_NAME));
         checkUrl(INTENT_URL_WITH_FALLBACK_URL)
                 .withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
                 .expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT, START_WEBAPK);
@@ -877,8 +877,7 @@
         // IMDB app isn't installed.
         mDelegate.setCanResolveActivityForExternalSchemes(false);
 
-        mDelegate.add(new IntentActivity(IMDB_WEBPAGE_FOR_TOM_HANKS, WEBAPK_PACKAGE_NAME)
-                              .withIsWebApk(true));
+        mDelegate.add(new IntentActivity(IMDB_WEBPAGE_FOR_TOM_HANKS, WEBAPK_PACKAGE_NAME));
         mDelegate.add(new IntentActivity(IMDB_WEBPAGE_FOR_TOM_HANKS, TEXT_APP_1_PACKAGE_NAME));
         checkUrl(INTENT_URL_WITH_FALLBACK_URL)
                 .withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
@@ -1007,8 +1006,7 @@
         // IMDB app isn't installed.
         mDelegate.setCanResolveActivityForExternalSchemes(false);
 
-        mDelegate.add(new IntentActivity(IMDB_WEBPAGE_FOR_TOM_HANKS, WEBAPK_PACKAGE_NAME)
-                              .withIsWebApk(true));
+        mDelegate.add(new IntentActivity(IMDB_WEBPAGE_FOR_TOM_HANKS, WEBAPK_PACKAGE_NAME));
 
         RedirectHandler redirectHandler = RedirectHandler.create();
         redirectHandler.updateNewUrlLoading(PageTransition.AUTO_SUBFRAME, true, true, 0, 0);
@@ -1025,8 +1023,7 @@
     @Test
     @SmallTest
     public void testFallbackUrl_NoExternalFallbackWithoutGesture() {
-        mDelegate.add(new IntentActivity(IMDB_WEBPAGE_FOR_TOM_HANKS, WEBAPK_PACKAGE_NAME)
-                              .withIsWebApk(true));
+        mDelegate.add(new IntentActivity(IMDB_WEBPAGE_FOR_TOM_HANKS, WEBAPK_PACKAGE_NAME));
 
         RedirectHandler redirectHandler = RedirectHandler.create();
         redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, false, 0, 0);
@@ -1518,7 +1515,7 @@
     @Test
     @SmallTest
     public void testLaunchWebApk_BypassIntentPicker() {
-        mDelegate.add(new IntentActivity(WEBAPK_SCOPE, WEBAPK_PACKAGE_NAME).withIsWebApk(true));
+        mDelegate.add(new IntentActivity(WEBAPK_SCOPE, WEBAPK_PACKAGE_NAME));
 
         checkUrl(WEBAPK_SCOPE)
                 .expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT, START_WEBAPK);
@@ -1532,8 +1529,7 @@
     @SmallTest
     public void testLaunchWebApk_ShowIntentPickerMultipleIntentHandlers() {
         final String scope = "https://www.webapk.with.native.com";
-        mDelegate.add(new IntentActivity(scope, WEBAPK_PACKAGE_PREFIX + ".with.native")
-                              .withIsWebApk(true));
+        mDelegate.add(new IntentActivity(scope, WEBAPK_PACKAGE_PREFIX + ".with.native"));
         mDelegate.add(new IntentActivity(scope, "com.webapk.with.native.android"));
 
         checkUrl(scope).expecting(
@@ -1551,9 +1547,9 @@
         final String scope1WebApkPackageName = WEBAPK_PACKAGE_PREFIX + ".with.native";
         final String scope1NativeAppPackageName = "com.webapk.with.native.android";
         final String scope2 = "https://www.template.com";
-        mDelegate.add(new IntentActivity(scope1, scope1WebApkPackageName).withIsWebApk(true));
+        mDelegate.add(new IntentActivity(scope1, scope1WebApkPackageName));
         mDelegate.add(new IntentActivity(scope1, scope1NativeAppPackageName));
-        mDelegate.add(new IntentActivity(scope2, WEBAPK_PACKAGE_NAME).withIsWebApk(true));
+        mDelegate.add(new IntentActivity(scope2, WEBAPK_PACKAGE_NAME));
         mDelegate.setReferrerWebappPackageName(scope1WebApkPackageName);
 
         checkUrl(scope2).withReferrer(scope1).expecting(
@@ -1568,7 +1564,7 @@
     @Test
     @SmallTest
     public void testLaunchWebApk_ShowIntentPickerInvalidWebApk() {
-        mDelegate.add(new IntentActivity(WEBAPK_SCOPE, WEBAPK_PACKAGE_NAME).withIsWebApk(false));
+        mDelegate.add(new IntentActivity(WEBAPK_SCOPE, INVALID_WEBAPK_PACKAGE_NAME));
         checkUrl(WEBAPK_SCOPE)
                 .expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
                         START_OTHER_ACTIVITY);
@@ -1950,11 +1946,6 @@
             mPackageName = packageName;
         }
 
-        public IntentActivity withIsWebApk(boolean isWebApk) {
-            mIsWebApk = isWebApk;
-            return this;
-        }
-
         public String urlPrefix() {
             return mUrlPrefix;
         }
@@ -1963,10 +1954,6 @@
             return mPackageName;
         }
 
-        public boolean isWebApk() {
-            return mIsWebApk;
-        }
-
         public boolean isSpecialized() {
             // Specialized if URL prefix is more than just a scheme.
             return Pattern.compile("[^:/]+://.+").matcher(mUrlPrefix).matches();
@@ -1989,6 +1976,12 @@
         }
 
         @Override
+        protected boolean isValidWebApk(String packageName) {
+            return packageName.startsWith(WEBAPK_PACKAGE_PREFIX)
+                    && !packageName.equals(INVALID_WEBAPK_PACKAGE_NAME);
+        }
+
+        @Override
         public boolean blockExternalFormRedirectsWithoutGesture() {
             return true;
         }
@@ -2216,16 +2209,6 @@
         }
 
         @Override
-        public boolean isValidWebApk(String packageName) {
-            for (IntentActivity activity : mIntentActivities) {
-                if (activity.packageName().equals(packageName)) {
-                    return activity.isWebApk();
-                }
-            }
-            return false;
-        }
-
-        @Override
         public boolean handleWithAutofillAssistant(ExternalNavigationParams params,
                 Intent targetIntent, String browserFallbackUrl, boolean isGoogleReferrer) {
             return mHandleWithAutofillAssistant;
diff --git a/components/feed/core/proto/BUILD.gn b/components/feed/core/proto/BUILD.gn
index b2b79298..e65d102d 100644
--- a/components/feed/core/proto/BUILD.gn
+++ b/components/feed/core/proto/BUILD.gn
@@ -54,6 +54,7 @@
     "v2/wire/there_and_back_again_data.proto",
     "v2/wire/token.proto",
     "v2/wire/version.proto",
+    "v2/xsurface.proto",
   ]
 }
 
diff --git a/components/feed/core/proto/v2/xsurface.proto b/components/feed/core/proto/v2/xsurface.proto
new file mode 100644
index 0000000..223af8f
--- /dev/null
+++ b/components/feed/core/proto/v2/xsurface.proto
@@ -0,0 +1,15 @@
+// Copyright 2020 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.
+
+// These are protos for driving the XSurface data store.
+
+syntax = "proto2";
+
+package feedxsurface;
+
+option optimize_for = LITE_RUNTIME;
+
+message OfflineBadgeContent {
+  optional bool available_offline = 1;
+}
diff --git a/components/feed/core/v2/BUILD.gn b/components/feed/core/v2/BUILD.gn
index d1561d4e..92326c9 100644
--- a/components/feed/core/v2/BUILD.gn
+++ b/components/feed/core/v2/BUILD.gn
@@ -10,6 +10,7 @@
 
 source_set("feed_core_v2") {
   sources = [
+    "algorithm.h",
     "config.cc",
     "config.h",
     "enums.cc",
@@ -24,6 +25,8 @@
     "feed_stream.h",
     "metrics_reporter.cc",
     "metrics_reporter.h",
+    "offline_page_spy.cc",
+    "offline_page_spy.h",
     "prefs.cc",
     "prefs.h",
     "proto_util.cc",
@@ -69,6 +72,7 @@
     "//components/feed/core:feed_core",
     "//components/feed/core/common:feed_core_common",
     "//components/history/core/browser",
+    "//components/offline_pages/core:core",
     "//components/offline_pages/core/prefetch",
     "//components/offline_pages/task:task",
     "//components/prefs",
@@ -94,6 +98,7 @@
 source_set("core_unit_tests") {
   testonly = true
   sources = [
+    "algorithm_unittest.cc",
     "feed_network_impl_unittest.cc",
     "feed_store_unittest.cc",
     "feed_stream_unittest.cc",
@@ -122,6 +127,8 @@
     "//components/feed/core/common:feed_core_common",
     "//components/history/core/browser",
     "//components/leveldb_proto:test_support",
+    "//components/offline_pages/core:core",
+    "//components/offline_pages/core:test_support",
     "//components/offline_pages/core/prefetch",
     "//components/offline_pages/core/prefetch:test_support",
     "//components/prefs:test_support",
diff --git a/components/feed/core/v2/algorithm.h b/components/feed/core/v2/algorithm.h
new file mode 100644
index 0000000..2deee49
--- /dev/null
+++ b/components/feed/core/v2/algorithm.h
@@ -0,0 +1,47 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_FEED_CORE_V2_ALGORITHM_H_
+#define COMPONENTS_FEED_CORE_V2_ALGORITHM_H_
+
+namespace feed {
+
+// Compares two sorted ranges. Iterates each range, calling |fn()| for all
+// items. |fn()| takes the parameters (left_pointer, right_pointer). A null
+// value is passed in for items not appearing in either the left or right range.
+// For each call to |fn|, at least one parameter will be non-null.
+template <typename ITER_A, typename ITER_B, typename VISITOR>
+void DiffSortedRange(ITER_A left_begin,
+                     ITER_A left_end,
+                     ITER_B right_begin,
+                     ITER_B right_end,
+                     VISITOR fn) {
+  auto left = left_begin;
+  auto right = right_begin;
+  decltype(&*left) kLeftNull = nullptr;
+  decltype(&*right) kRightNull = nullptr;
+  for (;;) {
+    bool left_at_end = left == left_end, right_at_end = right == right_end;
+    if (left_at_end && right_at_end)
+      break;
+
+    if (!left_at_end && (right_at_end || *left < *right)) {
+      fn(&*left, kRightNull);
+      ++left;
+      continue;
+    }
+    if (!right_at_end && (left_at_end || *right < *left)) {
+      fn(kLeftNull, &*right);
+      ++right;
+      continue;
+    }
+
+    fn(&*left, &*right);
+    ++left;
+    ++right;
+  }
+}
+
+}  // namespace feed
+#endif  // COMPONENTS_FEED_CORE_V2_ALGORITHM_H_
diff --git a/components/feed/core/v2/algorithm_unittest.cc b/components/feed/core/v2/algorithm_unittest.cc
new file mode 100644
index 0000000..0fedc70
--- /dev/null
+++ b/components/feed/core/v2/algorithm_unittest.cc
@@ -0,0 +1,71 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/feed/core/v2/algorithm.h"
+
+#include <utility>
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+namespace feed {
+class ResultAggregator {
+ public:
+  explicit ResultAggregator(std::vector<std::pair<int, int>>* output)
+      : results_(output) {}
+
+  void operator()(int* left, int* right) {
+    results_->emplace_back(left ? *left : -1, right ? *right : -1);
+  }
+
+ private:
+  std::vector<std::pair<int, int>>* results_;
+};
+
+TEST(DiffSortedRange, LeftEmpty) {
+  std::vector<int> left;
+  std::vector<int> right = {1, 2, 3};
+  std::vector<std::pair<int, int>> results;
+  DiffSortedRange(left.begin(), left.end(), right.begin(), right.end(),
+                  ResultAggregator(&results));
+
+  EXPECT_EQ((std::vector<std::pair<int, int>>{{-1, 1}, {-1, 2}, {-1, 3}}),
+            results);
+}
+
+TEST(DiffSortedRange, RightEmpty) {
+  std::vector<int> left = {1, 2, 3};
+  std::vector<int> right;
+  std::vector<std::pair<int, int>> results;
+  DiffSortedRange(left.begin(), left.end(), right.begin(), right.end(),
+                  ResultAggregator(&results));
+
+  EXPECT_EQ((std::vector<std::pair<int, int>>{{1, -1}, {2, -1}, {3, -1}}),
+            results);
+}
+
+TEST(DiffSortedRange, EndsWithRight) {
+  std::vector<int> left = {1, 2, 4};
+  std::vector<int> right = {2, 3, 4, 5};
+  std::vector<std::pair<int, int>> results;
+  DiffSortedRange(left.begin(), left.end(), right.begin(), right.end(),
+                  ResultAggregator(&results));
+
+  EXPECT_EQ((std::vector<std::pair<int, int>>{
+                {1, -1}, {2, 2}, {-1, 3}, {4, 4}, {-1, 5}}),
+            results);
+}
+
+TEST(DiffSortedRange, EndsWithLeft) {
+  std::vector<int> left = {2, 4, 5};
+  std::vector<int> right = {1, 3, 4};
+  std::vector<std::pair<int, int>> results;
+  DiffSortedRange(left.begin(), left.end(), right.begin(), right.end(),
+                  ResultAggregator(&results));
+
+  EXPECT_EQ((std::vector<std::pair<int, int>>{
+                {-1, 1}, {2, -1}, {-1, 3}, {4, 4}, {5, -1}}),
+            results);
+}
+
+}  // namespace feed
diff --git a/components/feed/core/v2/feed_stream.cc b/components/feed/core/v2/feed_stream.cc
index d189c090..2491f839 100644
--- a/components/feed/core/v2/feed_stream.cc
+++ b/components/feed/core/v2/feed_stream.cc
@@ -23,6 +23,7 @@
 #include "components/feed/core/v2/feed_network.h"
 #include "components/feed/core/v2/feed_store.h"
 #include "components/feed/core/v2/metrics_reporter.h"
+#include "components/feed/core/v2/offline_page_spy.h"
 #include "components/feed/core/v2/prefs.h"
 #include "components/feed/core/v2/protocol_translator.h"
 #include "components/feed/core/v2/refresh_task_scheduler.h"
@@ -113,6 +114,7 @@
                        FeedNetwork* feed_network,
                        FeedStore* feed_store,
                        offline_pages::PrefetchService* prefetch_service,
+                       offline_pages::OfflinePageModel* offline_page_model,
                        const base::Clock* clock,
                        const base::TickClock* tick_clock,
                        const ChromeInfo& chrome_info)
@@ -133,6 +135,8 @@
   wire_response_translator_ = &default_translator;
 
   surface_updater_ = std::make_unique<SurfaceUpdater>(metrics_reporter_);
+  offline_page_spy_ = std::make_unique<OfflinePageSpy>(surface_updater_.get(),
+                                                       offline_page_model);
 
   if (prefetch_service_) {
     offline_suggestions_provider_ =
@@ -344,6 +348,8 @@
 }
 
 void FeedStream::ForceRefreshForDebugging() {
+  // Avoid request throttling for debug refreshes.
+  feed::prefs::SetThrottlerRequestCounts({}, *profile_prefs_);
   task_queue_.AddTask(
       std::make_unique<offline_pages::ClosureTask>(base::BindOnce(
           &FeedStream::ForceRefreshForDebuggingTask, base::Unretained(this))));
@@ -548,6 +554,7 @@
   model_ = std::move(model);
   model_->SetStoreObserver(this);
   surface_updater_->SetModel(model_.get());
+  offline_page_spy_->SetModel(model_.get());
   ScheduleModelUnloadIfNoSurfacesAttached();
 }
 
@@ -567,6 +574,7 @@
   // the model remains loaded.
   if (!model_)
     return;
+  offline_page_spy_->SetModel(nullptr);
   surface_updater_->SetModel(nullptr);
   model_.reset();
 }
diff --git a/components/feed/core/v2/feed_stream.h b/components/feed/core/v2/feed_stream.h
index b53b87d..ec1b925 100644
--- a/components/feed/core/v2/feed_stream.h
+++ b/components/feed/core/v2/feed_stream.h
@@ -36,6 +36,7 @@
 }  // namespace base
 
 namespace offline_pages {
+class OfflinePageModel;
 class PrefetchService;
 }  // namespace offline_pages
 
@@ -43,6 +44,7 @@
 class FeedNetwork;
 class FeedStore;
 class MetricsReporter;
+class OfflinePageSpy;
 class RefreshTaskScheduler;
 class StreamModel;
 class SurfaceUpdater;
@@ -101,6 +103,7 @@
              FeedNetwork* feed_network,
              FeedStore* feed_store,
              offline_pages::PrefetchService* prefetch_service,
+             offline_pages::OfflinePageModel* offline_page_model,
              const base::Clock* clock,
              const base::TickClock* tick_clock,
              const ChromeInfo& chrome_info);
@@ -288,6 +291,7 @@
   bool model_loading_in_progress_ = false;
   std::unique_ptr<SurfaceUpdater> surface_updater_;
   std::unique_ptr<OfflineSuggestionsProvider> offline_suggestions_provider_;
+  std::unique_ptr<OfflinePageSpy> offline_page_spy_;
   // The stream model. Null if not yet loaded.
   // Internally, this should only be changed by |LoadModel()| and
   // |UnloadModel()|.
diff --git a/components/feed/core/v2/feed_stream_unittest.cc b/components/feed/core/v2/feed_stream_unittest.cc
index 5fcac6d..8194799 100644
--- a/components/feed/core/v2/feed_stream_unittest.cc
+++ b/components/feed/core/v2/feed_stream_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <map>
 #include <memory>
+#include <set>
 #include <sstream>
 #include <string>
 #include <utility>
@@ -29,6 +30,7 @@
 #include "components/feed/core/proto/v2/wire/action_request.pb.h"
 #include "components/feed/core/proto/v2/wire/request.pb.h"
 #include "components/feed/core/proto/v2/wire/there_and_back_again_data.pb.h"
+#include "components/feed/core/proto/v2/xsurface.pb.h"
 #include "components/feed/core/shared_prefs/pref_names.h"
 #include "components/feed/core/v2/config.h"
 #include "components/feed/core/v2/feed_network.h"
@@ -42,7 +44,10 @@
 #include "components/feed/core/v2/test/proto_printer.h"
 #include "components/feed/core/v2/test/stream_builder.h"
 #include "components/leveldb_proto/public/proto_database_provider.h"
+#include "components/offline_pages/core/client_namespace_constants.h"
+#include "components/offline_pages/core/page_criteria.h"
 #include "components/offline_pages/core/prefetch/stub_prefetch_service.h"
+#include "components/offline_pages/core/stub_offline_page_model.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/testing_pref_service.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -114,6 +119,14 @@
   return std::move(*cr.GetResult());
 }
 
+std::string SerializedOfflineBadgeContent() {
+  feedxsurface::OfflineBadgeContent testbadge;
+  std::string badge_serialized;
+  testbadge.set_available_offline(true);
+  testbadge.SerializeToString(&badge_serialized);
+  return badge_serialized;
+}
+
 // This is EXPECT_EQ, but also dumps the string values for ease of reading.
 #define EXPECT_STRINGS_EQUAL(WANT, GOT)                                       \
   {                                                                           \
@@ -160,6 +173,13 @@
 
     described_updates_.push_back(CurrentState());
   }
+  void ReplaceDataStoreEntry(base::StringPiece key,
+                             base::StringPiece data) override {
+    data_store_entries_[key.as_string()] = data.as_string();
+  }
+  void RemoveDataStoreEntry(base::StringPiece key) override {
+    data_store_entries_.erase(key.as_string());
+  }
 
   // Test functions.
 
@@ -177,6 +197,10 @@
     return result;
   }
 
+  std::map<std::string, std::string> GetDataStoreEntries() const {
+    return data_store_entries_;
+  }
+
   // The initial state of the stream, if it was received. This is nullopt if
   // only the loading spinner was seen.
   base::Optional<feedui::StreamUpdate> initial_state;
@@ -227,6 +251,7 @@
   // The stream if it was attached using the constructor.
   FeedStream* stream_ = nullptr;
   std::vector<std::string> described_updates_;
+  std::map<std::string, std::string> data_store_entries_;
 };
 
 class TestFeedNetwork : public FeedNetwork {
@@ -426,6 +451,59 @@
   int new_suggestions_available_call_count_ = 0;
 };
 
+class TestOfflinePageModel : public offline_pages::StubOfflinePageModel {
+ public:
+  // offline_pages::OfflinePageModel
+  void AddObserver(Observer* observer) override {
+    CHECK(observers_.insert(observer).second);
+  }
+  void RemoveObserver(Observer* observer) override {
+    CHECK_EQ(1UL, observers_.erase(observer));
+  }
+  void GetPagesWithCriteria(
+      const offline_pages::PageCriteria& criteria,
+      offline_pages::MultipleOfflinePageItemCallback callback) override {
+    std::vector<offline_pages::OfflinePageItem> result;
+    for (const offline_pages::OfflinePageItem& item : items_) {
+      if (MeetsCriteria(criteria, item)) {
+        result.push_back(item);
+      }
+    }
+    base::SequencedTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), result));
+  }
+
+  // Test functions.
+
+  void AddTestPage(const GURL& url) {
+    offline_pages::OfflinePageItem item;
+    item.url = url;
+    item.client_id =
+        offline_pages::ClientId(offline_pages::kSuggestedArticlesNamespace, "");
+    items_.push_back(item);
+  }
+
+  std::vector<offline_pages::OfflinePageItem>& items() { return items_; }
+
+  void CallObserverOfflinePageAdded(
+      const offline_pages::OfflinePageItem& item) {
+    for (Observer* observer : observers_) {
+      observer->OfflinePageAdded(this, item);
+    }
+  }
+
+  void CallObserverOfflinePageDeleted(
+      const offline_pages::OfflinePageItem& item) {
+    for (Observer* observer : observers_) {
+      observer->OfflinePageDeleted(item);
+    }
+  }
+
+ private:
+  std::vector<offline_pages::OfflinePageItem> items_;
+  std::set<Observer*> observers_;
+};
+
 class FeedStreamTest : public testing::Test, public FeedStream::Delegate {
  public:
   void SetUp() override {
@@ -471,7 +549,7 @@
     chrome_info.version = base::Version({99, 1, 9911, 2});
     stream_ = std::make_unique<FeedStream>(
         &refresh_scheduler_, metrics_reporter_.get(), this, &profile_prefs_,
-        &network_, store_.get(), &prefetch_service_,
+        &network_, store_.get(), &prefetch_service_, &offline_page_model_,
         task_environment_.GetMockClock(), task_environment_.GetMockTickClock(),
         chrome_info);
 
@@ -542,6 +620,7 @@
           task_environment_.GetMainThreadTaskRunner()));
   FakeRefreshTaskScheduler refresh_scheduler_;
   TestPrefetchService prefetch_service_;
+  TestOfflinePageModel offline_page_model_;
   std::unique_ptr<FeedStream> stream_;
   bool is_eula_accepted_ = true;
   bool is_offline_ = false;
@@ -1638,5 +1717,109 @@
   EXPECT_EQ("", suggestions[0].favicon_url.possibly_invalid_spec());
 }
 
+TEST_F(FeedStreamTest, OfflineBadgesArePopulatedInitially) {
+  // Add two offline pages. We exclude tab-bound pages, so only the first is
+  // used.
+  offline_page_model_.AddTestPage(GURL("http://content0/"));
+  offline_page_model_.AddTestPage(GURL("http://content1/"));
+  offline_page_model_.items()[1].client_id.name_space =
+      offline_pages::kLastNNamespace;
+  response_translator_.InjectResponse(MakeTypicalInitialModelState());
+  TestSurface surface(stream_.get());
+  WaitForIdleTaskQueue();
+
+  EXPECT_EQ((std::map<std::string, std::string>(
+                {{"app/badge0", SerializedOfflineBadgeContent()}})),
+            surface.GetDataStoreEntries());
+}
+
+TEST_F(FeedStreamTest, OfflineBadgesArePopulatedOnNewOfflineItemAdded) {
+  response_translator_.InjectResponse(MakeTypicalInitialModelState());
+  TestSurface surface(stream_.get());
+  WaitForIdleTaskQueue();
+
+  ASSERT_EQ((std::map<std::string, std::string>({})),
+            surface.GetDataStoreEntries());
+
+  // Add an offline page.
+  offline_page_model_.AddTestPage(GURL("http://content1/"));
+  offline_page_model_.CallObserverOfflinePageAdded(
+      offline_page_model_.items()[0]);
+  task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(1));
+
+  EXPECT_EQ((std::map<std::string, std::string>(
+                {{"app/badge1", SerializedOfflineBadgeContent()}})),
+            surface.GetDataStoreEntries());
+}
+
+TEST_F(FeedStreamTest, OfflineBadgesAreRemovedWhenOfflineItemRemoved) {
+  offline_page_model_.AddTestPage(GURL("http://content0/"));
+  response_translator_.InjectResponse(MakeTypicalInitialModelState());
+  TestSurface surface(stream_.get());
+  WaitForIdleTaskQueue();
+
+  ASSERT_EQ((std::map<std::string, std::string>(
+                {{"app/badge0", SerializedOfflineBadgeContent()}})),
+            surface.GetDataStoreEntries());
+
+  // Remove the offline page.
+  offline_page_model_.CallObserverOfflinePageDeleted(
+      offline_page_model_.items()[0]);
+  task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(1));
+
+  EXPECT_EQ((std::map<std::string, std::string>()),
+            surface.GetDataStoreEntries());
+}
+
+TEST_F(FeedStreamTest, OfflineBadgesAreProvidedToNewSurfaces) {
+  offline_page_model_.AddTestPage(GURL("http://content0/"));
+  response_translator_.InjectResponse(MakeTypicalInitialModelState());
+  TestSurface surface(stream_.get());
+  WaitForIdleTaskQueue();
+
+  TestSurface surface2(stream_.get());
+  WaitForIdleTaskQueue();
+
+  EXPECT_EQ((std::map<std::string, std::string>(
+                {{"app/badge0", SerializedOfflineBadgeContent()}})),
+            surface2.GetDataStoreEntries());
+}
+
+TEST_F(FeedStreamTest, OfflineBadgesAreRemovedWhenModelIsUnloaded) {
+  offline_page_model_.AddTestPage(GURL("http://content0/"));
+  response_translator_.InjectResponse(MakeTypicalInitialModelState());
+  TestSurface surface(stream_.get());
+  WaitForIdleTaskQueue();
+
+  stream_->UnloadModel();
+
+  // Offline badge no longer present.
+  EXPECT_EQ((std::map<std::string, std::string>()),
+            surface.GetDataStoreEntries());
+}
+
+TEST_F(FeedStreamTest, MultipleOfflineBadgesWithSameUrl) {
+  {
+    std::unique_ptr<StreamModelUpdateRequest> state =
+        MakeTypicalInitialModelState();
+    const feedwire::PrefetchMetadata& prefetch_metadata1 =
+        state->content[0].prefetch_metadata(0);
+    feedwire::PrefetchMetadata& prefetch_metadata2 =
+        *state->content[0].add_prefetch_metadata();
+    prefetch_metadata2 = prefetch_metadata1;
+    prefetch_metadata2.set_badge_id("app/badge0b");
+    response_translator_.InjectResponse(std::move(state));
+  }
+  offline_page_model_.AddTestPage(GURL("http://content0/"));
+
+  TestSurface surface(stream_.get());
+  WaitForIdleTaskQueue();
+
+  EXPECT_EQ((std::map<std::string, std::string>(
+                {{"app/badge0", SerializedOfflineBadgeContent()},
+                 {"app/badge0b", SerializedOfflineBadgeContent()}})),
+            surface.GetDataStoreEntries());
+}
+
 }  // namespace
 }  // namespace feed
diff --git a/components/feed/core/v2/offline_page_spy.cc b/components/feed/core/v2/offline_page_spy.cc
new file mode 100644
index 0000000..391bb13f
--- /dev/null
+++ b/components/feed/core/v2/offline_page_spy.cc
@@ -0,0 +1,172 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/feed/core/v2/offline_page_spy.h"
+
+#include <algorithm>
+#include <tuple>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/containers/flat_set.h"
+#include "components/feed/core/v2/algorithm.h"
+#include "components/feed/core/v2/surface_updater.h"
+
+namespace feed {
+
+namespace {
+
+std::vector<OfflinePageSpy::BadgeInfo> GetBadgesInStream(
+    StreamModel* stream_model) {
+  std::vector<OfflinePageSpy::BadgeInfo> badges;
+  for (ContentRevision content_rev : stream_model->GetContentList()) {
+    const feedstore::Content* content = stream_model->FindContent(content_rev);
+    if (!content)
+      continue;
+    for (const feedwire::PrefetchMetadata& prefetch_metadata :
+         content->prefetch_metadata()) {
+      const std::string& badge_id = prefetch_metadata.badge_id();
+      if (badge_id.empty())
+        continue;
+      GURL url(prefetch_metadata.uri());
+      if (url.is_empty() || !url.is_valid())
+        continue;
+
+      badges.emplace_back(url, badge_id);
+    }
+  }
+  return badges;
+}
+
+base::flat_set<GURL> OfflineItemsToUrlSet(
+    const std::vector<offline_pages::OfflinePageItem>& items) {
+  std::vector<GURL> url_list;
+  for (const auto& item : items) {
+    url_list.push_back(item.GetOriginalUrl());
+  }
+  return url_list;
+}
+
+}  // namespace
+
+OfflinePageSpy::BadgeInfo::BadgeInfo() = default;
+OfflinePageSpy::BadgeInfo::BadgeInfo(const GURL& url,
+                                     const std::string& badge_id)
+    : url(url), badge_id(badge_id) {}
+
+bool OfflinePageSpy::BadgeInfo::operator<(const BadgeInfo& rhs) const {
+  return std::tie(url, badge_id) < std::tie(rhs.url, rhs.badge_id);
+}
+
+OfflinePageSpy::OfflinePageSpy(
+    SurfaceUpdater* surface_updater,
+    offline_pages::OfflinePageModel* offline_page_model)
+    : surface_updater_(surface_updater),
+      offline_page_model_(offline_page_model) {
+  offline_page_model_->AddObserver(this);
+}
+
+OfflinePageSpy::~OfflinePageSpy() {
+  offline_page_model_->RemoveObserver(this);
+}
+
+void OfflinePageSpy::SetModel(StreamModel* stream_model) {
+  if (stream_model_) {
+    stream_model_->RemoveObserver(this);
+    stream_model_ = nullptr;
+  }
+  if (stream_model) {
+    stream_model_ = stream_model;
+    stream_model_->AddObserver(this);
+    UpdateWatchedPages();
+  } else {
+    for (const BadgeInfo& badge : badges_) {
+      if (badge.available_offline) {
+        surface_updater_->SetOfflinePageAvailability(badge.badge_id, false);
+      }
+    }
+    badges_.clear();
+  }
+}
+
+void OfflinePageSpy::SetAvailability(const base::flat_set<GURL>& urls,
+                                     bool available) {
+  for (BadgeInfo& badge : badges_) {
+    if (badge.available_offline != available && urls.contains(badge.url)) {
+      badge.available_offline = available;
+      surface_updater_->SetOfflinePageAvailability(badge.badge_id, available);
+    }
+  }
+}
+
+void OfflinePageSpy::GetPagesDone(
+    const std::vector<offline_pages::OfflinePageItem>& items) {
+  SetAvailability(OfflineItemsToUrlSet(items), true);
+}
+
+void OfflinePageSpy::OfflinePageAdded(
+    offline_pages::OfflinePageModel* model,
+    const offline_pages::OfflinePageItem& added_page) {
+  SetAvailability({added_page.GetOriginalUrl()}, true);
+}
+
+void OfflinePageSpy::OfflinePageDeleted(
+    const offline_pages::OfflinePageItem& deleted_page) {
+  SetAvailability({deleted_page.GetOriginalUrl()}, false);
+}
+
+void OfflinePageSpy::OnUiUpdate(const StreamModel::UiUpdate& update) {
+  DCHECK(stream_model_);
+  if (update.content_list_changed)
+    UpdateWatchedPages();
+}
+
+void OfflinePageSpy::UpdateWatchedPages() {
+  std::vector<BadgeInfo> badges = GetBadgesInStream(stream_model_);
+
+  // Both lists need to be sorted. |badges_| should already be sorted.
+  std::sort(badges.begin(), badges.end());
+  DCHECK(std::is_sorted(badges_.begin(), badges_.end()));
+
+  // Compare new and old lists. We need to inform SurfaceUpdater of removed
+  // badges, and collect new URLs.
+  std::vector<GURL> new_urls;
+  auto differ = [&](BadgeInfo* new_badge, BadgeInfo* old_badge) {
+    if (!old_badge) {  // Added a page.
+      new_urls.push_back(new_badge->url);
+      return;
+    }
+    if (!new_badge) {  // Removed a page.
+      surface_updater_->SetOfflinePageAvailability(old_badge->badge_id, false);
+      return;
+    }
+    // Page remains, update |badges|.
+    new_badge->available_offline = old_badge->available_offline;
+  };
+
+  DiffSortedRange(badges.begin(), badges.end(), badges_.begin(), badges_.end(),
+                  differ);
+
+  badges_ = std::move(badges);
+  RequestOfflinePageStatus(std::move(new_urls));
+}
+
+void OfflinePageSpy::RequestOfflinePageStatus(std::vector<GURL> new_urls) {
+  if (new_urls.empty())
+    return;
+
+  offline_pages::PageCriteria criteria;
+  criteria.exclude_tab_bound_pages = true;
+  criteria.additional_criteria = base::BindRepeating(
+      [](const base::flat_set<GURL>& url_set,
+         const offline_pages::OfflinePageItem& item) {
+        return url_set.count(item.GetOriginalUrl()) > 0;
+      },
+      std::move(new_urls));
+
+  offline_page_model_->GetPagesWithCriteria(
+      criteria, base::BindOnce(&OfflinePageSpy::GetPagesDone, GetWeakPtr()));
+}
+
+}  // namespace feed
diff --git a/components/feed/core/v2/offline_page_spy.h b/components/feed/core/v2/offline_page_spy.h
new file mode 100644
index 0000000..ae5539b
--- /dev/null
+++ b/components/feed/core/v2/offline_page_spy.h
@@ -0,0 +1,81 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_FEED_CORE_V2_OFFLINE_PAGE_SPY_H_
+#define COMPONENTS_FEED_CORE_V2_OFFLINE_PAGE_SPY_H_
+
+#include <string>
+#include <vector>
+
+#include "base/containers/flat_set.h"
+#include "base/memory/weak_ptr.h"
+#include "components/feed/core/v2/stream_model.h"
+#include "components/offline_pages/core/offline_page_model.h"
+#include "url/gurl.h"
+
+namespace feed {
+class SurfaceUpdater;
+
+// Watches for availability of offline pages for pages linked on the Feed.
+// Offline page availability is sent to
+// |SurfaceUpdater::SetOfflinePageAvailability()|.
+class OfflinePageSpy : public offline_pages::OfflinePageModel::Observer,
+                       public feed::StreamModel::Observer {
+ public:
+  struct BadgeInfo {
+    BadgeInfo();
+    BadgeInfo(const GURL& url, const std::string& badge_id);
+    // For sorting by (url, badge_id).
+    bool operator<(const BadgeInfo& rhs) const;
+    GURL url;
+    std::string badge_id;
+    // Initially, this is false until we receive information that indicates
+    // otherwise.
+    bool available_offline = false;
+  };
+
+  OfflinePageSpy(SurfaceUpdater* surface_updater,
+                 offline_pages::OfflinePageModel* offline_page_model);
+  ~OfflinePageSpy() override;
+  OfflinePageSpy(const OfflinePageSpy&) = delete;
+  OfflinePageSpy& operator=(const OfflinePageSpy&) = delete;
+
+  void SetModel(StreamModel* stream_model);
+
+ private:
+  // offline_pages::OfflinePageModel::Observer
+  void OfflinePageAdded(
+      offline_pages::OfflinePageModel* model,
+      const offline_pages::OfflinePageItem& added_page) override;
+  void OfflinePageDeleted(
+      const offline_pages::OfflinePageItem& deleted_page) override;
+  void OfflinePageModelLoaded(offline_pages::OfflinePageModel* model) override {
+  }
+
+  // StreamModel::Observer
+  void OnUiUpdate(const StreamModel::UiUpdate& update) override;
+
+  void SetAvailability(const base::flat_set<GURL>& urls, bool available);
+  void GetPagesDone(const std::vector<offline_pages::OfflinePageItem>& items);
+  void UpdateWatchedPages();
+  void RequestOfflinePageStatus(std::vector<GURL> new_urls);
+
+  base::WeakPtr<OfflinePageSpy> GetWeakPtr() {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
+  SurfaceUpdater* surface_updater_;                      // unowned
+  offline_pages::OfflinePageModel* offline_page_model_;  // unowned
+  // Null when the model is not loaded.
+  StreamModel* stream_model_ = nullptr;  // unowned
+
+  // A list of offline badges for all content in the stream.
+  std::vector<BadgeInfo> badges_;
+
+  base::WeakPtrFactory<OfflinePageSpy> weak_ptr_factory_{this};
+};
+
+}  // namespace feed
+
+#endif  // COMPONENTS_FEED_CORE_V2_OFFLINE_PAGE_SPY_H_
diff --git a/components/feed/core/v2/public/feed_service.cc b/components/feed/core/v2/public/feed_service.cc
index 8591103..4771a10e 100644
--- a/components/feed/core/v2/public/feed_service.cc
+++ b/components/feed/core/v2/public/feed_service.cc
@@ -162,6 +162,7 @@
     signin::IdentityManager* identity_manager,
     history::HistoryService* history_service,
     offline_pages::PrefetchService* prefetch_service,
+    offline_pages::OfflinePageModel* offline_page_model,
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
     scoped_refptr<base::SequencedTaskRunner> background_task_runner,
     const std::string& api_key,
@@ -182,7 +183,7 @@
   stream_ = std::make_unique<FeedStream>(
       refresh_task_scheduler_.get(), metrics_reporter_.get(),
       stream_delegate_.get(), profile_prefs, feed_network_.get(), store_.get(),
-      prefetch_service, base::DefaultClock::GetInstance(),
+      prefetch_service, offline_page_model, base::DefaultClock::GetInstance(),
       base::DefaultTickClock::GetInstance(), chrome_info);
 
   history_observer_ = std::make_unique<HistoryObserverImpl>(
diff --git a/components/feed/core/v2/public/feed_service.h b/components/feed/core/v2/public/feed_service.h
index dd97a52..7196b935 100644
--- a/components/feed/core/v2/public/feed_service.h
+++ b/components/feed/core/v2/public/feed_service.h
@@ -35,6 +35,7 @@
 class SharedURLLoaderFactory;
 }  // namespace network
 namespace offline_pages {
+class OfflinePageModel;
 class PrefetchService;
 }  // namespace offline_pages
 namespace signin {
@@ -78,6 +79,7 @@
       signin::IdentityManager* identity_manager,
       history::HistoryService* history_service,
       offline_pages::PrefetchService* prefetch_service,
+      offline_pages::OfflinePageModel* offline_page_model,
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
       scoped_refptr<base::SequencedTaskRunner> background_task_runner,
       const std::string& api_key,
diff --git a/components/feed/core/v2/public/feed_stream_api.h b/components/feed/core/v2/public/feed_stream_api.h
index 9599b82e..e9fc38c2 100644
--- a/components/feed/core/v2/public/feed_stream_api.h
+++ b/components/feed/core/v2/public/feed_stream_api.h
@@ -10,6 +10,7 @@
 
 #include "base/callback_forward.h"
 #include "base/observer_list_types.h"
+#include "base/strings/string_piece_forward.h"
 #include "components/feed/core/v2/public/types.h"
 
 namespace feedui {
@@ -36,6 +37,10 @@
     // after the Chrome process is closed.
     SurfaceId GetSurfaceId() const;
 
+    virtual void ReplaceDataStoreEntry(base::StringPiece key,
+                                       base::StringPiece data) = 0;
+    virtual void RemoveDataStoreEntry(base::StringPiece key) = 0;
+
    private:
     SurfaceId surface_id_;
   };
diff --git a/components/feed/core/v2/stream_model.cc b/components/feed/core/v2/stream_model.cc
index f0645b5..fbdef12 100644
--- a/components/feed/core/v2/stream_model.cc
+++ b/components/feed/core/v2/stream_model.cc
@@ -48,10 +48,12 @@
   store_observer_ = store_observer;
 }
 
-void StreamModel::SetObserver(Observer* observer) {
-  DCHECK(!observer || !observer_)
-      << "Attempting to set the observer multiple times";
-  observer_ = observer;
+void StreamModel::AddObserver(Observer* observer) {
+  observers_.AddObserver(observer);
+}
+
+void StreamModel::RemoveObserver(Observer* observer) {
+  observers_.RemoveObserver(observer);
 }
 
 const feedstore::Content* StreamModel::FindContent(
@@ -222,8 +224,8 @@
     shared_state.updated = false;
   }
 
-  if (observer_)
-    observer_->OnUiUpdate(update);
+  for (Observer& observer : observers_)
+    observer.OnUiUpdate(update);
 }
 
 stream_model::FeatureTree* StreamModel::GetFinalFeatureTree() {
diff --git a/components/feed/core/v2/stream_model.h b/components/feed/core/v2/stream_model.h
index b116eb0..4a579704 100644
--- a/components/feed/core/v2/stream_model.h
+++ b/components/feed/core/v2/stream_model.h
@@ -11,6 +11,8 @@
 #include <vector>
 
 #include "base/containers/flat_map.h"
+#include "base/observer_list.h"
+#include "base/observer_list_types.h"
 #include "components/feed/core/proto/v2/store.pb.h"
 #include "components/feed/core/proto/v2/wire/content_id.pb.h"
 #include "components/feed/core/v2/proto_util.h"
@@ -62,9 +64,8 @@
     std::unique_ptr<StreamModelUpdateRequest> update_request;
   };
 
-  class Observer {
+  class Observer : public base::CheckedObserver {
    public:
-    virtual ~Observer() = default;
     // Called when the UI model changes.
     virtual void OnUiUpdate(const UiUpdate& update) = 0;
   };
@@ -82,7 +83,8 @@
   StreamModel(const StreamModel& src) = delete;
   StreamModel& operator=(const StreamModel&) = delete;
 
-  void SetObserver(Observer* observer);
+  void AddObserver(Observer* observer);
+  void RemoveObserver(Observer* observer);
   void SetStoreObserver(StoreObserver* store_observer);
 
   // Data access.
@@ -132,7 +134,7 @@
 
   void UpdateFlattenedTree();
 
-  Observer* observer_ = nullptr;  // Unowned.
+  base::ObserverList<Observer> observers_;
   StoreObserver* store_observer_ = nullptr;  // Unowned.
   stream_model::ContentMap content_map_;
   stream_model::FeatureTree base_feature_tree_{&content_map_};
diff --git a/components/feed/core/v2/stream_model_unittest.cc b/components/feed/core/v2/stream_model_unittest.cc
index 6c843db..d220f218 100644
--- a/components/feed/core/v2/stream_model_unittest.cc
+++ b/components/feed/core/v2/stream_model_unittest.cc
@@ -37,7 +37,7 @@
 
 class TestObserver : public StreamModel::Observer {
  public:
-  explicit TestObserver(StreamModel* model) { model->SetObserver(this); }
+  explicit TestObserver(StreamModel* model) { model->AddObserver(this); }
 
   // StreamModel::Observer.
   void OnUiUpdate(const UiUpdate& update) override { update_ = update; }
diff --git a/components/feed/core/v2/surface_updater.cc b/components/feed/core/v2/surface_updater.cc
index ff92bd98..fb35e9c7 100644
--- a/components/feed/core/v2/surface_updater.cc
+++ b/components/feed/core/v2/surface_updater.cc
@@ -5,9 +5,11 @@
 #include "components/feed/core/v2/surface_updater.h"
 
 #include <tuple>
+#include <utility>
 
 #include "base/check.h"
 #include "base/strings/string_number_conversions.h"
+#include "components/feed/core/proto/v2/xsurface.pb.h"
 #include "components/feed/core/v2/feed_stream.h"
 #include "components/feed/core/v2/metrics_reporter.h"
 
@@ -171,11 +173,11 @@
   if (model_ == model)
     return;
   if (model_)
-    model_->SetObserver(nullptr);
+    model_->RemoveObserver(this);
   model_ = model;
   sent_content_.clear();
   if (model_) {
-    model_->SetObserver(this);
+    model_->AddObserver(this);
     loading_initial_ = loading_initial_ && model_->GetContentList().empty();
     loading_more_ = false;
     SendStreamUpdate(model_->GetSharedStateIds());
@@ -200,6 +202,12 @@
 
 void SurfaceUpdater::SurfaceAdded(SurfaceInterface* surface) {
   SendUpdateToSurface(surface, GetUpdateForNewSurface(GetState(), model_));
+
+  for (const auto& datastore_entry : xsurface_datastore_entries_) {
+    surface->ReplaceDataStoreEntry(datastore_entry.first,
+                                   datastore_entry.second);
+  }
+
   surfaces_.AddObserver(surface);
 }
 
@@ -294,4 +302,33 @@
   metrics_reporter_->SurfaceReceivedContent(surface->GetSurfaceId());
 }
 
+void SurfaceUpdater::SetOfflinePageAvailability(const std::string& badge_id,
+                                                bool available_offline) {
+  feedxsurface::OfflineBadgeContent testbadge;
+  if (available_offline) {
+    std::string badge_serialized;
+    testbadge.set_available_offline(available_offline);
+    testbadge.SerializeToString(&badge_serialized);
+    InsertDatastoreEntry(badge_id, badge_serialized);
+  } else {
+    RemoveDatastoreEntry(badge_id);
+  }
+}
+
+void SurfaceUpdater::InsertDatastoreEntry(const std::string& key,
+                                          const std::string& value) {
+  xsurface_datastore_entries_[key] = value;
+  for (SurfaceInterface& surface : surfaces_) {
+    surface.ReplaceDataStoreEntry(key, value);
+  }
+}
+
+void SurfaceUpdater::RemoveDatastoreEntry(const std::string& key) {
+  if (xsurface_datastore_entries_.erase(key) == 1) {
+    for (SurfaceInterface& surface : surfaces_) {
+      surface.RemoveDataStoreEntry(key);
+    }
+  }
+}
+
 }  // namespace feed
diff --git a/components/feed/core/v2/surface_updater.h b/components/feed/core/v2/surface_updater.h
index cfccb86..e70e004 100644
--- a/components/feed/core/v2/surface_updater.h
+++ b/components/feed/core/v2/surface_updater.h
@@ -5,7 +5,9 @@
 #ifndef COMPONENTS_FEED_CORE_V2_SURFACE_UPDATER_H_
 #define COMPONENTS_FEED_CORE_V2_SURFACE_UPDATER_H_
 
+#include <map>
 #include <string>
+#include <vector>
 
 #include "base/containers/flat_set.h"
 #include "base/observer_list.h"
@@ -59,6 +61,9 @@
   // Returns whether or not at least one surface is attached.
   bool HasSurfaceAttached() const;
 
+  void SetOfflinePageAvailability(const std::string& badge_id,
+                                  bool available_offline);
+
   // State that together with |model_| determines what should be sent to a
   // surface. |DrawState| is usually the same for all surfaces, except for the
   // moment when a surface is first attached.
@@ -78,6 +83,8 @@
       const std::vector<std::string>& updated_shared_state_ids);
   void SendUpdateToSurface(SurfaceInterface* surface,
                            const feedui::StreamUpdate& update);
+  void InsertDatastoreEntry(const std::string& key, const std::string& value);
+  void RemoveDatastoreEntry(const std::string& key);
 
   // Members that affect what is sent to surfaces. A value change of these may
   // require sending an update to surfaces.
@@ -92,6 +99,10 @@
   // The set of content that has been sent to all attached surfaces.
   base::flat_set<ContentRevision> sent_content_;
 
+  // XSurface datastore entries that should be sent to all surfaces.
+  // Cached here so that we don't need to recompute for a new surface.
+  std::map<std::string, std::string> xsurface_datastore_entries_;
+
   // Owned by |FeedStream|. Null when the model is not loaded.
   StreamModel* model_ = nullptr;
   // Owned by |FeedStream|.
diff --git a/components/feed/core/v2/test/stream_builder.cc b/components/feed/core/v2/test/stream_builder.cc
index a50d95e..0f27906 100644
--- a/components/feed/core/v2/test/stream_builder.cc
+++ b/components/feed/core/v2/test/stream_builder.cc
@@ -105,7 +105,7 @@
   prefetch_metadata.set_snippet("snippet" + suffix);
   prefetch_metadata.set_image_url("http://image" + suffix);
   prefetch_metadata.set_favicon_url("http://favicon" + suffix);
-
+  prefetch_metadata.set_badge_id("app/badge" + suffix);
   return result;
 }
 
diff --git a/components/management_strings.grdp b/components/management_strings.grdp
index dfcb263..ca93ce1 100644
--- a/components/management_strings.grdp
+++ b/components/management_strings.grdp
@@ -215,6 +215,9 @@
   <message name="IDS_MANAGEMENT_ENTERPRISE_REPORTING_EVENT" desc="Event for the enterprise reporting feature">
     Unsafe event occurs
   </message>
+  <message name="IDS_MANAGEMENT_PAGE_VISITED_EVENT" desc="Event for the real time URL check feature.">
+    Page is visited
+  </message>
   <message name="IDS_MANAGEMENT_FILE_ATTACHED_VISIBLE_DATA" desc="Description of the visible data for the file attachment scanning feature.">
     Files you upload or attach are sent to Google Cloud or third parties for analysis. For example, they might be scanned for sensitive data or malware.
   </message>
@@ -227,4 +230,7 @@
   <message name="IDS_MANAGEMENT_ENTERPRISE_REPORTING_VISIBLE_DATA" desc="Description of the visible data for the Connectors reporting feature">
     When security events are flagged by Chrome Enterprise Connectors, relevant data about the event is sent to your administrator. This can include URLs of pages you visit in Chrome, file names or metadata, and the username that you use to sign in to your device and Chrome.
   </message>
+  <message name="IDS_MANAGEMENT_PAGE_VISITED_VISIBLE_DATA" desc="Description of the visible data for the real time URL check feature.">
+    URLs of pages you visit are sent to Google Cloud or third parties for analysis. For example, they might be scanned to detect unsafe websites.
+  </message>
 </grit-part>
diff --git a/components/management_strings_grdp/IDS_MANAGEMENT_PAGE_VISITED_EVENT.png.sha1 b/components/management_strings_grdp/IDS_MANAGEMENT_PAGE_VISITED_EVENT.png.sha1
new file mode 100644
index 0000000..f6bd91308
--- /dev/null
+++ b/components/management_strings_grdp/IDS_MANAGEMENT_PAGE_VISITED_EVENT.png.sha1
@@ -0,0 +1 @@
+fdc3da98ce35f6204362a1bb0ff0e11b5134d2b5
\ No newline at end of file
diff --git a/components/management_strings_grdp/IDS_MANAGEMENT_PAGE_VISITED_VISIBLE_DATA.png.sha1 b/components/management_strings_grdp/IDS_MANAGEMENT_PAGE_VISITED_VISIBLE_DATA.png.sha1
new file mode 100644
index 0000000..f6bd91308
--- /dev/null
+++ b/components/management_strings_grdp/IDS_MANAGEMENT_PAGE_VISITED_VISIBLE_DATA.png.sha1
@@ -0,0 +1 @@
+fdc3da98ce35f6204362a1bb0ff0e11b5134d2b5
\ No newline at end of file
diff --git a/components/metrics/serialization/serialization_utils_unittest.cc b/components/metrics/serialization/serialization_utils_unittest.cc
index 92a63a2..0c0545d 100644
--- a/components/metrics/serialization/serialization_utils_unittest.cc
+++ b/components/metrics/serialization/serialization_utils_unittest.cc
@@ -20,15 +20,15 @@
 class SerializationUtilsTest : public testing::Test {
  protected:
   SerializationUtilsTest() {
-    bool success = temporary_dir.CreateUniqueTempDir();
+    bool success = temporary_dir_.CreateUniqueTempDir();
     if (success) {
-      base::FilePath dir_path = temporary_dir.GetPath();
-      filename = dir_path.value() + "chromeossampletest";
-      filepath = base::FilePath(filename);
+      base::FilePath dir_path = temporary_dir_.GetPath();
+      filename_ = dir_path.value() + "chromeossampletest";
+      filepath_ = base::FilePath(filename_);
     }
   }
 
-  void SetUp() override { base::DeleteFile(filepath); }
+  void SetUp() override { base::DeleteFile(filepath_); }
 
   void TestSerialization(MetricSample* sample) {
     std::string serialized(sample->ToString());
@@ -39,9 +39,13 @@
     EXPECT_TRUE(sample->IsEqual(*deserialized.get()));
   }
 
-  std::string filename;
-  base::ScopedTempDir temporary_dir;
-  base::FilePath filepath;
+  const std::string& filename() const { return filename_; }
+  const base::FilePath& filepath() const { return filepath_; }
+
+ private:
+  std::string filename_;
+  base::ScopedTempDir temporary_dir_;
+  base::FilePath filepath_;
 };
 
 TEST_F(SerializationUtilsTest, CrashSerializeTest) {
@@ -72,11 +76,13 @@
   std::unique_ptr<MetricSample> sample2 = MetricSample::LinearHistogramSample(
       base::StringPrintf("here%cbhe", '\0'), 1, 3);
 
-  EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*sample1.get(), filename));
-  EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*sample2.get(), filename));
+  EXPECT_FALSE(
+      SerializationUtils::WriteMetricToFile(*sample1.get(), filename()));
+  EXPECT_FALSE(
+      SerializationUtils::WriteMetricToFile(*sample2.get(), filename()));
   int64_t size = 0;
 
-  ASSERT_TRUE(!PathExists(filepath) || base::GetFileSize(filepath, &size));
+  ASSERT_TRUE(!PathExists(filepath()) || base::GetFileSize(filepath(), &size));
 
   EXPECT_EQ(0, size);
 }
@@ -90,9 +96,9 @@
 TEST_F(SerializationUtilsTest, MessageSeparatedByZero) {
   std::unique_ptr<MetricSample> crash = MetricSample::CrashSample("mycrash");
 
-  SerializationUtils::WriteMetricToFile(*crash.get(), filename);
+  SerializationUtils::WriteMetricToFile(*crash.get(), filename());
   int64_t size = 0;
-  ASSERT_TRUE(base::GetFileSize(filepath, &size));
+  ASSERT_TRUE(base::GetFileSize(filepath(), &size));
   // 4 bytes for the size
   // 5 bytes for crash
   // 7 bytes for mycrash
@@ -108,14 +114,14 @@
   std::string name(SerializationUtils::kMessageMaxLength, 'c');
 
   std::unique_ptr<MetricSample> crash = MetricSample::CrashSample(name);
-  EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*crash.get(), filename));
+  EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*crash.get(), filename()));
   int64_t size = 0;
-  ASSERT_TRUE(base::GetFileSize(filepath, &size));
+  ASSERT_TRUE(base::GetFileSize(filepath(), &size));
   EXPECT_EQ(0, size);
 }
 
 TEST_F(SerializationUtilsTest, ReadLongMessageTest) {
-  base::File test_file(filepath,
+  base::File test_file(filepath(),
                        base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_APPEND);
   std::string message(SerializationUtils::kMessageMaxLength + 1, 'c');
 
@@ -126,10 +132,10 @@
   test_file.Close();
 
   std::unique_ptr<MetricSample> crash = MetricSample::CrashSample("test");
-  SerializationUtils::WriteMetricToFile(*crash.get(), filename);
+  SerializationUtils::WriteMetricToFile(*crash.get(), filename());
 
   std::vector<std::unique_ptr<MetricSample>> samples;
-  SerializationUtils::ReadAndTruncateMetricsFromFile(filename, &samples);
+  SerializationUtils::ReadAndTruncateMetricsFromFile(filename(), &samples);
   ASSERT_EQ(size_t(1), samples.size());
   ASSERT_TRUE(samples[0].get() != nullptr);
   EXPECT_TRUE(crash->IsEqual(*samples[0]));
@@ -171,11 +177,11 @@
       0xff,
       0xff,
   };
-  CHECK(base::WriteFile(filepath, reinterpret_cast<const char*>(kInput),
+  CHECK(base::WriteFile(filepath(), reinterpret_cast<const char*>(kInput),
                         sizeof(kInput)));
 
   std::vector<std::unique_ptr<MetricSample>> samples;
-  SerializationUtils::ReadAndTruncateMetricsFromFile(filename, &samples);
+  SerializationUtils::ReadAndTruncateMetricsFromFile(filename(), &samples);
   ASSERT_EQ(0U, samples.size());
 }
 
@@ -190,13 +196,13 @@
   std::unique_ptr<MetricSample> action =
       MetricSample::UserActionSample("myaction");
 
-  SerializationUtils::WriteMetricToFile(*hist.get(), filename);
-  SerializationUtils::WriteMetricToFile(*crash.get(), filename);
-  SerializationUtils::WriteMetricToFile(*lhist.get(), filename);
-  SerializationUtils::WriteMetricToFile(*shist.get(), filename);
-  SerializationUtils::WriteMetricToFile(*action.get(), filename);
+  SerializationUtils::WriteMetricToFile(*hist.get(), filename());
+  SerializationUtils::WriteMetricToFile(*crash.get(), filename());
+  SerializationUtils::WriteMetricToFile(*lhist.get(), filename());
+  SerializationUtils::WriteMetricToFile(*shist.get(), filename());
+  SerializationUtils::WriteMetricToFile(*action.get(), filename());
   std::vector<std::unique_ptr<MetricSample>> vect;
-  SerializationUtils::ReadAndTruncateMetricsFromFile(filename, &vect);
+  SerializationUtils::ReadAndTruncateMetricsFromFile(filename(), &vect);
   ASSERT_EQ(vect.size(), size_t(5));
   for (auto& sample : vect) {
     ASSERT_NE(nullptr, sample.get());
@@ -208,7 +214,7 @@
   EXPECT_TRUE(action->IsEqual(*vect[4]));
 
   int64_t size = 0;
-  ASSERT_TRUE(base::GetFileSize(filepath, &size));
+  ASSERT_TRUE(base::GetFileSize(filepath(), &size));
   ASSERT_EQ(0, size);
 }
 
diff --git a/components/ntp_snippets_strings.grdp b/components/ntp_snippets_strings.grdp
index d755fd1..fdca4e0 100644
--- a/components/ntp_snippets_strings.grdp
+++ b/components/ntp_snippets_strings.grdp
@@ -39,15 +39,6 @@
     Your suggested articles appear here
   </message>
 
-  <!-- When you change this string, please change IDS_NTP_PHYSICAL_WEB_PAGE_SUGGESTIONS_SECTION_EMPTY description accordingly. -->
-  <message name="IDS_NTP_PHYSICAL_WEB_PAGE_SUGGESTIONS_SECTION_HEADER" desc="Header of the physical web section. The physical web section shows a list of physical web pages available near the user. A physical web page is a URL transmitted over bluetooth by a low power device (a beacon). The user must be at most 100 meters (generally even closer) away from the beacon to receive the URL. This allows the URL to be context based (e.g. one is at a train station and the schedule is available as a physical web page).">
-    Nearby
-  </message>
-
-  <message name="IDS_NTP_PHYSICAL_WEB_PAGE_SUGGESTIONS_SECTION_EMPTY" desc="On the New Tab Page, text of the card explaining to the user that they can expect to see physical web page suggestions in this area in the future, but currently this area is empty. Connection to the physical web section header (which is currently 'Nearby') must be clear, but the words (i.e. translation of 'nearby') may differ. The physical web section shows a list of physical web pages available near the user. A physical web page is a URL transmitted over bluetooth by a low power device (a beacon). The user must be at most 100 meters (generally even closer) away from the beacon to receive the URL. This allows the URL to be context based (e.g. one is at a train station and the schedule is available as a physical web page).">
-    Your nearby suggestions appear here
-  </message>
-
   <if expr="use_titlecase">
     <message name="IDS_NTP_READING_LIST_SUGGESTIONS_SECTION_HEADER" desc="Header of the Reading List section. The Reading List section shows a list of unread pages from the Reading List.">
       Reading List
diff --git a/components/omnibox/browser/keyword_provider.cc b/components/omnibox/browser/keyword_provider.cc
index ed322eb..7847e518 100644
--- a/components/omnibox/browser/keyword_provider.cc
+++ b/components/omnibox/browser/keyword_provider.cc
@@ -572,7 +572,14 @@
   }
 
   // Remove leading "www.", if any, and again try to find a matching keyword.
-  result = url_formatter::StripWWW(result);
+  // The 'www.' stripping is done directly here instead of calling
+  // url_formatter::StripWWW because we're not assuming that the keyword is a
+  // hostname.
+  const base::string16 kWww(base::ASCIIToUTF16("www."));
+  constexpr size_t kWwwLength = 4;
+  result = base::StartsWith(result, kWww, base::CompareCase::SENSITIVE)
+               ? result.substr(kWwwLength)
+               : result;
   if (template_url_service->GetTemplateURLForKeyword(result) != nullptr)
     return result;
 
diff --git a/components/omnibox_strings.grdp b/components/omnibox_strings.grdp
index d3c812b9..98627559 100644
--- a/components/omnibox_strings.grdp
+++ b/components/omnibox_strings.grdp
@@ -68,15 +68,6 @@
   <message name="IDS_OMNIBOX_TAB_SUGGEST_SHORT_HINT" desc="A shortened, one word version of button text to say that this suggestion will switch to another tab.">
     Switch
   </message>
-  <message name="IDS_PHYSICAL_WEB_OVERFLOW_DESCRIPTION" desc="The description in the omnibox dropdown indicating that multiple nearby devices are broadcasting URLs.">
-    Physical Web suggestions
-  </message>
-  <message name="IDS_PHYSICAL_WEB_OVERFLOW" desc="A label in the omnibox dropdown indicating the number of additional nearby devices broadcasting URLs.">
-    {URL_count, plural, =1 {and 1 more web page} other {and # more web pages}}
-  </message>
-  <message name="IDS_PHYSICAL_WEB_OVERFLOW_EMPTY_TITLE" desc="A label in the omnibox dropdown indicating the number of additional nearby devices broadcasting URLs. This version is used when the title of the top result is empty.">
-    {URL_count, plural, =1 {1 web page nearby} other {# web pages nearby}}
-  </message>
   <message name="IDS_OMNIBOX_FILE" desc="Text shown in the omnibox to indicate a user is viewing a file.">
     File
   </message>
diff --git a/components/page_load_metrics/browser/page_load_metrics_util.cc b/components/page_load_metrics/browser/page_load_metrics_util.cc
index 592fb1d..2353b69 100644
--- a/components/page_load_metrics/browser/page_load_metrics_util.cc
+++ b/components/page_load_metrics/browser/page_load_metrics_util.cc
@@ -132,6 +132,10 @@
           event.value() <= delegate.GetFirstBackgroundTime().value());
 }
 
+bool WasInForeground(const PageLoadMetricsObserverDelegate& delegate) {
+  return delegate.StartedInForeground() || delegate.GetFirstForegroundTime();
+}
+
 PageAbortInfo GetPageAbortInfo(
     const PageLoadMetricsObserverDelegate& delegate) {
   if (IsBackgroundAbort(delegate)) {
diff --git a/components/page_load_metrics/browser/page_load_metrics_util.h b/components/page_load_metrics/browser/page_load_metrics_util.h
index c2dbd870..33f62e05 100644
--- a/components/page_load_metrics/browser/page_load_metrics_util.h
+++ b/components/page_load_metrics/browser/page_load_metrics_util.h
@@ -117,6 +117,10 @@
     const base::Optional<base::TimeDelta>& event,
     const PageLoadMetricsObserverDelegate& delegate);
 
+// Returns true if |delegate| started in the foreground or became foregrounded
+// at some point in time.
+bool WasInForeground(const PageLoadMetricsObserverDelegate& delegate);
+
 PageAbortInfo GetPageAbortInfo(const PageLoadMetricsObserverDelegate& delegate);
 
 // Get the duration of time that the page spent in the foreground, from
diff --git a/components/paint_preview/browser/paint_preview_client.cc b/components/paint_preview/browser/paint_preview_client.cc
index b97a04e..8625772 100644
--- a/components/paint_preview/browser/paint_preview_client.cc
+++ b/components/paint_preview/browser/paint_preview_client.cc
@@ -168,6 +168,9 @@
   // TODO(crbug/1044983): Investigate possible issues with cleanup if just
   // a single subframe gets deleted.
   auto maybe_token = render_frame_host->GetEmbeddingToken();
+  if (!maybe_token.has_value())
+    return;
+
   bool is_main_frame = render_frame_host->GetParent() == nullptr;
   base::UnguessableToken frame_guid = maybe_token.value();
   auto it = pending_previews_on_subframe_.find(frame_guid);
diff --git a/components/paint_preview/player/android/BUILD.gn b/components/paint_preview/player/android/BUILD.gn
index 6ff295f..372766f 100644
--- a/components/paint_preview/player/android/BUILD.gn
+++ b/components/paint_preview/player/android/BUILD.gn
@@ -54,6 +54,7 @@
     "java/src/org/chromium/components/paintpreview/player/PlayerCompositorDelegateImpl.java",
     "java/src/org/chromium/components/paintpreview/player/PlayerManager.java",
     "java/src/org/chromium/components/paintpreview/player/PlayerSwipeRefreshHandler.java",
+    "java/src/org/chromium/components/paintpreview/player/PlayerUserActionRecorder.java",
     "java/src/org/chromium/components/paintpreview/player/frame/PlayerFrameBitmapPainter.java",
     "java/src/org/chromium/components/paintpreview/player/frame/PlayerFrameBitmapState.java",
     "java/src/org/chromium/components/paintpreview/player/frame/PlayerFrameCoordinator.java",
diff --git a/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/PlayerCompositorDelegate.java b/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/PlayerCompositorDelegate.java
index d12d294..578554ed 100644
--- a/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/PlayerCompositorDelegate.java
+++ b/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/PlayerCompositorDelegate.java
@@ -34,4 +34,10 @@
      * @param y The y coordinate of the click event, relative to the frame.
      */
     void onClick(UnguessableToken frameGuid, int x, int y);
+
+    /**
+     * Sets whether to compress the directory when closing the player.
+     * @param compressOnClose Whether to compress the directory when closing.
+     */
+    default void setCompressOnClose(boolean compressOnClose) {}
 }
diff --git a/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/PlayerCompositorDelegateImpl.java b/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/PlayerCompositorDelegateImpl.java
index 32e06253..75316dc 100644
--- a/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/PlayerCompositorDelegateImpl.java
+++ b/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/PlayerCompositorDelegateImpl.java
@@ -105,8 +105,19 @@
                 mNativePlayerCompositorDelegate, frameGuid, x, y);
     }
 
+    @Override
+    public void setCompressOnClose(boolean compressOnClose) {
+        if (mNativePlayerCompositorDelegate == 0) {
+            return;
+        }
+
+        PlayerCompositorDelegateImplJni.get().setCompressOnClose(
+                mNativePlayerCompositorDelegate, compressOnClose);
+    }
+
     @CalledByNative
     public void onLinkClicked(String url) {
+        PlayerUserActionRecorder.recordLinkClick();
         mLinkClickHandler.onLinkClicked(new GURL(url));
     }
 
@@ -129,5 +140,7 @@
                 int clipX, int clipY, int clipWidth, int clipHeight);
         void onClick(long nativePlayerCompositorDelegateAndroid, UnguessableToken frameGuid, int x,
                 int y);
+        void setCompressOnClose(
+                long nativePlayerCompositorDelegateAndroid, boolean compressOnClose);
     }
 }
diff --git a/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/PlayerManager.java b/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/PlayerManager.java
index a362aa8..e40851e 100644
--- a/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/PlayerManager.java
+++ b/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/PlayerManager.java
@@ -168,6 +168,12 @@
         }
     }
 
+    public void setCompressOnClose(boolean compressOnClose) {
+        if (mDelegate != null) {
+            mDelegate.setCompressOnClose(compressOnClose);
+        }
+    }
+
     public void destroy() {
         if (mDelegate != null) {
             mDelegate.destroy();
diff --git a/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/PlayerUserActionRecorder.java b/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/PlayerUserActionRecorder.java
new file mode 100644
index 0000000..99dbb71
--- /dev/null
+++ b/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/PlayerUserActionRecorder.java
@@ -0,0 +1,53 @@
+// Copyright 2020 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.components.paintpreview.player;
+
+import org.chromium.base.metrics.RecordUserAction;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/** Helper class for recording paint preview user actions. */
+public class PlayerUserActionRecorder {
+    private static final String ACTION_FLING = "PaintPreview.Player.Flung";
+    private static final String ACTION_SCROLL = "PaintPreview.Player.Scrolled";
+    private static final String ACTION_ZOOM = "PaintPreview.Player.Zoomed";
+    private static final String ACTION_LINK_CLICK = "PaintPreview.Player.LinkClicked";
+
+    private static final long NO_RECORD_WINDOW_MS = (long) (.5 * 1000);
+    private static Map<String, Long> sLastRecordMap = new HashMap<>();
+
+    private static boolean shouldNotRecord(String action) {
+        long nowMs = System.currentTimeMillis();
+        long lastRecordedMs = 0;
+        if (sLastRecordMap.get(action) != null) lastRecordedMs = sLastRecordMap.get(action);
+        return (nowMs - lastRecordedMs) < NO_RECORD_WINDOW_MS;
+    }
+
+    public static void recordScroll() {
+        if (shouldNotRecord(ACTION_SCROLL)) return;
+
+        RecordUserAction.record(ACTION_SCROLL);
+        sLastRecordMap.put(ACTION_SCROLL, System.currentTimeMillis());
+    }
+
+    public static void recordFling() {
+        if (shouldNotRecord(ACTION_FLING)) return;
+
+        RecordUserAction.record(ACTION_FLING);
+        sLastRecordMap.put(ACTION_FLING, System.currentTimeMillis());
+    }
+
+    public static void recordZoom() {
+        if (shouldNotRecord(ACTION_ZOOM)) return;
+
+        RecordUserAction.record(ACTION_ZOOM);
+        sLastRecordMap.put(ACTION_ZOOM, System.currentTimeMillis());
+    }
+
+    public static void recordLinkClick() {
+        RecordUserAction.record(ACTION_LINK_CLICK);
+    }
+}
diff --git a/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/frame/PlayerFrameScaleController.java b/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/frame/PlayerFrameScaleController.java
index 8f1461f..223e502d 100644
--- a/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/frame/PlayerFrameScaleController.java
+++ b/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/frame/PlayerFrameScaleController.java
@@ -8,6 +8,8 @@
 import android.graphics.Rect;
 import android.util.Size;
 
+import org.chromium.components.paintpreview.player.PlayerUserActionRecorder;
+
 import javax.annotation.Nullable;
 
 /**
@@ -138,7 +140,6 @@
         }
         mMediatorDelegate.setBitmapScaleMatrix(mBitmapScaleMatrix, mUncommittedScaleFactor);
         if (mUserInteractionCallback != null) mUserInteractionCallback.run();
-
         return true;
     }
 
@@ -171,7 +172,7 @@
         mMediatorDelegate.resetScaleFactorOfAllSubframes();
         mMediatorDelegate.updateVisuals(true);
         mMediatorDelegate.forceRedrawVisibleSubframes();
-
+        PlayerUserActionRecorder.recordZoom();
         return true;
     }
 }
diff --git a/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/frame/PlayerFrameScrollController.java b/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/frame/PlayerFrameScrollController.java
index 943dae0..2f10f194 100644
--- a/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/frame/PlayerFrameScrollController.java
+++ b/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/frame/PlayerFrameScrollController.java
@@ -10,6 +10,7 @@
 import android.widget.OverScroller;
 
 import org.chromium.components.paintpreview.player.OverscrollHandler;
+import org.chromium.components.paintpreview.player.PlayerUserActionRecorder;
 
 import javax.annotation.Nullable;
 
@@ -56,7 +57,9 @@
      */
     public boolean scrollBy(float distanceX, float distanceY) {
         mScroller.forceFinished(true);
-        return scrollByInternal(distanceX, distanceY);
+        boolean result = scrollByInternal(distanceX, distanceY);
+        if (result) PlayerUserActionRecorder.recordScroll();
+        return result;
     }
 
     /**
@@ -76,6 +79,7 @@
                 scaledContentHeight - viewportRect.height());
 
         mScrollerHandler.post(this::handleFling);
+        if (!mScroller.isFinished()) PlayerUserActionRecorder.recordFling();
         return true;
     }
 
diff --git a/components/paint_preview/player/android/javatests/paint_preview_test_service.cc b/components/paint_preview/player/android/javatests/paint_preview_test_service.cc
index 965ee46..73a7db3 100644
--- a/components/paint_preview/player/android/javatests/paint_preview_test_service.cc
+++ b/components/paint_preview/player/android/javatests/paint_preview_test_service.cc
@@ -27,6 +27,11 @@
                     const DirectoryKey& key,
                     PaintPreviewBaseService::OnReadProtoCallback callback,
                     std::unique_ptr<PaintPreviewProto> proto) {
+  if (proto == nullptr) {
+    std::move(callback).Run(std::move(proto));
+    return;
+  }
+
   // Update the file path for the root SKP to match the isolated test
   // environment.
   std::string root_skp_file_name =
diff --git a/components/paint_preview/player/android/javatests/src/org/chromium/components/paintpreview/player/PaintPreviewPlayerTest.java b/components/paint_preview/player/android/javatests/src/org/chromium/components/paintpreview/player/PaintPreviewPlayerTest.java
index c0a479b..8a1d699a 100644
--- a/components/paint_preview/player/android/javatests/src/org/chromium/components/paintpreview/player/PaintPreviewPlayerTest.java
+++ b/components/paint_preview/player/android/javatests/src/org/chromium/components/paintpreview/player/PaintPreviewPlayerTest.java
@@ -162,6 +162,7 @@
                         Assert.fail("View Ready callback occurred, but expected a failure.");
                     }, null,
                     0xffffffff, () -> { compositorErrorCallback.notifyCalled(); }, false);
+            mPlayerManager.setCompressOnClose(false);
         });
         compositorErrorCallback.waitForFirst();
     }
@@ -219,6 +220,7 @@
                     () -> { viewReady.notifyCalled(); }, null,
                     0xffffffff, () -> { Assert.fail("Compositor initialization failed."); },
                     false);
+            mPlayerManager.setCompressOnClose(false);
             getActivity().setContentView(mPlayerManager.getView());
         });
 
diff --git a/components/paint_preview/player/android/player_compositor_delegate_android.cc b/components/paint_preview/player/android/player_compositor_delegate_android.cc
index ead1788..2fe40de 100644
--- a/components/paint_preview/player/android/player_compositor_delegate_android.cc
+++ b/components/paint_preview/player/android/player_compositor_delegate_android.cc
@@ -268,6 +268,13 @@
       base::android::ConvertUTF8ToJavaString(env, res[0]->spec()));
 }
 
+void PlayerCompositorDelegateAndroid::SetCompressOnClose(
+    JNIEnv* env,
+    jboolean compress_on_close) {
+  PlayerCompositorDelegate::SetCompressOnClose(
+      static_cast<bool>(compress_on_close));
+}
+
 void PlayerCompositorDelegateAndroid::Destroy(JNIEnv* env) {
   delete this;
 }
diff --git a/components/paint_preview/player/android/player_compositor_delegate_android.h b/components/paint_preview/player/android/player_compositor_delegate_android.h
index aec2b24..434ea78 100644
--- a/components/paint_preview/player/android/player_compositor_delegate_android.h
+++ b/components/paint_preview/player/android/player_compositor_delegate_android.h
@@ -49,6 +49,9 @@
                jint j_x,
                jint j_y);
 
+  // Called to set if compression should happen at close time.
+  void SetCompressOnClose(JNIEnv* env, jboolean compress_on_close);
+
   void Destroy(JNIEnv* env);
 
   static void CompositeResponseFramesToVectors(
diff --git a/components/paint_preview/player/player_compositor_delegate.cc b/components/paint_preview/player/player_compositor_delegate.cc
index 1e4e2f79..9afb4af9 100644
--- a/components/paint_preview/player/player_compositor_delegate.cc
+++ b/components/paint_preview/player/player_compositor_delegate.cc
@@ -142,7 +142,8 @@
     bool skip_service_launch)
     : compositor_error_(std::move(compositor_error)),
       paint_preview_service_(paint_preview_service),
-      key_(key) {
+      key_(key),
+      compress_on_close_(true) {
   if (skip_service_launch) {
     paint_preview_service_->GetCapturedPaintPreviewProto(
         key, base::BindOnce(&PlayerCompositorDelegate::OnProtoAvailable,
@@ -167,10 +168,12 @@
 }
 
 PlayerCompositorDelegate::~PlayerCompositorDelegate() {
-  paint_preview_service_->GetTaskRunner()->PostTask(
-      FROM_HERE,
-      base::BindOnce(base::IgnoreResult(&FileManager::CompressDirectory),
-                     paint_preview_service_->GetFileManager(), key_));
+  if (compress_on_close_) {
+    paint_preview_service_->GetTaskRunner()->PostTask(
+        FROM_HERE,
+        base::BindOnce(base::IgnoreResult(&FileManager::CompressDirectory),
+                       paint_preview_service_->GetFileManager(), key_));
+  }
 }
 
 void PlayerCompositorDelegate::OnCompositorServiceDisconnected() {
diff --git a/components/paint_preview/player/player_compositor_delegate.h b/components/paint_preview/player/player_compositor_delegate.h
index 372d568e..4da92a2 100644
--- a/components/paint_preview/player/player_compositor_delegate.h
+++ b/components/paint_preview/player/player_compositor_delegate.h
@@ -39,6 +39,8 @@
   PlayerCompositorDelegate(const PlayerCompositorDelegate&) = delete;
   PlayerCompositorDelegate& operator=(const PlayerCompositorDelegate&) = delete;
 
+  void SetCompressOnClose(bool compress) { compress_on_close_ = compress; }
+
   virtual void OnCompositorReady(
       mojom::PaintPreviewCompositor::Status status,
       mojom::PaintPreviewBeginCompositeResponsePtr composite_response) {}
@@ -73,6 +75,7 @@
 
   PaintPreviewBaseService* paint_preview_service_;
   DirectoryKey key_;
+  bool compress_on_close_;
   std::unique_ptr<PaintPreviewCompositorService>
       paint_preview_compositor_service_;
   std::unique_ptr<PaintPreviewCompositorClient>
diff --git a/components/password_manager/core/browser/mock_password_feature_manager.h b/components/password_manager/core/browser/mock_password_feature_manager.h
index 4ec7a46..c92fc2e 100644
--- a/components/password_manager/core/browser/mock_password_feature_manager.h
+++ b/components/password_manager/core/browser/mock_password_feature_manager.h
@@ -19,7 +19,7 @@
 
   MOCK_CONST_METHOD0(IsOptedInForAccountStorage, bool());
   MOCK_CONST_METHOD0(ShouldShowAccountStorageOptIn, bool());
-  MOCK_CONST_METHOD0(ShouldShowAccountStorageReSignin, bool());
+  MOCK_CONST_METHOD1(ShouldShowAccountStorageReSignin, bool(const GURL&));
   MOCK_METHOD0(OptInToAccountStorage, void());
   MOCK_METHOD0(OptOutOfAccountStorageAndClearSettings, void());
 
diff --git a/components/password_manager/core/browser/password_autofill_manager.cc b/components/password_manager/core/browser/password_autofill_manager.cc
index a4960edf..63f2d11 100644
--- a/components/password_manager/core/browser/password_autofill_manager.cc
+++ b/components/password_manager/core/browser/password_autofill_manager.cc
@@ -584,7 +584,8 @@
                               ->ShouldShowAccountStorageOptIn();
   bool show_account_storage_resignin =
       password_client_ && password_client_->GetPasswordFeatureManager()
-                              ->ShouldShowAccountStorageReSignin();
+                              ->ShouldShowAccountStorageReSignin(
+                                  password_client_->GetLastCommittedURL());
 
   if (!fill_data_ && !show_account_storage_optin &&
       !show_account_storage_resignin) {
diff --git a/components/password_manager/core/browser/password_feature_manager.h b/components/password_manager/core/browser/password_feature_manager.h
index b6dce40..f2cd4b1 100644
--- a/components/password_manager/core/browser/password_feature_manager.h
+++ b/components/password_manager/core/browser/password_feature_manager.h
@@ -33,7 +33,12 @@
   // Whether it makes sense to ask the user to signin again to access the
   // account-based password storage. This is true if a user on this device
   // previously opted into using the account store but is signed-out now.
-  virtual bool ShouldShowAccountStorageReSignin() const = 0;
+  // |current_page_url| is the current URL, used to suppress the promo on the
+  // Google signin page (no point in asking the user to sign in while they're
+  // already doing that). For non-web contexts (e.g. native UIs), it is valid to
+  // pass an empty GURL.
+  virtual bool ShouldShowAccountStorageReSignin(
+      const GURL& current_page_url) const = 0;
 
   // Sets opt-in to using account storage for passwords for the current
   // signed-in user (unconsented primary account).
diff --git a/components/password_manager/core/browser/password_feature_manager_impl.cc b/components/password_manager/core/browser/password_feature_manager_impl.cc
index ed983ea..7ceb1a0 100644
--- a/components/password_manager/core/browser/password_feature_manager_impl.cc
+++ b/components/password_manager/core/browser/password_feature_manager_impl.cc
@@ -40,9 +40,10 @@
                                                       sync_service_);
 }
 
-bool PasswordFeatureManagerImpl::ShouldShowAccountStorageReSignin() const {
-  return features_util::ShouldShowAccountStorageReSignin(pref_service_,
-                                                         sync_service_);
+bool PasswordFeatureManagerImpl::ShouldShowAccountStorageReSignin(
+    const GURL& current_page_url) const {
+  return features_util::ShouldShowAccountStorageReSignin(
+      pref_service_, sync_service_, current_page_url);
 }
 
 void PasswordFeatureManagerImpl::OptInToAccountStorage() {
diff --git a/components/password_manager/core/browser/password_feature_manager_impl.h b/components/password_manager/core/browser/password_feature_manager_impl.h
index 8505e3c..e49d484 100644
--- a/components/password_manager/core/browser/password_feature_manager_impl.h
+++ b/components/password_manager/core/browser/password_feature_manager_impl.h
@@ -28,7 +28,8 @@
 
   bool IsOptedInForAccountStorage() const override;
   bool ShouldShowAccountStorageOptIn() const override;
-  bool ShouldShowAccountStorageReSignin() const override;
+  bool ShouldShowAccountStorageReSignin(
+      const GURL& current_page_url) const override;
   void OptInToAccountStorage() override;
   void OptOutOfAccountStorageAndClearSettings() override;
 
diff --git a/components/password_manager/core/browser/password_form_filling.cc b/components/password_manager/core/browser/password_form_filling.cc
index b6158b0..ebd7ebd 100644
--- a/components/password_manager/core/browser/password_form_filling.cc
+++ b/components/password_manager/core/browser/password_form_filling.cc
@@ -144,7 +144,8 @@
   if (best_matches.empty()) {
     bool should_show_popup_without_passwords =
         client->GetPasswordFeatureManager()->ShouldShowAccountStorageOptIn() ||
-        client->GetPasswordFeatureManager()->ShouldShowAccountStorageReSignin();
+        client->GetPasswordFeatureManager()->ShouldShowAccountStorageReSignin(
+            client->GetLastCommittedURL());
     driver->InformNoSavedCredentials(should_show_popup_without_passwords);
     metrics_recorder->RecordFillEvent(
         PasswordFormMetricsRecorder::kManagerFillEventNoCredential);
diff --git a/components/password_manager/core/browser/password_manager_features_util.cc b/components/password_manager/core/browser/password_manager_features_util.cc
index a287f60..803012d0 100644
--- a/components/password_manager/core/browser/password_manager_features_util.cc
+++ b/components/password_manager/core/browser/password_manager_features_util.cc
@@ -16,6 +16,7 @@
 #include "components/signin/public/identity_manager/account_info.h"
 #include "components/sync/driver/sync_service.h"
 #include "components/sync/driver/sync_user_settings.h"
+#include "google_apis/gaia/gaia_urls.h"
 
 using autofill::GaiaIdHash;
 using autofill::PasswordForm;
@@ -186,7 +187,8 @@
 }
 
 bool ShouldShowAccountStorageReSignin(const PrefService* pref_service,
-                                      const syncer::SyncService* sync_service) {
+                                      const syncer::SyncService* sync_service,
+                                      const GURL& current_page_url) {
   DCHECK(pref_service);
 
   // Checks that the sync_service is not null and the feature is enabled.
@@ -200,6 +202,11 @@
     return false;
   }
 
+  if (current_page_url.GetOrigin() ==
+      GaiaUrls::GetInstance()->gaia_url().GetOrigin()) {
+    return false;
+  }
+
   const base::DictionaryValue* global_pref =
       pref_service->GetDictionary(prefs::kAccountStoragePerAccountSettings);
   // Show the opt-in if any known previous user opted into using the account
@@ -385,7 +392,7 @@
   if (sync_service->HasDisableReason(
           syncer::SyncService::DisableReason::DISABLE_REASON_NOT_SIGNED_IN)) {
     // Signed out. Check if any account storage opt-in exists.
-    return ShouldShowAccountStorageReSignin(pref_service, sync_service)
+    return ShouldShowAccountStorageReSignin(pref_service, sync_service, GURL())
                ? PasswordAccountStorageUserState::kSignedOutAccountStoreUser
                : PasswordAccountStorageUserState::kSignedOutUser;
   }
diff --git a/components/password_manager/core/browser/password_manager_features_util.h b/components/password_manager/core/browser/password_manager_features_util.h
index 4f46739..d7529ad4 100644
--- a/components/password_manager/core/browser/password_manager_features_util.h
+++ b/components/password_manager/core/browser/password_manager_features_util.h
@@ -41,9 +41,14 @@
 // Whether it makes sense to ask the user to signin again to access the
 // account-based password storage. This is true if a user on this device
 // previously opted into using the account store but is signed-out now.
+// |current_page_url| is the current URL, used to suppress the promo on the
+// Google signin page (no point in asking the user to sign in while they're
+// already doing that). For non-web contexts (e.g. native UIs), it is valid to
+// pass an empty GURL.
 // See PasswordFeatureManager::ShouldShowAccountStorageReSignin.
 bool ShouldShowAccountStorageReSignin(const PrefService* pref_service,
-                                      const syncer::SyncService* sync_service);
+                                      const syncer::SyncService* sync_service,
+                                      const GURL& current_page_url);
 
 // Sets opt-in to using account storage for passwords for the current
 // signed-in user (unconsented primary account).
diff --git a/components/password_manager/core/browser/password_manager_features_util_unittest.cc b/components/password_manager/core/browser/password_manager_features_util_unittest.cc
index 261f11a..741fad7a 100644
--- a/components/password_manager/core/browser/password_manager_features_util_unittest.cc
+++ b/components/password_manager/core/browser/password_manager_features_util_unittest.cc
@@ -81,7 +81,8 @@
   sync_service.SetDisableReasons(
       {syncer::SyncService::DisableReason::DISABLE_REASON_NOT_SIGNED_IN});
 
-  EXPECT_TRUE(ShouldShowAccountStorageReSignin(&pref_service, &sync_service));
+  EXPECT_TRUE(
+      ShouldShowAccountStorageReSignin(&pref_service, &sync_service, GURL()));
 }
 
 TEST(PasswordFeatureManagerUtil, ShowAccountStorageResignIn_FeatureDisabled) {
@@ -101,7 +102,8 @@
   sync_service.SetDisableReasons(
       {syncer::SyncService::DisableReason::DISABLE_REASON_NOT_SIGNED_IN});
 
-  EXPECT_FALSE(ShouldShowAccountStorageReSignin(&pref_service, &sync_service));
+  EXPECT_FALSE(
+      ShouldShowAccountStorageReSignin(&pref_service, &sync_service, GURL()));
 }
 
 TEST(PasswordFeatureManagerUtil, DontShowAccountStorageResignIn_SyncActive) {
@@ -119,7 +121,8 @@
   // SyncService is running (e.g for a different signed-in user).
   sync_service.SetTransportState(syncer::SyncService::TransportState::ACTIVE);
 
-  EXPECT_FALSE(ShouldShowAccountStorageReSignin(&pref_service, &sync_service));
+  EXPECT_FALSE(
+      ShouldShowAccountStorageReSignin(&pref_service, &sync_service, GURL()));
 }
 
 TEST(PasswordFeatureManagerUtil, DontShowAccountStorageResignIn_NoPrefs) {
@@ -137,7 +140,42 @@
   sync_service.SetDisableReasons(
       {syncer::SyncService::DisableReason::DISABLE_REASON_NOT_SIGNED_IN});
 
-  EXPECT_FALSE(ShouldShowAccountStorageReSignin(&pref_service, &sync_service));
+  EXPECT_FALSE(
+      ShouldShowAccountStorageReSignin(&pref_service, &sync_service, GURL()));
+}
+
+TEST(PasswordFeatureManagerUtil, DontShowAccountStorageResignIn_GaiaUrl) {
+  TestingPrefServiceSimple pref_service;
+  syncer::TestSyncService sync_service;
+  base::test::ScopedFeatureList features;
+  features.InitAndEnableFeature(features::kEnablePasswordsAccountStorage);
+
+  // Add an account to prefs which opted into using the account-storage.
+  pref_service.registry()->RegisterDictionaryPref(
+      prefs::kAccountStoragePerAccountSettings);
+  pref_service.Set(prefs::kAccountStoragePerAccountSettings,
+                   CreateOptedInAccountPref());
+
+  // SyncService is not running (because no user is signed-in).
+  sync_service.SetTransportState(syncer::SyncService::TransportState::DISABLED);
+  sync_service.SetDisableReasons(
+      {syncer::SyncService::DisableReason::DISABLE_REASON_NOT_SIGNED_IN});
+
+  // The re-signin promo should show up in contexts without a URL (e.g. native
+  // UI).
+  EXPECT_TRUE(
+      ShouldShowAccountStorageReSignin(&pref_service, &sync_service, GURL()));
+  // The re-signin promo should show up on all regular pages.
+  EXPECT_TRUE(ShouldShowAccountStorageReSignin(&pref_service, &sync_service,
+                                               GURL("http://www.example.com")));
+  EXPECT_TRUE(ShouldShowAccountStorageReSignin(
+      &pref_service, &sync_service, GURL("https://www.example.com")));
+  // The re-signin promo should NOT show up on Google sign-in pages.
+  EXPECT_FALSE(ShouldShowAccountStorageReSignin(
+      &pref_service, &sync_service, GURL("https://accounts.google.com")));
+  EXPECT_FALSE(ShouldShowAccountStorageReSignin(
+      &pref_service, &sync_service,
+      GURL("https://accounts.google.com/some/path")));
 }
 
 TEST(PasswordFeatureManagerUtil, AccountStoragePerAccountSettings) {
diff --git a/components/password_manager/ios/password_form_helper.h b/components/password_manager/ios/password_form_helper.h
index 5d59390..711dcd6 100644
--- a/components/password_manager/ios/password_form_helper.h
+++ b/components/password_manager/ios/password_form_helper.h
@@ -7,6 +7,7 @@
 
 #import <Foundation/Foundation.h>
 
+#include "base/memory/ref_counted_memory.h"
 #include "components/autofill/core/common/renderer_id.h"
 #import "components/autofill/ios/form_util/form_activity_observer_bridge.h"
 #import "ios/web/public/web_state_observer_bridge.h"
@@ -18,6 +19,7 @@
 @class PasswordFormHelper;
 
 namespace autofill {
+class FieldDataManager;
 struct FormData;
 struct PasswordFormFillData;
 }  // namespace autofill
@@ -54,6 +56,15 @@
 // Returns empty URL if current web state is not available.
 @property(nonatomic, readonly) const GURL& lastCommittedURL;
 
+// Maps UniqueFieldId of an input element to the pair of:
+// 1) The most recent text that user typed or PasswordManager autofilled in
+// input elements. Used for storing username/password before JavaScript
+// changes them.
+// 2) Field properties mask, i.e. whether the field was autofilled, modified
+// by user, etc. (see FieldPropertiesMask).
+@property(nonatomic, readonly) scoped_refptr<autofill::FieldDataManager>
+    fieldDataManager;
+
 // Uses JavaScript to find password forms. Calls |completionHandler| with the
 // extracted information used for matching and saving passwords. Calls
 // |completionHandler| with an empty vector if no password forms are found.
@@ -84,13 +95,6 @@
 - (void)fillPasswordFormWithFillData:(const password_manager::FillData&)fillData
                    completionHandler:(nullable void (^)(BOOL))completionHandler;
 
-// Finds password forms in the page and fills them with the |username| and
-// |password|. If not nil, |completionHandler| is called once per form filled.
-- (void)findAndFillPasswordFormsWithUserName:(NSString*)username
-                                    password:(NSString*)password
-                           completionHandler:
-                               (nullable void (^)(BOOL))completionHandler;
-
 // Finds the password form with unique ID |formIdentifier| and calls
 // |completionHandler| with the populated |FormData| data structure. |found| is
 // YES if the current form was found successfully, NO otherwise.
@@ -104,6 +108,11 @@
 - (void)setUpForUniqueIDsWithInitialState:(uint32_t)nextAvailableID
                                   inFrame:(web::WebFrame*)frame;
 
+// Updates the stored field data. In case if there is a presaved credential it
+// updates the presaved credential.
+- (void)updateFieldDataOnUserInput:(autofill::FieldRendererId)field_id
+                        inputValue:(NSString*)field_value;
+
 // Creates a instance with the given WebState, observer and delegate.
 - (instancetype)initWithWebState:(web::WebState*)webState
                         delegate:
diff --git a/components/password_manager/ios/password_form_helper.mm b/components/password_manager/ios/password_form_helper.mm
index 7a30b33..178b1e9 100644
--- a/components/password_manager/ios/password_form_helper.mm
+++ b/components/password_manager/ios/password_form_helper.mm
@@ -10,10 +10,10 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/values.h"
+#include "components/autofill/core/common/field_data_manager.h"
 #include "components/autofill/core/common/form_data.h"
 #include "components/autofill/core/common/password_form_fill_data.h"
 #include "components/autofill/ios/browser/autofill_util.h"
-#include "components/password_manager/core/browser/form_parsing/form_parser.h"
 #include "components/password_manager/core/browser/password_form_filling.h"
 #include "components/password_manager/ios/account_select_fill_data.h"
 #include "components/password_manager/ios/js_password_manager.h"
@@ -26,13 +26,12 @@
 #error "This file requires ARC support."
 #endif
 
+using autofill::FieldPropertiesFlags;
 using autofill::FormData;
 using autofill::FormRendererId;
 using autofill::FieldRendererId;
-using autofill::PasswordForm;
 using autofill::PasswordFormFillData;
 using password_manager::FillData;
-using password_manager::FormDataParser;
 using password_manager::GetPageURLAndCheckTrustLevel;
 using password_manager::SerializePasswordFormFillData;
 
@@ -106,6 +105,7 @@
 
 @synthesize delegate = _delegate;
 @synthesize jsPasswordManager = _jsPasswordManager;
+@synthesize fieldDataManager = _fieldDataManager;
 
 - (const GURL&)lastCommittedURL {
   return _webState ? _webState->GetLastCommittedURL() : GURL::EmptyGURL();
@@ -126,6 +126,7 @@
     _formActivityObserverBridge =
         std::make_unique<autofill::FormActivityObserverBridge>(_webState, self);
     _jsPasswordManager = [[JsPasswordManager alloc] init];
+    _fieldDataManager = base::MakeRefCounted<autofill::FieldDataManager>();
 
     __weak PasswordFormHelper* weakSelf = self;
     auto callback = base::BindRepeating(
@@ -216,6 +217,9 @@
     return NO;
   }
 
+  // Extract FieldDataManager data for observed fields.
+  [self extractKnownFieldData:form];
+
   if (_webState && self.delegate) {
     [self.delegate formHelper:self didSubmitForm:form inMainFrame:YES];
     return YES;
@@ -232,9 +236,13 @@
                                   pageURL.GetOrigin(), &formsData)) {
     return;
   }
+  // Extract FieldDataManager data for observed form fields.
+  for (FormData& form : formsData)
+    [self extractKnownFieldData:form];
   *forms = std::move(formsData);
 }
 
+// TODO(kazinova): remove unnecessary arguments.
 - (void)fillPasswordForm:(const autofill::PasswordFormFillData&)formData
             withUsername:(const base::string16&)username
                 password:(const base::string16&)password
@@ -259,6 +267,18 @@
       }];
 }
 
+// Extracts known field data.
+- (void)extractKnownFieldData:(FormData&)form {
+  for (auto& field : form.fields) {
+    if (self.fieldDataManager->HasFieldData(field.unique_renderer_id)) {
+      field.typed_value =
+          self.fieldDataManager->GetUserTypedValue(field.unique_renderer_id);
+      field.properties_mask = self.fieldDataManager->GetFieldPropertiesMask(
+          field.unique_renderer_id);
+    }
+  }
+}
+
 #pragma mark - Private methods for test only
 
 - (void)setJsPasswordManager:(JsPasswordManager*)jsPasswordManager {
@@ -310,6 +330,23 @@
     return;
   }
 
+  // Do not refill the form if a field has user typed input or input filled
+  // on user trigger.
+  FieldRendererId passwordId = formData.password_field.unique_renderer_id;
+  if (self.fieldDataManager->WasAutofilledOnUserTrigger(passwordId) ||
+      self.fieldDataManager->DidUserType(passwordId)) {
+    if (completionHandler) {
+      completionHandler(NO);
+    }
+    return;
+  }
+
+  self.fieldDataManager->UpdateFieldDataMapWithNullValue(
+      formData.username_field.unique_renderer_id,
+      FieldPropertiesFlags::kAutofilledOnPageLoad);
+  self.fieldDataManager->UpdateFieldDataMapWithNullValue(
+      formData.password_field.unique_renderer_id,
+      FieldPropertiesFlags::kAutofilledOnPageLoad);
   [self fillPasswordForm:formData
             withUsername:formData.username_field.value
                 password:formData.password_field.value
@@ -321,6 +358,12 @@
     confirmPasswordIdentifier:(FieldRendererId)confirmPasswordIdentifier
             generatedPassword:(NSString*)generatedPassword
             completionHandler:(nullable void (^)(BOOL))completionHandler {
+  self.fieldDataManager->UpdateFieldDataMapWithNullValue(
+      newPasswordIdentifier, FieldPropertiesFlags::kAutofilledOnUserTrigger);
+  self.fieldDataManager->UpdateFieldDataMapWithNullValue(
+      confirmPasswordIdentifier,
+      FieldPropertiesFlags::kAutofilledOnUserTrigger);
+
   // Send JSON over to the web view.
   [self.jsPasswordManager fillPasswordForm:formIdentifier
                                    inFrame:GetMainFrame(_webState)
@@ -337,6 +380,13 @@
 - (void)fillPasswordFormWithFillData:(const password_manager::FillData&)fillData
                    completionHandler:
                        (nullable void (^)(BOOL))completionHandler {
+  self.fieldDataManager->UpdateFieldDataMapWithNullValue(
+      fillData.username_element_id,
+      FieldPropertiesFlags::kAutofilledOnUserTrigger);
+  self.fieldDataManager->UpdateFieldDataMapWithNullValue(
+      fillData.password_element_id,
+      FieldPropertiesFlags::kAutofilledOnUserTrigger);
+
   [self.jsPasswordManager
        fillPasswordForm:SerializeFillData(fillData)
                 inFrame:GetMainFrame(_webState)
@@ -349,36 +399,6 @@
       }];
 }
 
-- (void)findAndFillPasswordFormsWithUserName:(NSString*)username
-                                    password:(NSString*)password
-                           completionHandler:
-                               (nullable void (^)(BOOL))completionHandler {
-  __weak PasswordFormHelper* weakSelf = self;
-  [self findPasswordFormsWithCompletionHandler:^(
-            const std::vector<FormData>& forms, uint32_t maxID) {
-    PasswordFormHelper* strongSelf = weakSelf;
-    for (const auto& form : forms) {
-      std::vector<const PasswordForm*> matches;
-      FormDataParser parser;
-      std::unique_ptr<PasswordForm> passwordForm =
-          parser.Parse(form, FormDataParser::Mode::kFilling);
-      if (!passwordForm)
-        continue;
-
-      passwordForm->username_value = base::SysNSStringToUTF16(username);
-      passwordForm->password_value = base::SysNSStringToUTF16(password);
-      PasswordFormFillData formData =
-          password_manager::CreatePasswordFormFillData(
-              *passwordForm, matches, *passwordForm,
-              /*wait_for_username=*/false);
-      [strongSelf fillPasswordForm:formData
-                      withUsername:base::SysNSStringToUTF16(username)
-                          password:base::SysNSStringToUTF16(password)
-                 completionHandler:completionHandler];
-    }
-  }];
-}
-
 // Finds the password form named |formName| and calls
 // |completionHandler| with the populated |FormData| data structure. |found| is
 // YES if the current form was found successfully, NO otherwise.
@@ -425,4 +445,11 @@
                                                     inFrame:frame];
 }
 
+- (void)updateFieldDataOnUserInput:(autofill::FieldRendererId)field_id
+                        inputValue:(NSString*)value {
+  self.fieldDataManager->UpdateFieldDataMap(
+      field_id, base::SysNSStringToUTF16(value),
+      autofill::FieldPropertiesFlags::kUserTyped);
+}
+
 @end
diff --git a/components/password_manager/ios/password_form_helper_unittest.mm b/components/password_manager/ios/password_form_helper_unittest.mm
index 7051050..324a323 100644
--- a/components/password_manager/ios/password_form_helper_unittest.mm
+++ b/components/password_manager/ios/password_form_helper_unittest.mm
@@ -13,11 +13,7 @@
 #include "components/autofill/core/browser/logging/log_manager.h"
 #include "components/autofill/core/common/form_data.h"
 #include "components/autofill/core/common/password_form_fill_data.h"
-#include "components/password_manager/core/browser/stub_password_manager_client.h"
-#include "components/password_manager/core/browser/stub_password_manager_driver.h"
 #include "components/password_manager/ios/account_select_fill_data.h"
-#import "components/password_manager/ios/js_password_manager.h"
-#import "components/password_manager/ios/password_form_helper.h"
 #include "components/password_manager/ios/test_helpers.h"
 #include "ios/web/public/test/fakes/test_web_client.h"
 #import "ios/web/public/test/web_test_with_web_state.h"
@@ -31,6 +27,7 @@
 
 NS_ASSUME_NONNULL_BEGIN
 
+using autofill::FieldRendererId;
 using autofill::FormData;
 using autofill::FormRendererId;
 using autofill::PasswordForm;
@@ -40,54 +37,7 @@
 using password_manager::FillData;
 using test_helpers::SetPasswordFormFillData;
 using test_helpers::SetFillData;
-
-@interface PasswordFormHelper (Testing)
-
-// Provides access to replace |jsPasswordManager| with Mock one for test.
-- (void)setJsPasswordManager:(JsPasswordManager*)jsPasswordManager;
-
-@end
-
-// Mocks JsPasswordManager to simluate javascript execution failure.
-@interface MockJsPasswordManager : JsPasswordManager
-
-// For the first |targetFailureCount| calls to
-// |fillPasswordForm:withUserName:password:completionHandler:|, skips the
-// invocation of the real JavaScript manager, giving the effect that password
-// form fill failed. As soon as |_fillPasswordFormFailureCountRemaining| reaches
-// zero, stop mocking and let the original JavaScript manager execute.
-- (void)setFillPasswordFormTargetFailureCount:(NSUInteger)targetFailureCount;
-
-@end
-
-@implementation MockJsPasswordManager {
-  NSUInteger _fillPasswordFormFailureCountRemaining;
-}
-
-- (void)setFillPasswordFormTargetFailureCount:(NSUInteger)targetFailureCount {
-  _fillPasswordFormFailureCountRemaining = targetFailureCount;
-}
-
-- (void)fillPasswordForm:(std::unique_ptr<base::Value>)form
-                 inFrame:(web::WebFrame*)frame
-            withUsername:(NSString*)username
-                password:(NSString*)password
-       completionHandler:(void (^)(NSString*))completionHandler {
-  if (_fillPasswordFormFailureCountRemaining > 0) {
-    --_fillPasswordFormFailureCountRemaining;
-    if (completionHandler) {
-      completionHandler(@"false");
-    }
-    return;
-  }
-  [super fillPasswordForm:std::move(form)
-                  inFrame:frame
-             withUsername:username
-                 password:password
-        completionHandler:completionHandler];
-}
-
-@end
+using test_helpers::SetFormData;
 
 namespace {
 // Returns a string containing the JavaScript loaded from a
@@ -300,16 +250,21 @@
   ExecuteJavaScript(@"__gCrWeb.fill.setUpForUniqueIDs(0);");
   // Run password forms search to set up unique IDs.
   EXPECT_TRUE(ExecuteJavaScript(@"__gCrWeb.passwords.findPasswordForms();"));
+
+  PasswordFormFillData form_data;
+  SetPasswordFormFillData(BaseUrl(), "gChrome~form~0", 0, "u1", 1,
+                          "john.doe@gmail.com", "p1", 2, "super!secret",
+                          nullptr, nullptr, false, &form_data);
+
   __block int call_counter = 0;
   __block int success_counter = 0;
-  [helper_ findAndFillPasswordFormsWithUserName:@"john.doe@gmail.com"
-                                       password:@"super!secret"
-                              completionHandler:^(BOOL complete) {
-                                ++call_counter;
-                                if (complete) {
-                                  ++success_counter;
-                                }
-                              }];
+  [helper_ fillPasswordForm:form_data
+          completionHandler:^(BOOL complete) {
+            ++call_counter;
+            if (complete) {
+              ++success_counter;
+            }
+          }];
   EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
     return call_counter == 1;
   }));
@@ -318,53 +273,9 @@
   EXPECT_NSEQ(@"u1=john.doe@gmail.com;p1=super!secret;", result);
 }
 
-// Tests that multiple forms on the same page are found and filled.
-// This test includes an mock injected failure on form filling to verify
-// that completion handler is called with the proper values.
-TEST_F(PasswordFormHelperTest, FindAndFillMultiplePasswordForms) {
-  // Fails the first call to fill password form.
-  MockJsPasswordManager* mockJsPasswordManager =
-      [[MockJsPasswordManager alloc] init];
-  [mockJsPasswordManager setFillPasswordFormTargetFailureCount:1];
-  [helper_ setJsPasswordManager:mockJsPasswordManager];
-  LoadHtml(
-      @"<form><input id='u1' type='text' name='un1'>"
-       "<input id='p1' type='password' name='pw1'></form>"
-       "<form><input id='u2' type='text' name='un2'>"
-       "<input id='p2' type='password' name='pw2'></form>"
-       "<form><input id='u3' type='text' name='un3'>"
-       "<input id='p3' type='password' name='pw3'></form>");
-  ExecuteJavaScript(@"__gCrWeb.fill.setUpForUniqueIDs(0);");
-  // Run password forms search to set up unique IDs.
-  EXPECT_TRUE(ExecuteJavaScript(@"__gCrWeb.passwords.findPasswordForms();"));
-  __block int call_counter = 0;
-  __block int success_counter = 0;
-  [helper_ findAndFillPasswordFormsWithUserName:@"john.doe@gmail.com"
-                                       password:@"super!secret"
-                              completionHandler:^(BOOL complete) {
-                                ++call_counter;
-                                if (complete) {
-                                  ++success_counter;
-                                }
-                              }];
-  // There should be 3 password forms and only 2 successfully filled forms.
-  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
-    return call_counter == 3;
-  }));
-  EXPECT_EQ(2, success_counter);
-  id result = ExecuteJavaScript(kInputFieldValueVerificationScript);
-  EXPECT_NSEQ(
-      @"u2=john.doe@gmail.com;p2=super!secret;"
-       "u3=john.doe@gmail.com;p3=super!secret;",
-      result);
-}
-
 // Tests that extractPasswordFormData extracts wanted form on page with mutiple
 // forms.
 TEST_F(PasswordFormHelperTest, ExtractPasswordFormData) {
-  MockJsPasswordManager* mockJsPasswordManager =
-      [[MockJsPasswordManager alloc] init];
-  [helper_ setJsPasswordManager:mockJsPasswordManager];
   LoadHtml(@"<form><input id='u1' type='text' name='un1'>"
             "<input id='p1' type='password' name='pw1'></form>"
             "<form><input id='u2' type='text' name='un2'>"
@@ -409,6 +320,66 @@
   EXPECT_EQ(0, success_counter);
 }
 
+// Tests that a form with credentials fillied on user trigger
+// is not autorefilled.
+TEST_F(PasswordFormHelperTest, RefillFormFilledOnUserTrigger) {
+  LoadHtml(@"<form><input id='u1' type='text' name='un1'>"
+            "<input id='p1' type='password' name='pw1'></form>");
+  ExecuteJavaScript(@"__gCrWeb.fill.setUpForUniqueIDs(0);");
+  // Run password forms search to set up unique IDs.
+  EXPECT_TRUE(ExecuteJavaScript(@"__gCrWeb.passwords.findPasswordForms();"));
+
+  // Fill the form on user trigger.
+  const std::string base_url = BaseUrl();
+  FillData fill_data;
+  SetFillData(base_url, 0, 1, "john.doe@gmail.com", 2, "super!secret",
+              &fill_data);
+  __block int call_counter = 0;
+  [helper_ fillPasswordFormWithFillData:fill_data
+                      completionHandler:^(BOOL complete) {
+                        ++call_counter;
+                        EXPECT_TRUE(complete);
+                      }];
+  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
+    return call_counter == 1;
+  }));
+
+  // Try to autofill the form.
+  FormData form_data;
+  SetFormData(base_url, 0, 1, "someacc@store.com", 2, "store!pw", &form_data);
+
+  // Verify that the form has not been refilled.
+  id result = ExecuteJavaScript(kInputFieldValueVerificationScript);
+  EXPECT_NSEQ(@"u1=john.doe@gmail.com;p1=super!secret;", result);
+}
+
+// Tests that a form with credentials typed by user
+// is not autorefilled.
+TEST_F(PasswordFormHelperTest, RefillFormWithUserTypedInput) {
+  LoadHtml(@"<form><input id='u1' type='text' name='un1'>"
+            "<input id='p1' type='password' name='pw1'></form>");
+  ExecuteJavaScript(@"__gCrWeb.fill.setUpForUniqueIDs(0);");
+  // Run password forms search to set up unique IDs.
+  EXPECT_TRUE(ExecuteJavaScript(@"__gCrWeb.passwords.findPasswordForms();"));
+
+  ExecuteJavaScript(
+      @"document.getElementById('u1').value = 'john.doe@gmail.com';");
+  [helper_ updateFieldDataOnUserInput:FieldRendererId(1)
+                           inputValue:@"john.doe@gmail.com"];
+
+  ExecuteJavaScript(@"document.getElementById('p1').value = 'super!secret';");
+  [helper_ updateFieldDataOnUserInput:FieldRendererId(2)
+                           inputValue:@"super!secret"];
+
+  // Try to autofill the form.
+  FormData form_data;
+  SetFormData(BaseUrl(), 0, 1, "someacc@store.com", 2, "store!pw", &form_data);
+
+  // Verify that the form has not been refilled.
+  id result = ExecuteJavaScript(kInputFieldValueVerificationScript);
+  EXPECT_NSEQ(@"u1=john.doe@gmail.com;p1=super!secret;", result);
+}
+
 }  // namespace
 
 NS_ASSUME_NONNULL_END
diff --git a/components/password_manager/ios/test_helpers.cc b/components/password_manager/ios/test_helpers.cc
index b8e06bb..84fdad9 100644
--- a/components/password_manager/ios/test_helpers.cc
+++ b/components/password_manager/ios/test_helpers.cc
@@ -5,11 +5,14 @@
 #include "components/password_manager/ios/test_helpers.h"
 
 #include "base/strings/utf_string_conversions.h"
+#include "components/autofill/core/common/form_data.h"
 #include "components/autofill/core/common/password_form_fill_data.h"
 #include "components/password_manager/ios/account_select_fill_data.h"
 #include "url/gurl.h"
 
 using autofill::FieldRendererId;
+using autofill::FormData;
+using autofill::FormFieldData;
 using autofill::FormRendererId;
 using autofill::PasswordFormFillData;
 using password_manager::FillData;
@@ -70,4 +73,27 @@
   fill_data->password_value = base::UTF8ToUTF16(password_value);
 }
 
+void SetFormData(const std::string& origin,
+                 uint32_t form_id,
+                 uint32_t username_field_id,
+                 const char* username_value,
+                 uint32_t password_field_id,
+                 const char* password_value,
+                 FormData* form_data) {
+  DCHECK(form_data);
+  form_data->url = GURL(origin);
+  form_data->unique_renderer_id = FormRendererId(form_id);
+
+  FormFieldData field;
+  field.value = base::UTF8ToUTF16(username_value);
+  field.form_control_type = "text";
+  field.unique_renderer_id = FieldRendererId(username_field_id);
+  form_data->fields.push_back(field);
+
+  field.value = base::UTF8ToUTF16(password_value);
+  field.form_control_type = "password";
+  field.unique_renderer_id = FieldRendererId(password_field_id);
+  form_data->fields.push_back(field);
+}
+
 }  // namespace  test_helpers
diff --git a/components/password_manager/ios/test_helpers.h b/components/password_manager/ios/test_helpers.h
index 9241363..49d894ac 100644
--- a/components/password_manager/ios/test_helpers.h
+++ b/components/password_manager/ios/test_helpers.h
@@ -8,6 +8,7 @@
 #include <string>
 
 namespace autofill {
+struct FormData;
 struct PasswordFormFillData;
 }  // namespace autofill
 
@@ -41,6 +42,15 @@
                  const char* password_value,
                  password_manager::FillData* fill_data);
 
+// Populates |form_data| with test values.
+void SetFormData(const std::string& origin,
+                 uint32_t unique_renderer_id,
+                 uint32_t username_field_id,
+                 const char* username_value,
+                 uint32_t password_field_id,
+                 const char* password_value,
+                 autofill::FormData* form_data);
+
 }  // namespace test_helpers
 
 #endif  // COMPONENTS_PASSWORD_MANAGER_IOS_TEST_HELPERS_H_
diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelper.java b/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelper.java
index e8bd598..b035c03 100644
--- a/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelper.java
+++ b/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelper.java
@@ -72,6 +72,7 @@
     public static final String EXTRA_RESPONSE_PAYER_NAME = "payerName";
     public static final String EXTRA_RESPONSE_PAYER_EMAIL = "payerEmail";
     public static final String EXTRA_RESPONSE_PAYER_PHONE = "payerPhone";
+    public static final String EXTRA_SHIPPING_OPTION_ID = "shippingOptionId";
 
     // Shipping address bundle used in payment response and shippingAddressChange.
     public static final String EXTRA_SHIPPING_ADDRESS = "shippingAddress";
@@ -183,7 +184,7 @@
         }
 
         String selectedShippingOptionId = requestedPaymentOptions.requestShipping
-                ? getStringOrEmpty(data, PaymentShippingOption.EXTRA_SHIPPING_OPTION_ID)
+                ? getStringOrEmpty(data, EXTRA_SHIPPING_OPTION_ID)
                 : "";
         if (requestedPaymentOptions.requestShipping
                 && TextUtils.isEmpty(selectedShippingOptionId)) {
diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelperType.java b/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelperType.java
index 028bbb0..8ac1a1d87 100644
--- a/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelperType.java
+++ b/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelperType.java
@@ -180,23 +180,20 @@
 
     /** The class that mirrors mojom.PaymentShippingOption. */
     public static final class PaymentShippingOption {
-        public static final String EXTRA_SHIPPING_OPTION_ID = "shippingOptionId";
+        public static final String EXTRA_SHIPPING_OPTION_ID = "id";
         public static final String EXTRA_SHIPPING_OPTION_LABEL = "label";
+        public static final String EXTRA_SHIPPING_OPTION_AMOUNT = "amount";
         public static final String EXTRA_SHIPPING_OPTION_SELECTED = "selected";
-        public static final String EXTRA_SHIPPING_OPTION_AMOUNT_CURRENCY = "amountCurrency";
-        public static final String EXTRA_SHIPPING_OPTION_AMOUNT_VALUE = "amountValue";
 
         public final String id;
         public final String label;
-        public final String amountCurrency;
-        public final String amountValue;
+        public final PaymentCurrencyAmount amount;
         public final boolean selected;
-        public PaymentShippingOption(String id, String label, String amountCurrency,
-                String amountValue, boolean selected) {
+        public PaymentShippingOption(
+                String id, String label, PaymentCurrencyAmount amount, boolean selected) {
             this.id = id;
             this.label = label;
-            this.amountCurrency = amountCurrency;
-            this.amountValue = amountValue;
+            this.amount = amount;
             this.selected = selected;
         }
 
@@ -204,8 +201,7 @@
             Bundle bundle = new Bundle();
             bundle.putString(EXTRA_SHIPPING_OPTION_ID, id);
             bundle.putString(EXTRA_SHIPPING_OPTION_LABEL, label);
-            bundle.putString(EXTRA_SHIPPING_OPTION_AMOUNT_CURRENCY, amountCurrency);
-            bundle.putString(EXTRA_SHIPPING_OPTION_AMOUNT_VALUE, amountValue);
+            bundle.putBundle(EXTRA_SHIPPING_OPTION_AMOUNT, amount.asBundle());
             bundle.putBoolean(EXTRA_SHIPPING_OPTION_SELECTED, selected);
             return bundle;
         }
diff --git a/components/payments/content/manifest_verifier.cc b/components/payments/content/manifest_verifier.cc
index 9529b48a..5800ab98 100644
--- a/components/payments/content/manifest_verifier.cc
+++ b/components/payments/content/manifest_verifier.cc
@@ -32,7 +32,7 @@
 void EnableMethodManifestUrlForSupportedApps(
     const GURL& method_manifest_url,
     const std::vector<std::string>& supported_origin_strings,
-    content::PaymentAppProvider::PaymentApps* apps,
+    content::InstalledPaymentAppsFinder::PaymentApps* apps,
     std::vector<int64_t> app_ids,
     std::map<GURL, std::set<GURL>>* prohibited_payment_methods) {
   for (auto app_id : app_ids) {
@@ -68,9 +68,10 @@
   }
 }
 
-void ManifestVerifier::Verify(content::PaymentAppProvider::PaymentApps apps,
-                              VerifyCallback finished_verification,
-                              base::OnceClosure finished_using_resources) {
+void ManifestVerifier::Verify(
+    content::InstalledPaymentAppsFinder::PaymentApps apps,
+    VerifyCallback finished_verification,
+    base::OnceClosure finished_using_resources) {
   DCHECK(apps_.empty());
   DCHECK(finished_verification_callback_.is_null());
   DCHECK(finished_using_resources_callback_.is_null());
diff --git a/components/payments/content/manifest_verifier.h b/components/payments/content/manifest_verifier.h
index 6e1207e..0974571 100644
--- a/components/payments/content/manifest_verifier.h
+++ b/components/payments/content/manifest_verifier.h
@@ -18,6 +18,7 @@
 #include "components/payments/content/developer_console_logger.h"
 #include "components/webdata/common/web_data_service_base.h"
 #include "components/webdata/common/web_data_service_consumer.h"
+#include "content/public/browser/installed_payment_apps_finder.h"
 #include "content/public/browser/payment_app_provider.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "url/origin.h"
@@ -59,7 +60,7 @@
   // remaining. If a payment handler does not have any valid payment method
   // names, then it's not included in the returned set of handlers.
   using VerifyCallback =
-      base::OnceCallback<void(content::PaymentAppProvider::PaymentApps,
+      base::OnceCallback<void(content::InstalledPaymentAppsFinder::PaymentApps,
                               const std::string& error_message)>;
 
   // Creates the verifier and starts up the parser utility process.
@@ -81,7 +82,7 @@
 
   // Initiates the verification. This object should be deleted after
   // |finished_using_resources| is invoked.
-  void Verify(content::PaymentAppProvider::PaymentApps apps,
+  void Verify(content::InstalledPaymentAppsFinder::PaymentApps apps,
               VerifyCallback finished_verification,
               base::OnceClosure finished_using_resources);
 
@@ -121,7 +122,7 @@
   PaymentManifestWebDataService* cache_;
 
   // The list of payment apps being verified.
-  content::PaymentAppProvider::PaymentApps apps_;
+  content::InstalledPaymentAppsFinder::PaymentApps apps_;
 
   // A mapping from the payment app scope to the set of the URL-based payment
   // methods that it claims to support, but is not allowed due to the payment
diff --git a/components/payments/content/service_worker_payment_app_factory.cc b/components/payments/content/service_worker_payment_app_factory.cc
index 5b710d3..0426e8e 100644
--- a/components/payments/content/service_worker_payment_app_factory.cc
+++ b/components/payments/content/service_worker_payment_app_factory.cc
@@ -39,7 +39,7 @@
   ~ServiceWorkerPaymentAppCreator() {}
 
   void CreatePaymentApps(
-      content::PaymentAppProvider::PaymentApps apps,
+      content::InstalledPaymentAppsFinder::PaymentApps apps,
       ServiceWorkerPaymentAppFinder::InstallablePaymentApps installable_apps,
       const std::string& error_message) {
     if (!delegate_) {
diff --git a/components/payments/content/service_worker_payment_app_finder.cc b/components/payments/content/service_worker_payment_app_finder.cc
index 87b33d0..ed22125 100644
--- a/components/payments/content/service_worker_payment_app_finder.cc
+++ b/components/payments/content/service_worker_payment_app_finder.cc
@@ -84,7 +84,7 @@
 }
 
 void RemovePortNumbersFromScopesForTest(
-    content::PaymentAppProvider::PaymentApps* apps) {
+    content::InstalledPaymentAppsFinder::PaymentApps* apps) {
   GURL::Replacements replacements;
   replacements.ClearPort();
   for (auto& app : *apps) {
@@ -160,9 +160,9 @@
     finished_using_resources_callback_ =
         std::move(finished_using_resources_callback);
 
-    content::PaymentAppProvider::GetInstance()->GetAllPaymentApps(
-        web_contents->GetBrowserContext(),
-        base::BindOnce(
+    content::InstalledPaymentAppsFinder::GetInstance(
+        web_contents->GetBrowserContext())
+        ->GetAllPaymentApps(base::BindOnce(
             &SelfDeletingServiceWorkerPaymentAppFinder::OnGotAllPaymentApps,
             weak_ptr_factory_.GetWeakPtr()));
   }
@@ -179,7 +179,7 @@
 
   static void RemoveUnrequestedMethods(
       const std::vector<mojom::PaymentMethodDataPtr>& requested_method_data,
-      content::PaymentAppProvider::PaymentApps* apps) {
+      content::InstalledPaymentAppsFinder::PaymentApps* apps) {
     std::set<std::string> requested_methods;
     for (const auto& requested_method_datum : requested_method_data) {
       requested_methods.insert(requested_method_datum->supported_method);
@@ -193,7 +193,8 @@
     }
   }
 
-  void OnGotAllPaymentApps(content::PaymentAppProvider::PaymentApps apps) {
+  void OnGotAllPaymentApps(
+      content::InstalledPaymentAppsFinder::PaymentApps apps) {
     if (ignore_port_in_origin_comparison_for_testing_)
       RemovePortNumbersFromScopesForTest(&apps);
 
@@ -220,8 +221,9 @@
                        weak_ptr_factory_.GetWeakPtr()));
   }
 
-  void OnPaymentAppsVerified(content::PaymentAppProvider::PaymentApps apps,
-                             const std::string& error_message) {
+  void OnPaymentAppsVerified(
+      content::InstalledPaymentAppsFinder::PaymentApps apps,
+      const std::string& error_message) {
     if (first_error_message_.empty())
       first_error_message_ = error_message;
 
@@ -382,7 +384,7 @@
 
   bool ignore_port_in_origin_comparison_for_testing_ = false;
 
-  content::PaymentAppProvider::PaymentApps installed_apps_;
+  content::InstalledPaymentAppsFinder::PaymentApps installed_apps_;
 
   size_t number_of_app_icons_to_update_ = 0;
 
@@ -410,7 +412,7 @@
                 });
   if (requested_method_data.empty()) {
     std::move(callback).Run(
-        content::PaymentAppProvider::PaymentApps(),
+        content::InstalledPaymentAppsFinder::PaymentApps(),
         std::map<GURL, std::unique_ptr<WebAppInstallationInfo>>(),
         /*error_message=*/"");
     return;
@@ -444,7 +446,7 @@
 // static
 void ServiceWorkerPaymentAppFinder::RemoveAppsWithoutMatchingMethodData(
     const std::vector<mojom::PaymentMethodDataPtr>& requested_method_data,
-    content::PaymentAppProvider::PaymentApps* apps) {
+    content::InstalledPaymentAppsFinder::PaymentApps* apps) {
   for (auto it = apps->begin(); it != apps->end();) {
     if (AppSupportsAtLeastOneRequestedMethodData(*it->second,
                                                  requested_method_data)) {
diff --git a/components/payments/content/service_worker_payment_app_finder.h b/components/payments/content/service_worker_payment_app_finder.h
index 05a73e7..0f8b632f8 100644
--- a/components/payments/content/service_worker_payment_app_finder.h
+++ b/components/payments/content/service_worker_payment_app_finder.h
@@ -13,6 +13,7 @@
 #include "base/callback.h"
 #include "base/macros.h"
 #include "components/payments/content/web_app_manifest.h"
+#include "content/public/browser/installed_payment_apps_finder.h"
 #include "content/public/browser/payment_app_provider.h"
 #include "content/public/browser/render_document_host_user_data.h"
 #include "third_party/blink/public/mojom/payments/payment_request.mojom.h"
@@ -43,7 +44,7 @@
   using InstallablePaymentApps =
       std::map<GURL, std::unique_ptr<WebAppInstallationInfo>>;
   using GetAllPaymentAppsCallback =
-      base::OnceCallback<void(content::PaymentAppProvider::PaymentApps,
+      base::OnceCallback<void(content::InstalledPaymentAppsFinder::PaymentApps,
                               InstallablePaymentApps,
                               const std::string& error_message)>;
 
@@ -78,7 +79,7 @@
   // the method names and method-specific capabilities.
   static void RemoveAppsWithoutMatchingMethodData(
       const std::vector<mojom::PaymentMethodDataPtr>& requested_method_data,
-      content::PaymentAppProvider::PaymentApps* apps);
+      content::InstalledPaymentAppsFinder::PaymentApps* apps);
 
   // Ignore the given |method|, so that no installed or installable service
   // workers would ever be looked up in GetAllPaymentApps(). Calling this
diff --git a/components/payments/content/service_worker_payment_app_finder_unittest.cc b/components/payments/content/service_worker_payment_app_finder_unittest.cc
index 32ab275..feb1845 100644
--- a/components/payments/content/service_worker_payment_app_finder_unittest.cc
+++ b/components/payments/content/service_worker_payment_app_finder_unittest.cc
@@ -13,7 +13,7 @@
  protected:
   void RemoveAppsWithoutMatchingMethodData(
       const std::vector<mojom::PaymentMethodDataPtr>& requested_method_data,
-      content::PaymentAppProvider::PaymentApps* apps) {
+      content::InstalledPaymentAppsFinder::PaymentApps* apps) {
     ServiceWorkerPaymentAppFinder::RemoveAppsWithoutMatchingMethodData(
         requested_method_data, apps);
   }
@@ -24,7 +24,7 @@
   std::vector<mojom::PaymentMethodDataPtr> requested_methods;
   requested_methods.emplace_back(mojom::PaymentMethodData::New());
   requested_methods.back()->supported_method = "method";
-  content::PaymentAppProvider::PaymentApps no_apps;
+  content::InstalledPaymentAppsFinder::PaymentApps no_apps;
 
   RemoveAppsWithoutMatchingMethodData(requested_methods, &no_apps);
 
@@ -34,7 +34,7 @@
 TEST_F(ServiceWorkerPaymentAppFinderTest,
        RemoveAppsWithoutMatchingMethodData_NoMethods) {
   std::vector<mojom::PaymentMethodDataPtr> no_requested_methods;
-  content::PaymentAppProvider::PaymentApps apps;
+  content::InstalledPaymentAppsFinder::PaymentApps apps;
   apps[0] = std::make_unique<content::StoredPaymentApp>();
   apps[0]->enabled_methods = {"method1", "method2"};
 
@@ -52,7 +52,7 @@
   requested_methods.back()->supported_method = "method2";
   requested_methods.emplace_back(mojom::PaymentMethodData::New());
   requested_methods.back()->supported_method = "method3";
-  content::PaymentAppProvider::PaymentApps apps;
+  content::InstalledPaymentAppsFinder::PaymentApps apps;
   apps[0] = std::make_unique<content::StoredPaymentApp>();
   apps[0]->enabled_methods = {"method2"};
   apps[1] = std::make_unique<content::StoredPaymentApp>();
@@ -76,7 +76,7 @@
   std::vector<mojom::PaymentMethodDataPtr> requested_methods;
   requested_methods.emplace_back(mojom::PaymentMethodData::New());
   requested_methods.back()->supported_method = "basic-card";
-  content::PaymentAppProvider::PaymentApps apps;
+  content::InstalledPaymentAppsFinder::PaymentApps apps;
   apps[0] = std::make_unique<content::StoredPaymentApp>();
   apps[0]->enabled_methods = {"basic-card"};
 
@@ -95,7 +95,7 @@
   requested_methods.back()->supported_method = "basic-card";
   requested_methods.back()->supported_networks = {
       mojom::BasicCardNetwork::AMEX};
-  content::PaymentAppProvider::PaymentApps apps;
+  content::InstalledPaymentAppsFinder::PaymentApps apps;
   apps[0] = std::make_unique<content::StoredPaymentApp>();
   apps[0]->enabled_methods = {"basic-card"};
 
@@ -111,7 +111,7 @@
   requested_methods.back()->supported_method = "basic-card";
   requested_methods.back()->supported_networks = {
       mojom::BasicCardNetwork::AMEX};
-  content::PaymentAppProvider::PaymentApps apps;
+  content::InstalledPaymentAppsFinder::PaymentApps apps;
   apps[0] = std::make_unique<content::StoredPaymentApp>();
   apps[0]->enabled_methods = {"basic-card"};
   apps[0]->capabilities.emplace_back();
@@ -128,7 +128,7 @@
   std::vector<mojom::PaymentMethodDataPtr> requested_methods;
   requested_methods.emplace_back(mojom::PaymentMethodData::New());
   requested_methods.back()->supported_method = "basic-card";
-  content::PaymentAppProvider::PaymentApps apps;
+  content::InstalledPaymentAppsFinder::PaymentApps apps;
   apps[0] = std::make_unique<content::StoredPaymentApp>();
   apps[0]->enabled_methods = {"basic-card"};
   apps[0]->capabilities.emplace_back();
@@ -155,7 +155,7 @@
   requested_methods.back()->supported_method = "basic-card";
   requested_methods.back()->supported_networks = {
       mojom::BasicCardNetwork::AMEX, mojom::BasicCardNetwork::DINERS};
-  content::PaymentAppProvider::PaymentApps apps;
+  content::InstalledPaymentAppsFinder::PaymentApps apps;
   apps[0] = std::make_unique<content::StoredPaymentApp>();
   apps[0]->enabled_methods = {"basic-card"};
   apps[0]->capabilities.emplace_back();
@@ -182,7 +182,7 @@
   std::vector<mojom::PaymentMethodDataPtr> requested_methods;
   requested_methods.emplace_back(mojom::PaymentMethodData::New());
   requested_methods.back()->supported_method = "unknown-method";
-  content::PaymentAppProvider::PaymentApps apps;
+  content::InstalledPaymentAppsFinder::PaymentApps apps;
   apps[0] = std::make_unique<content::StoredPaymentApp>();
   apps[0]->enabled_methods = {"unknown-method"};
   apps[0]->capabilities.emplace_back();
diff --git a/components/performance_manager/service_worker_context_adapter.cc b/components/performance_manager/service_worker_context_adapter.cc
index 354a417..19cf551 100644
--- a/components/performance_manager/service_worker_context_adapter.cc
+++ b/components/performance_manager/service_worker_context_adapter.cc
@@ -133,7 +133,7 @@
   NOTIMPLEMENTED();
 }
 
-void ServiceWorkerContextAdapter::DeleteForOrigin(const GURL& origin_url,
+void ServiceWorkerContextAdapter::DeleteForOrigin(const url::Origin& origin_url,
                                                   ResultCallback callback) {
   NOTIMPLEMENTED();
 }
diff --git a/components/performance_manager/service_worker_context_adapter.h b/components/performance_manager/service_worker_context_adapter.h
index bca2cd42..de9cdb9 100644
--- a/components/performance_manager/service_worker_context_adapter.h
+++ b/components/performance_manager/service_worker_context_adapter.h
@@ -66,7 +66,7 @@
       base::Optional<std::string> host_filter,
       GetInstalledRegistrationOriginsCallback callback) override;
   void GetAllOriginsInfo(GetUsageInfoCallback callback) override;
-  void DeleteForOrigin(const GURL& origin_url,
+  void DeleteForOrigin(const url::Origin& origin_url,
                        ResultCallback callback) override;
   void PerformStorageCleanup(base::OnceClosure callback) override;
   void CheckHasServiceWorker(const GURL& url,
diff --git a/components/permissions/permission_context_base.h b/components/permissions/permission_context_base.h
index f13d57d..992aca4 100644
--- a/components/permissions/permission_context_base.h
+++ b/components/permissions/permission_context_base.h
@@ -16,7 +16,7 @@
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/permissions/permission_request.h"
 #include "components/permissions/permission_result.h"
-#include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom-forward.h"
+#include "third_party/blink/public/mojom/feature_policy/feature_policy_feature.mojom-forward.h"
 
 class GURL;
 
diff --git a/components/policy/core/common/cloud/cloud_policy_client.cc b/components/policy/core/common/cloud/cloud_policy_client.cc
index 4a7fb073..3255332 100644
--- a/components/policy/core/common/cloud/cloud_policy_client.cc
+++ b/components/policy/core/common/cloud/cloud_policy_client.cc
@@ -460,7 +460,7 @@
 
   request->set_device_type(device_type);
 
-  policy_fetch_request_job_ = service_->CreateJob(std::move(config));
+  request_jobs_.push_back(service_->CreateJob(std::move(config)));
 }
 
 void CloudPolicyClient::Unregister() {
@@ -1037,6 +1037,10 @@
     DeviceManagementStatus status,
     int net_error,
     const em::DeviceManagementResponse& response) {
+  // Remove the job before executing the callback because |this| might be
+  // deleted during the callback.
+  RemoveJob(job);
+
   if (status == DM_STATUS_SUCCESS &&
       (!response.has_service_api_access_response())) {
     LOG(WARNING) << "Invalid service api access response.";
@@ -1051,6 +1055,7 @@
   } else {
     std::move(callback).Run(status, std::string());
   }
+  // |this| might be deleted at this point.
 }
 
 void CloudPolicyClient::OnPolicyFetchCompleted(
diff --git a/components/policy/core/common/cloud/cloud_policy_client_unittest.cc b/components/policy/core/common/cloud/cloud_policy_client_unittest.cc
index 3e523c6..399ab73 100644
--- a/components/policy/core/common/cloud/cloud_policy_client_unittest.cc
+++ b/components/policy/core/common/cloud/cloud_policy_client_unittest.cc
@@ -30,6 +30,7 @@
 #include "components/policy/core/common/cloud/realtime_reporting_job_configuration.h"
 #include "components/policy/proto/device_management_backend.pb.h"
 #include "components/version_info/version_info.h"
+#include "google_apis/gaia/gaia_urls.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
 #include "services/network/test/test_url_loader_factory.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -92,6 +93,8 @@
 const char kPolicyToken[] = "fake-policy-token";
 const char kPolicyName[] = "fake-policy-name";
 const char kValueValidationMessage[] = "fake-value-validation-message";
+const char kRobotAuthCode[] = "fake-robot-auth-code";
+const char kApiAuthScope[] = "fake-api-auth-scope";
 
 const int64_t kAgeOfCommand = 123123123;
 const int64_t kLastCommandId = 123456789;
@@ -104,7 +107,7 @@
 // A mock class to allow us to set expectations on upload callbacks.
 class MockStatusCallbackObserver {
  public:
-  MockStatusCallbackObserver() {}
+  MockStatusCallbackObserver() = default;
 
   MOCK_METHOD1(OnCallbackComplete, void(bool));
 };
@@ -113,7 +116,7 @@
 // callbacks.
 class MockRemoteCommandsObserver {
  public:
-  MockRemoteCommandsObserver() {}
+  MockRemoteCommandsObserver() = default;
 
   MOCK_METHOD3(OnRemoteCommandsFetched,
                void(DeviceManagementStatus,
@@ -123,12 +126,20 @@
 
 class MockDeviceDMTokenCallbackObserver {
  public:
-  MockDeviceDMTokenCallbackObserver() {}
+  MockDeviceDMTokenCallbackObserver() = default;
 
   MOCK_METHOD1(OnDeviceDMTokenRequested,
                std::string(const std::vector<std::string>&));
 };
 
+class MockRobotAuthCodeCallbackObserver {
+ public:
+  MockRobotAuthCodeCallbackObserver() = default;
+
+  MOCK_METHOD2(OnRobotAuthCodeFetched,
+               void(DeviceManagementStatus, const std::string&));
+};
+
 }  // namespace
 
 class CloudPolicyClientTest : public testing::Test {
@@ -290,6 +301,16 @@
             VALUE_VALIDATION_ISSUE_SEVERITY_WARNING);
     policy_value_validation_issue->set_debug_message(kValueValidationMessage);
 
+    em::DeviceServiceApiAccessRequest* api_request =
+        robot_auth_code_fetch_request_.mutable_service_api_access_request();
+    api_request->set_oauth2_client_id(
+        GaiaUrls::GetInstance()->oauth2_chrome_client_id());
+    api_request->add_auth_scopes(kApiAuthScope);
+    api_request->set_device_type(em::DeviceServiceApiAccessRequest::CHROME_OS);
+    em::DeviceServiceApiAccessResponse* api_response =
+        robot_auth_code_fetch_response_.mutable_service_api_access_response();
+    api_response->set_auth_code(kRobotAuthCode);
+
 #if defined(OS_CHROMEOS)
     fake_statistics_provider_.SetMachineStatistic(
         chromeos::system::kSerialNumberKeyForTest, "fake_serial_number");
@@ -532,6 +553,16 @@
                                    gcm_id_update_response_)));
   }
 
+  void ExpectRobotAuthCodeFetch() {
+    EXPECT_CALL(service_, StartJob(_))
+        .WillOnce(DoAll(
+            service_.CaptureJobType(&job_type_),
+            service_.CaptureQueryParams(&query_params_),
+            service_.CaptureRequest(&job_request_),
+            service_.StartJobAsync(net::OK, DeviceManagementService::kSuccess,
+                                   robot_auth_code_fetch_response_)));
+  }
+
   void CheckPolicyResponse() {
     ASSERT_TRUE(client_->GetPolicyFor(policy_type_, std::string()));
     EXPECT_THAT(*client_->GetPolicyFor(policy_type_, std::string()),
@@ -563,6 +594,7 @@
   em::DeviceManagementRequest attribute_update_request_;
   em::DeviceManagementRequest gcm_id_update_request_;
   em::DeviceManagementRequest upload_policy_validation_report_request_;
+  em::DeviceManagementRequest robot_auth_code_fetch_request_;
 
   // Protobufs used in successful responses.
   em::DeviceManagementResponse registration_response_;
@@ -577,6 +609,7 @@
   em::DeviceManagementResponse attribute_update_response_;
   em::DeviceManagementResponse gcm_id_update_response_;
   em::DeviceManagementResponse upload_policy_validation_report_response_;
+  em::DeviceManagementResponse robot_auth_code_fetch_response_;
 
   base::test::SingleThreadTaskEnvironment task_environment_;
   DeviceManagementService::JobConfiguration::JobType job_type_;
@@ -590,6 +623,8 @@
   StrictMock<MockStatusCallbackObserver> callback_observer_;
   StrictMock<MockDeviceDMTokenCallbackObserver>
       device_dmtoken_callback_observer_;
+  StrictMock<MockRobotAuthCodeCallbackObserver>
+      robot_auth_code_callback_observer_;
   FakeSigningService fake_signing_service_;
   std::unique_ptr<CloudPolicyClient> client_;
   network::TestURLLoaderFactory url_loader_factory_;
@@ -2083,6 +2118,67 @@
   EXPECT_EQ(DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID, client_->status());
 }
 
+TEST_F(CloudPolicyClientTest, RequestFetchRobotAuthCodes) {
+  RegisterClient();
+  ExpectRobotAuthCodeFetch();
+  EXPECT_CALL(robot_auth_code_callback_observer_,
+              OnRobotAuthCodeFetched(_, kRobotAuthCode));
+
+  em::DeviceServiceApiAccessRequest::DeviceType device_type =
+      em::DeviceServiceApiAccessRequest::CHROME_OS;
+  std::set<std::string> oauth_scopes = {kApiAuthScope};
+  client_->FetchRobotAuthCodes(
+      DMAuth::FromDMToken(kDMToken), device_type, oauth_scopes,
+      base::BindOnce(&MockRobotAuthCodeCallbackObserver::OnRobotAuthCodeFetched,
+                     base::Unretained(&robot_auth_code_callback_observer_)));
+
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_API_AUTH_CODE_FETCH,
+            job_type_);
+  EXPECT_EQ(robot_auth_code_fetch_request_.SerializePartialAsString(),
+            job_request_.SerializePartialAsString());
+  EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest,
+       RequestFetchRobotAuthCodesNotInterruptedByPolicyFetch) {
+  // Expect a robot auth code fetch request that never runs its callback to
+  // simulate something happening while we wait for the request to return.
+  DeviceManagementService::JobControl* robot_job = nullptr;
+  DeviceManagementService::JobConfiguration::JobType robot_job_type;
+  EXPECT_CALL(service_, StartJob(_))
+      .WillOnce(DoAll(service_.StartJobFullControl(&robot_job),
+                      service_.CaptureJobType(&robot_job_type)));
+
+  RegisterClient();
+  EXPECT_CALL(robot_auth_code_callback_observer_,
+              OnRobotAuthCodeFetched(_, kRobotAuthCode));
+
+  em::DeviceServiceApiAccessRequest::DeviceType device_type =
+      em::DeviceServiceApiAccessRequest::CHROME_OS;
+  std::set<std::string> oauth_scopes = {kApiAuthScope};
+  client_->FetchRobotAuthCodes(
+      DMAuth::FromDMToken(kDMToken), device_type, oauth_scopes,
+      base::BindOnce(&MockRobotAuthCodeCallbackObserver::OnRobotAuthCodeFetched,
+                     base::Unretained(&robot_auth_code_callback_observer_)));
+  base::RunLoop().RunUntilIdle();
+
+  ExpectPolicyFetch(kDMToken);
+  EXPECT_CALL(observer_, OnPolicyFetched(_));
+
+  client_->FetchPolicy();
+  base::RunLoop().RunUntilIdle();
+
+  // Try to manually finish the robot auth code fetch job.
+  service_.DoURLCompletion(&robot_job, net::OK,
+                           DeviceManagementService::kSuccess,
+                           robot_auth_code_fetch_response_);
+
+  EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_API_AUTH_CODE_FETCH,
+            robot_job_type);
+  EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+            job_type_);
+}
 class MockClientCertProvisioningStartCsrCallbackObserver {
  public:
   MockClientCertProvisioningStartCsrCallbackObserver() = default;
diff --git a/components/policy/core/common/policy_map_unittest.cc b/components/policy/core/common/policy_map_unittest.cc
index 97d959c..c60e297 100644
--- a/components/policy/core/common/policy_map_unittest.cc
+++ b/components/policy/core/common/policy_map_unittest.cc
@@ -736,7 +736,7 @@
       POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
       POLICY_SOURCE_ENTERPRISE_DEFAULT, base::Value(ef), nullptr));
 
-  // Case 2 - policy::key::kExtensionInstallBlacklist
+  // Case 2 - policy::key::kExtensionInstallBlocklist
   // This policy is part of the atomic group "Extensions" and has the highest
   // source in its group, its value should remain the same.
   PolicyMap::Entry cloud_machine_mandatory(
@@ -749,7 +749,7 @@
 
   // Case 3 - policy::key::kExtensionInstallAllowlist
   // This policy is part of the atomic group "Extensions" and has a lower
-  // source than policy::key::kExtensionInstallBlacklist from the same group,
+  // source than policy::key::kExtensionInstallBlocklist from the same group,
   // its value should be ignored.
   PolicyMap::Entry ad_machine_mandatory(
       POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
@@ -757,7 +757,7 @@
   auto ad_machine_mandatory_ignored = ad_machine_mandatory.DeepCopy();
   ad_machine_mandatory_ignored.SetIgnoredByPolicyAtomicGroup();
 
-  // Case 4 - policy::key::kExtensionInstallBlacklist
+  // Case 4 - policy::key::kExtensionInstallBlocklist
   // This policy is part of the atomic group "Extensions" and has the highest
   // source in its group, its value should remain the same.
   PolicyMap::Entry cloud_machine_recommended(
@@ -766,7 +766,7 @@
 
   PolicyMap policy_not_merged;
   policy_not_merged.Set(kTestPolicyName1, platform_user_mandatory.DeepCopy());
-  policy_not_merged.Set(policy::key::kExtensionInstallBlacklist,
+  policy_not_merged.Set(policy::key::kExtensionInstallBlocklist,
                         cloud_machine_mandatory.DeepCopy());
   policy_not_merged.Set(policy::key::kExtensionInstallAllowlist,
                         ad_machine_mandatory.DeepCopy());
@@ -781,7 +781,7 @@
   PolicyMap expected_group_merged;
   expected_group_merged.Set(kTestPolicyName1,
                             platform_user_mandatory.DeepCopy());
-  expected_group_merged.Set(policy::key::kExtensionInstallBlacklist,
+  expected_group_merged.Set(policy::key::kExtensionInstallBlocklist,
                             cloud_machine_mandatory.DeepCopy());
   expected_group_merged.Set(policy::key::kExtensionInstallAllowlist,
                             ad_machine_mandatory_ignored.DeepCopy());
diff --git a/components/policy/core/common/policy_service_impl.cc b/components/policy/core/common/policy_service_impl.cc
index 776735b6..81dad68d 100644
--- a/components/policy/core/common/policy_service_impl.cc
+++ b/components/policy/core/common/policy_service_impl.cc
@@ -296,6 +296,7 @@
   if (value && value->GetBool()) {
     policy_lists_to_merge.insert(key::kExtensionInstallForcelist);
     policy_lists_to_merge.insert(key::kExtensionInstallBlacklist);
+    policy_lists_to_merge.insert(key::kExtensionInstallBlocklist);
     policy_lists_to_merge.insert(key::kExtensionInstallWhitelist);
     policy_lists_to_merge.insert(key::kExtensionInstallAllowlist);
   }
diff --git a/components/policy/core/common/policy_service_impl_unittest.cc b/components/policy/core/common/policy_service_impl_unittest.cc
index 6f9e5f9..c386689 100644
--- a/components/policy/core/common/policy_service_impl_unittest.cc
+++ b/components/policy/core/common/policy_service_impl_unittest.cc
@@ -1036,7 +1036,7 @@
 
   std::unique_ptr<base::ListValue> policy = std::make_unique<base::ListValue>();
   policy->Append(base::Value(policy::key::kExtensionInstallForcelist));
-  policy->Append(base::Value(policy::key::kExtensionInstallBlacklist));
+  policy->Append(base::Value(policy::key::kExtensionInstallBlocklist));
 
   auto policy_bundle1 = std::make_unique<PolicyBundle>();
   PolicyMap& policy_map1 = policy_bundle1->Get(chrome_namespace);
@@ -1047,7 +1047,7 @@
                                 POLICY_SOURCE_PLATFORM, std::move(list1),
                                 nullptr);
   policy_map1.Set(key::kExtensionInstallForcelist, entry_list_1.DeepCopy());
-  policy_map1.Set(key::kExtensionInstallBlacklist, entry_list_1.DeepCopy());
+  policy_map1.Set(key::kExtensionInstallBlocklist, entry_list_1.DeepCopy());
   PolicyMap::Entry atomic_policy_enabled(POLICY_LEVEL_MANDATORY,
                                          POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
                                          base::Value(true), nullptr);
@@ -1061,7 +1061,7 @@
   PolicyMap::Entry entry_list_3(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
                                 POLICY_SOURCE_CLOUD, std::move(list3), nullptr);
   policy_map2.Set(key::kExtensionInstallForcelist, entry_list_2.DeepCopy());
-  policy_map2.Set(key::kExtensionInstallBlacklist, entry_list_2.DeepCopy());
+  policy_map2.Set(key::kExtensionInstallBlocklist, entry_list_2.DeepCopy());
   policy_map2.Set(key::kExtensionInstallAllowlist, entry_list_3.DeepCopy());
 
   PolicyMap expected_chrome;
@@ -1077,7 +1077,7 @@
   merged.AddConflictingPolicy(entry_list_1.DeepCopy());
   merged.AddWarning(IDS_POLICY_CONFLICT_DIFF_VALUE);
   expected_chrome.Set(key::kExtensionInstallForcelist, merged.DeepCopy());
-  expected_chrome.Set(key::kExtensionInstallBlacklist, std::move(merged));
+  expected_chrome.Set(key::kExtensionInstallBlocklist, std::move(merged));
   expected_chrome.Set(key::kExtensionInstallAllowlist, std::move(entry_list_3));
   expected_chrome.Set(key::kPolicyAtomicGroupsEnabled,
                       atomic_policy_enabled.DeepCopy());
@@ -1104,7 +1104,7 @@
 
   std::unique_ptr<base::ListValue> policy = std::make_unique<base::ListValue>();
   policy->Append(base::Value(policy::key::kExtensionInstallForcelist));
-  policy->Append(base::Value(policy::key::kExtensionInstallBlacklist));
+  policy->Append(base::Value(policy::key::kExtensionInstallBlocklist));
 
   auto policy_bundle1 = std::make_unique<PolicyBundle>();
   PolicyMap& policy_map1 = policy_bundle1->Get(chrome_namespace);
@@ -1115,7 +1115,7 @@
                                 POLICY_SOURCE_PLATFORM, std::move(list1),
                                 nullptr);
   policy_map1.Set(key::kExtensionInstallForcelist, entry_list_1.DeepCopy());
-  policy_map1.Set(key::kExtensionInstallBlacklist, entry_list_1.DeepCopy());
+  policy_map1.Set(key::kExtensionInstallBlocklist, entry_list_1.DeepCopy());
   PolicyMap::Entry atomic_policy_enabled(
       POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, POLICY_SOURCE_PLATFORM,
       base::Value(true), nullptr);
@@ -1129,7 +1129,7 @@
   PolicyMap::Entry entry_list_3(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
                                 POLICY_SOURCE_CLOUD, std::move(list3), nullptr);
   policy_map2.Set(key::kExtensionInstallForcelist, entry_list_2.DeepCopy());
-  policy_map2.Set(key::kExtensionInstallBlacklist, entry_list_2.DeepCopy());
+  policy_map2.Set(key::kExtensionInstallBlocklist, entry_list_2.DeepCopy());
   policy_map2.Set(key::kExtensionInstallAllowlist, entry_list_3.DeepCopy());
 
   PolicyMap expected_chrome;
@@ -1146,7 +1146,7 @@
   merged.AddWarning(IDS_POLICY_CONFLICT_DIFF_VALUE);
   entry_list_3.SetIgnoredByPolicyAtomicGroup();
   expected_chrome.Set(key::kExtensionInstallForcelist, merged.DeepCopy());
-  expected_chrome.Set(key::kExtensionInstallBlacklist, std::move(merged));
+  expected_chrome.Set(key::kExtensionInstallBlocklist, std::move(merged));
   expected_chrome.Set(key::kExtensionInstallAllowlist, std::move(entry_list_3));
   expected_chrome.Set(key::kPolicyAtomicGroupsEnabled,
                       atomic_policy_enabled.DeepCopy());
diff --git a/components/policy/core/common/policy_statistics_collector_unittest.cc b/components/policy/core/common/policy_statistics_collector_unittest.cc
index 74d930db..2d01549 100644
--- a/components/policy/core/common/policy_statistics_collector_unittest.cc
+++ b/components/policy/core/common/policy_statistics_collector_unittest.cc
@@ -33,7 +33,7 @@
 // Arbitrary policy names used for testing.
 const char kTestPolicy1[] = "Test Policy 1";
 const char kTestPolicy2[] = "Test Policy 2";
-const char* kTestPolicy3 = key::kExtensionInstallBlacklist;
+const char* kTestPolicy3 = key::kExtensionInstallBlocklist;
 
 const int kTestPolicy1Id = 42;
 const int kTestPolicy2Id = 123;
@@ -45,7 +45,7 @@
     "  \"properties\": {"
     "    \"Test Policy 1\": { \"type\": \"string\" },"
     "    \"Test Policy 2\": { \"type\": \"string\" },"
-    "    \"ExtensionInstallBlacklist\": { \"type\": \"string\" },"
+    "    \"ExtensionInstallBlocklist\": { \"type\": \"string\" },"
     "  }"
     "}";
 
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index ec0e74d..efa43ce29 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -446,6 +446,7 @@
       'desc': '''Configures extension-related policies. The user is not allowed to install blacklisted extensions unless they are whitelisted. You can also force <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> to automatically install extensions by specifying them in <ph name="EXTENSION_INSTALL_FORCELIST_POLICY_NAME">ExtensionInstallForcelist</ph>. Force-installed extensions are installed regardless whether they are present in the blacklist.''',
       'policies': [
         'ExtensionInstallAllowlist',
+        'ExtensionInstallBlocklist',
         'ExtensionInstallBlacklist',
         'ExtensionInstallWhitelist',
         'ExtensionInstallForcelist',
@@ -4277,9 +4278,11 @@
       },
       'example_value': ['extension_id1', 'extension_id2'],
       'id': 32,
+      'deprecated': True,
       'caption': '''Configure extension installation blacklist''',
       'tags': [],
-      'desc': '''Allows you to specify which extensions the users can NOT install. Extensions already installed will be disabled if blacklisted, without a way for the user to enable them. Once an extension disabled due to the blacklist is removed from it, it will automatically get re-enabled.
+      'desc': '''This policy is deprecated, please use the '<ph name="EXTENSION_INSTALL_BLOCKLIST_POLICY_NAME">ExtensionInstallBlocklist</ph>' policy instead.
+          Allows you to specify which extensions the users can NOT install. Extensions already installed will be disabled if blacklisted, without a way for the user to enable them. Once an extension disabled due to the blacklist is removed from it, it will automatically get re-enabled.
 
           A blacklist value of '*' means all extensions are blacklisted unless they are explicitly listed in the whitelist.
 
@@ -4287,6 +4290,31 @@
       'label': '''Extension IDs the user should be prevented from installing (or * for all)''',
     },
     {
+
+      'name': 'ExtensionInstallBlocklist',
+      'owners': ['file://components/policy/resources/OWNERS'],
+      'type': 'list',
+      'schema': {
+        'type': 'array',
+        'items': { 'type': 'string' },
+      },
+      'supported_on': ['chrome.*:86-', 'chrome_os:86-'],
+      'features': {
+        'dynamic_refresh': True,
+        'per_profile': True,
+      },
+      'example_value': ['extension_id1', 'extension_id2'],
+      'id': 740,
+      'caption': '''Configure extension installation blocklist''',
+      'tags': [],
+      'desc': '''Allows you to specify which extensions the users can NOT install. Extensions already installed will be disabled if blocked, without a way for the user to enable them. Once an extension disabled due to the blocklist is removed from it, it will automatically get re-enabled.
+
+          A blocklist value of '*' means all extensions are blocked unless they are explicitly listed in the allowlist.
+
+          If this policy is left not set the user can install any extension in <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>.''',
+      'label': '''Extension IDs the user should be prevented from installing (or * for all)''',
+    },
+    {
       'name': 'ExtensionInstallAllowlist',
       'owners': ['file://components/policy/resources/OWNERS'],
       'type': 'list',
@@ -4305,9 +4333,9 @@
       'tags': [],
       'desc': '''Allows you to specify which extensions are not subject to the blocklist.
 
-          A blocklist value of * means all extensions are blocklisted and users can only install extensions listed in the allow list.
+          A blocklist value of * means all extensions are blocked and users can only install extensions listed in the allow list.
 
-          By default, all extensions are allowed, but if all extensions have been blocklisted by policy, the allow list can be used to override that policy.''',
+          By default, all extensions are allowed, but if all extensions have been blocked by policy, the allow list can be used to override that policy.''',
       'label': '''Extension IDs to exempt from the blocklist''',
     },
     {
@@ -4328,13 +4356,12 @@
       'deprecated': True,
       'caption': '''Configure extension installation whitelist''',
       'tags': [],
-      'desc': '''Allows you to specify which extensions are not subject to the blacklist.
+      'desc': '''This policy is deprecated, please use the '<ph name="EXTENSION_INSTALL_ALLOWLIST_POLICY_NAME">ExtensionInstallAllowlist</ph>' policy instead.
+          Allows you to specify which extensions are not subject to the blacklist.
 
           A blacklist value of * means all extensions are blacklisted and users can only install extensions listed in the whitelist.
 
-          By default, all extensions are whitelisted, but if all extensions have been blacklisted by policy, the whitelist can be used to override that policy.
-
-          This policy is deprecated in favor of <ph name="EXTENSION_INSTALL_ALLOWLIST_POLICY_NAME">ExtensionInstallAllowlist</ph>. If both <ph name="EXTENSION_INSTALL_WHITELIST_POLICY_NAME">ExtensionInstallWhitelist</ph> and <ph name="EXTENSION_INSTALL_ALLOWLIST_POLICY_NAME">ExtensionInstallAllowlist</ph> are set, <ph name="EXTENSION_INSTALL_ALLOWLIST_POLICY_NAME">ExtensionInstallAllowlist</ph> will be used and <ph name="EXTENSION_INSTALL_WHITELIST_POLICY_NAME">ExtensionInstallWhitelist</ph> ignored.''',
+          By default, all extensions are whitelisted, but if all extensions have been blacklisted by policy, the whitelist can be used to override that policy.''',
       'label': '''Extension IDs to exempt from the blacklist''',
     },
     {
@@ -4364,7 +4391,7 @@
           APIs. (These two APIs are not available to apps/extensions that are
           not force-installed.)
 
-          This policy takes precedence over a potentially conflicting <ph name="EXTENSION_INSTALL_BLACKLIST_POLICY_NAME">ExtensionInstallBlacklist</ph> policy. If an app or extension that previously had been force-installed is removed from this list, it is automatically uninstalled by <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>.
+          This policy takes precedence over a potentially conflicting <ph name="EXTENSION_INSTALL_BLOCKLIST_POLICY_NAME">ExtensionInstallBlocklist</ph> policy. If an app or extension that previously had been force-installed is removed from this list, it is automatically uninstalled by <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>.
 
           Forced installation is limited to apps and extensions listed in the Chrome Web Store For instances that are not one of the following: Windows instances that are joined to a Microsoft® Active Directory® domain. or Windows 10 Pro or Enterprise instances that enrolled for device management and macOS instances that are managed via MDM or joined to a domain via MCX.
 
@@ -4404,7 +4431,7 @@
 
           Each item in this list is an extension-style match pattern (see https://developer.chrome.com/extensions/match_patterns). Users will be able to easily install items from any URL that matches an item in this list. Both the location of the *.crx file and the page where the download is started from (i.e. the referrer) must be allowed by these patterns.
 
-          <ph name="EXTENSION_INSTALL_BLACKLIST_POLICY_NAME">ExtensionInstallBlacklist</ph> takes precedence over this policy. That is, an extension on the blacklist won't be installed, even if it happens from a site on this list.''',
+          <ph name="EXTENSION_INSTALL_BLOCKLIST_POLICY_NAME">ExtensionInstallBlocklist</ph> takes precedence over this policy. That is, an extension on the blacklist won't be installed, even if it happens from a site on this list.''',
       'label': '''URL patterns to allow extension, app, and user script installs from''',
     },
     {
@@ -4635,7 +4662,7 @@
       'id': 544,
       'caption': '''Merge extension install list policies from multiple sources''',
       'tags': [],
-      'desc': '''Enables merging of the extension install list policies <ph name="EXTENSION_INSTALL_BLACKLIST_POLICY_NAME">ExtensionInstallBlacklist</ph>, <ph name="EXTENSION_INSTALL_ALLOWLIST_POLICY_NAME">ExtensionInstallAllowlist</ph> and <ph name="EXTENSION_INSTALL_FORCELIST_POLICY_NAME">ExtensionInstallForcelist</ph>.
+      'desc': '''Enables merging of the extension install list policies <ph name="EXTENSION_INSTALL_BLOCKLIST_POLICY_NAME">ExtensionInstallBlocklist</ph>, <ph name="EXTENSION_INSTALL_ALLOWLIST_POLICY_NAME">ExtensionInstallAllowlist</ph> and <ph name="EXTENSION_INSTALL_FORCELIST_POLICY_NAME">ExtensionInstallForcelist</ph>.
 
       If you enable this setting, the values from machine platform policy, machine cloud policy and user platform policy are merged into a single list and used as a whole instead of only using the values from the single source with highest priority.
 
@@ -7250,7 +7277,7 @@
 
       From Chrome 73 you can block 'javascript://*' URLs. However, it affects only JavaScript typed in address bar (or, for example, bookmarklets). Note that in-page JavaScript URLs, as long as dynamically loaded data, are not subject to this policy. For example, if you block 'example.com/abc', page 'example.com' will still be able to load 'example.com/abc' via XMLHTTPRequest.
 
-      If this policy is not set no URL will be blocklisted in the browser.''',
+      If this policy is not set no URL will be blocked in the browser.''',
       'arc_support': 'Android apps may voluntarily choose to honor this list. You cannot force them to honor it.',
     },
     {
@@ -7326,7 +7353,7 @@
 
       You can block 'javascript://*' URLs. However, it affects only JavaScript typed in address bar (or, for example, bookmarklets). Note that in-page JavaScript URLs, as long as dynamically loaded data, are not subject to this policy. For example, if you block 'example.com/abc', page 'example.com' will still be able to load 'example.com/abc' via XMLHTTPRequest.
 
-      If this policy is not set no URL will be blocklisted in the browser.''',
+      If this policy is not set no URL will be blocked in the browser.''',
       'arc_support': 'Android apps may voluntarily choose to honor this list. You cannot force them to honor it.',
     },
     {
@@ -7400,7 +7427,7 @@
         'dynamic_refresh': True,
         'per_profile': True,
       },
-      'example_value': ['ExtensionInstallAllowlist', 'ExtensionInstallBlacklist'],
+      'example_value': ['ExtensionInstallAllowlist', 'ExtensionInstallBlocklist'],
       'id': 554,
       'caption': '''Allow merging list policies from different sources''',
       'tags': [],
@@ -7494,7 +7521,7 @@
         'dynamic_refresh': True,
         'per_profile': True,
       },
-      'example_value': ['ExtensionInstallAllowlist', 'ExtensionInstallBlacklist'],
+      'example_value': ['ExtensionInstallAllowlist', 'ExtensionInstallBlocklist'],
       'id': 711,
       'caption': '''Enables experimental policies''',
       'tags': [],
@@ -13523,17 +13550,9 @@
       'id': 311,
       'caption': '''Set default display rotation, reapplied on every reboot''',
       'tags': [],
-      'desc': '''If this policy is set, each display is rotated to the
-      specified orientation on every reboot, and the first time it is connected
-      after the policy value has changed. Users may change the display
-      rotation via the settings page after logging in, but their
-      setting will be overridden by the policy value at the next reboot.
+      'desc': '''Setting the policy has each display rotate to the specified orientation on every reboot and the first time it's connected after the policy value changes. Users may change the display rotation through the settings page after signing in, but it changes back at the next reboot. This policy applies to primary and secondary displays.
 
-      This policy applies to both the primary and all secondary displays.
-
-      If the policy is not set, the default value is 0 degrees and the user is
-      free to change it. In this case, the default value is not reapplied at
-      restart.''',
+      If not set, the default value is 0 degrees and users are free to change it. In this case, the default value isn't reapplied at restart.''',
     },
     {
       'name': 'ArcEnabled',
@@ -18347,29 +18366,13 @@
       'id': 502,
       'caption': '''Set display resolution and scale factor''',
       'tags': [],
-      'desc': '''When this policy is set, resolution and scale factor of each
-      display are set to the specified values. External display settings are
-      applied to all connected external displays.
+      'desc': '''Setting the policy sets the resolution and scale factor for each display. External display settings apply to connected displays. (The policy doesn't apply if a display doesn't support the specified resolution or scale.)
 
-      Values of "external_width" and "external_height" should be specified in
-      pixels. Values of "external_scale_percentage" and
-      "internal_scale_percentage" should be specified in percents.
+      Setting <ph name="EXTERNAL_USE_NATIVE">external_use_native</ph> to True means the policy ignores <ph name="EXTERNAL_WIDTH">external_width</ph> and <ph name="EXTERNAL_HEIGHT">external_height</ph> and sets external displays to their native resolution. Setting <ph name="EXTERNAL_USE_NATIVE">external_use_native</ph> to False or leaving it and <ph name="EXTERNAL_WIDTH">external_width</ph> or <ph name="EXTERNAL_HEIGHT">external_height</ph> unset means the policy doesn't affect external displays.
 
-      If "external_use_native" is set to true, policy will ignore values of
-      "external_height" and "external_width" and set resolution of the external
-      displays to their native resolution.
+      Setting the recommended flag to True lets users change resolution and scale factor of any display through the settings page, but their settings change back at the next reboot. Setting the recommended flag to False or leaving it unset means users can't change the display settings.
 
-      If "external_use_native" is false or not provided and either
-      "external_height" or "external_width" is not provided, policy doesn't
-      affect the external display settings. If specified resolution or
-      scale factor is not supported by some display, policy is not applied to
-      that display.
-
-      If "recommended" flag is set to true, users may change resolution and
-      scale factor of any display via the settings page after logging in, but
-      their settings will be overriden by the policy value at the next reboot.
-      If "recommended" flag is set to false or not set, users can't change the
-      display settings.''',
+      Note: Set <ph name="EXTERNAL_WIDTH">external_width</ph> and <ph name="EXTERNAL_HEIGHT">external_height</ph> in pixels and <ph name="EXTERNAL_SCALE_PERCENTAGE">external_scale_percentage</ph> and <ph name="INTERNAL_SCALE_PERCENTAGE">internal_scale_percentage</ph> in percents.''',
     },
     {
       'name': 'PluginVmAllowed',
@@ -22018,6 +22021,28 @@
       If this policy is left unset, then suggestions for new content to explore will be disabled for managed users.
       '''
     },
+    {
+      'name': 'ShowFullUrlsInAddressBar',
+      'owners': ['livvielin@chromium.org', 'enamelites@google.com'],
+      'type': 'main',
+      'schema': { 'type': 'boolean' },
+      'supported_on': ['chrome_os:86-', 'chrome.*:86-'],
+      'features': {
+        'can_be_recommended': True,
+        'dynamic_refresh': True,
+        'per_profile': True,
+      },
+      'example_value': False,
+      'default_for_enterprise_users': False,
+      'id': 739,
+      'caption': 'Show Full URLs',
+      'tags': [],
+      'desc': '''This feature enables display of the full URL in the address bar.
+      If this policy is set to True, then the full URL will be shown in the address bar, including schemes and subdomains.
+      If this policy is set to False, then the default URL display will apply.
+      If this policy is left unset, then the default URL display will apply and the user will be able to toggle between default and full URL display with a context menu option.
+      '''
+    },
   ],
 
   'messages': {
@@ -22509,6 +22534,7 @@
       'caption': '''Extensions''',
       'policies': [
         'ExtensionInstallAllowlist',
+        'ExtensionInstallBlocklist',
         'ExtensionInstallBlacklist',
         'ExtensionInstallWhitelist',
         'ExtensionInstallForcelist',
@@ -22909,6 +22935,6 @@
   ],
   'placeholders': [],
   'deleted_policy_ids': [412, 476, 546, 562, 569, 578],
-  'highest_id_currently_used': 738,
+  'highest_id_currently_used': 740,
   'highest_atomic_group_id_currently_used': 38
 }
diff --git a/components/printing/browser/print_composite_client.cc b/components/printing/browser/print_composite_client.cc
index 7a1e36b4..1880b86e 100644
--- a/components/printing/browser/print_composite_client.cc
+++ b/components/printing/browser/print_composite_client.cc
@@ -34,23 +34,23 @@
   return static_cast<uint64_t>(process_id) << 32 | frame_id;
 }
 
-// Converts a ContentToProxyIdMap to ContentToFrameMap.
-// ContentToProxyIdMap maps content id to the routing id of its corresponding
-// render frame proxy. This is generated when the content holder was created;
-// ContentToFrameMap maps content id to its render frame's global unique id.
-// The global unique id has the render process id concatenated with render
-// frame routing id, which can uniquely identify a render frame.
+// Converts a ContentToProxyTokenMap to ContentToFrameMap.
+// ContentToProxyTokenMap maps content id to the frame token of its
+// corresponding render frame proxy. This is generated when the content holder
+// was created; ContentToFrameMap maps content id to its render frame's global
+// unique id. The global unique id has the render process id concatenated with
+// render frame routing id, which can uniquely identify a render frame.
 ContentToFrameMap ConvertContentInfoMap(
     content::RenderFrameHost* render_frame_host,
-    const ContentToProxyIdMap& content_proxy_map) {
+    const ContentToProxyTokenMap& content_proxy_map) {
   ContentToFrameMap content_frame_map;
   int process_id = render_frame_host->GetProcess()->GetID();
   for (const auto& entry : content_proxy_map) {
     auto content_id = entry.first;
-    auto proxy_id = entry.second;
+    auto proxy_token = entry.second;
     // Find the RenderFrameHost that the proxy id corresponds to.
     content::RenderFrameHost* rfh =
-        content::RenderFrameHost::FromPlaceholderId(process_id, proxy_id);
+        content::RenderFrameHost::FromPlaceholderToken(process_id, proxy_token);
     if (!rfh) {
       // If the corresponding RenderFrameHost cannot be found, just skip it.
       continue;
diff --git a/components/printing/common/print.mojom b/components/printing/common/print.mojom
index 033bd41..8159a61 100644
--- a/components/printing/common/print.mojom
+++ b/components/printing/common/print.mojom
@@ -6,6 +6,7 @@
 
 import "mojo/public/mojom/base/shared_memory.mojom";
 import "mojo/public/mojom/base/values.mojom";
+import "mojo/public/mojom/base/unguessable_token.mojom";
 import "printing/mojom/print.mojom";
 import "ui/gfx/geometry/mojom/geometry.mojom";
 
@@ -36,8 +37,9 @@
 struct DidPrintContentParams {
   // A shared memory region for the metafile data.
   mojo_base.mojom.ReadOnlySharedMemoryRegion metafile_data_region;
-  // Content id to render frame proxy id mapping for out-of-process subframes.
-  map<uint32, int32> subframe_content_info;
+  // Content id to render frame proxy token mapping for out-of-process
+  // subframes.
+  map<uint32, mojo_base.mojom.UnguessableToken> subframe_content_info;
 };
 
 // Parameters to describe the to-be-rendered preview document.
diff --git a/components/printing/common/printing_param_traits.cc b/components/printing/common/printing_param_traits.cc
index 0f6e91a..86ac42a 100644
--- a/components/printing/common/printing_param_traits.cc
+++ b/components/printing/common/printing_param_traits.cc
@@ -26,7 +26,7 @@
   base::ReadOnlySharedMemoryRegion metafile_data_region;
   success &= ReadParam(m, iter, &metafile_data_region);
 
-  base::flat_map<uint32_t, int32_t> subframe_content_info;
+  base::flat_map<uint32_t, base::UnguessableToken> subframe_content_info;
   success &= ReadParam(m, iter, &subframe_content_info);
 
   if (success) {
diff --git a/components/safe_browsing/content/web_ui/safe_browsing_ui.cc b/components/safe_browsing/content/web_ui/safe_browsing_ui.cc
index 50fa7311..52f8fb7 100644
--- a/components/safe_browsing/content/web_ui/safe_browsing_ui.cc
+++ b/components/safe_browsing/content/web_ui/safe_browsing_ui.cc
@@ -24,7 +24,6 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
 #include "base/values.h"
-#include "components/enterprise/common/proto/connectors.pb.h"
 #include "components/grit/components_resources.h"
 #include "components/grit/components_scaled_resources.h"
 #include "components/password_manager/core/browser/hash_password_manager.h"
@@ -34,6 +33,7 @@
 #include "components/safe_browsing/core/features.h"
 #include "components/safe_browsing/core/proto/csd.pb.h"
 #if BUILDFLAG(FULL_SAFE_BROWSING)
+#include "components/enterprise/common/proto/connectors.pb.h"
 #include "components/safe_browsing/core/proto/webprotect.pb.h"
 #endif
 #include "components/safe_browsing/core/realtime/policy_engine.h"
@@ -49,6 +49,11 @@
 #include "components/safe_browsing/core/db/v4_local_database_manager.h"
 #endif
 
+#if BUILDFLAG(FULL_SAFE_BROWSING)
+using TriggeredRule =
+    enterprise_connectors::ContentAnalysisResponse::Result::TriggeredRule;
+#endif
+
 using base::Time;
 using sync_pb::GaiaPasswordReuse;
 using PasswordCaptured = sync_pb::UserEventSpecifics::GaiaPasswordCaptured;
@@ -1472,20 +1477,16 @@
       base::DictionaryValue rule_value;
 
       switch (rule.action()) {
-        case enterprise_connectors::ContentAnalysisResponse::Result::
-            TriggeredRule::ACTION_UNSPECIFIED:
+        case TriggeredRule::ACTION_UNSPECIFIED:
           rule_value.SetStringKey("action", "ACTION_UNSPECIFIED");
           break;
-        case enterprise_connectors::ContentAnalysisResponse::Result::
-            TriggeredRule::REPORT_ONLY:
+        case TriggeredRule::REPORT_ONLY:
           rule_value.SetStringKey("action", "REPORT_ONLY");
           break;
-        case enterprise_connectors::ContentAnalysisResponse::Result::
-            TriggeredRule::WARN:
+        case TriggeredRule::WARN:
           rule_value.SetStringKey("action", "WARN");
           break;
-        case enterprise_connectors::ContentAnalysisResponse::Result::
-            TriggeredRule::BLOCK:
+        case TriggeredRule::BLOCK:
           rule_value.SetStringKey("action", "BLOCK");
           break;
       }
diff --git a/components/safe_browsing/core/realtime/url_lookup_service.cc b/components/safe_browsing/core/realtime/url_lookup_service.cc
index 488c256..818f0c0a 100644
--- a/components/safe_browsing/core/realtime/url_lookup_service.cc
+++ b/components/safe_browsing/core/realtime/url_lookup_service.cc
@@ -31,12 +31,6 @@
 
 namespace safe_browsing {
 
-namespace {
-
-constexpr char kAuthHeaderBearer[] = "Bearer ";
-
-}  // namespace
-
 RealTimeUrlLookupService::RealTimeUrlLookupService(
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
     VerdictCacheManager* cache_manager,
@@ -60,36 +54,16 @@
       std::make_unique<SafeBrowsingTokenFetcher>(identity_manager_);
 }
 
-void RealTimeUrlLookupService::StartLookup(
+void RealTimeUrlLookupService::GetAccessToken(
     const GURL& url,
     RTLookupRequestCallback request_callback,
     RTLookupResponseCallback response_callback) {
-  DCHECK(CurrentlyOnThread(ThreadID::UI));
-  DCHECK(url.is_valid());
-
-  // Check cache.
-  std::unique_ptr<RTLookupResponse> cache_response =
-      GetCachedRealTimeUrlVerdict(url);
-  if (cache_response) {
-    base::PostTask(FROM_HERE, CreateTaskTraits(ThreadID::IO),
-                   base::BindOnce(std::move(response_callback),
-                                  /* is_rt_lookup_successful */ true,
-                                  std::move(cache_response)));
-    return;
-  }
-
-  if (CanPerformFullURLLookupWithToken()) {
-    token_fetcher_->Start(
-        signin::ConsentLevel::kNotRequired,
-        base::BindOnce(&RealTimeUrlLookupService::OnGetAccessToken,
-                       weak_factory_.GetWeakPtr(), url,
-                       std::move(request_callback),
-                       std::move(response_callback), base::TimeTicks::Now()));
-  } else {
-    std::unique_ptr<RTLookupRequest> request = FillRequestProto(url);
-    SendRequest(url, /* access_token_info */ base::nullopt, std::move(request),
-                std::move(request_callback), std::move(response_callback));
-  }
+  token_fetcher_->Start(
+      signin::ConsentLevel::kNotRequired,
+      base::BindOnce(&RealTimeUrlLookupService::OnGetAccessToken,
+                     weak_factory_.GetWeakPtr(), url,
+                     std::move(request_callback), std::move(response_callback),
+                     base::TimeTicks::Now()));
 }
 
 void RealTimeUrlLookupService::OnGetAccessToken(
@@ -98,73 +72,18 @@
     RTLookupResponseCallback response_callback,
     base::TimeTicks get_token_start_time,
     base::Optional<signin::AccessTokenInfo> access_token_info) {
-  std::unique_ptr<RTLookupRequest> request = FillRequestProto(url);
   base::UmaHistogramTimes("SafeBrowsing.RT.GetToken.Time",
                           base::TimeTicks::Now() - get_token_start_time);
   base::UmaHistogramBoolean("SafeBrowsing.RT.HasTokenFromFetcher",
                             access_token_info.has_value());
-  SendRequest(url, access_token_info, std::move(request),
-              std::move(request_callback), std::move(response_callback));
-}
-
-void RealTimeUrlLookupService::SendRequest(
-    const GURL& url,
-    base::Optional<signin::AccessTokenInfo> access_token_info,
-    std::unique_ptr<RTLookupRequest> request,
-    RTLookupRequestCallback request_callback,
-    RTLookupResponseCallback response_callback) {
-  DCHECK(CurrentlyOnThread(ThreadID::UI));
-  UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.RT.Request.UserPopulation",
-                            request->population().user_population(),
-                            ChromeUserPopulation::UserPopulation_MAX + 1);
-  std::string req_data;
-  request->SerializeToString(&req_data);
-
-  auto resource_request = GetResourceRequest();
-  if (access_token_info.has_value()) {
-    resource_request->headers.SetHeader(
-        net::HttpRequestHeaders::kAuthorization,
-        base::StrCat({kAuthHeaderBearer, access_token_info.value().token}));
-  }
-  base::UmaHistogramBoolean("SafeBrowsing.RT.HasTokenInRequest",
-                            access_token_info.has_value());
-
-  SendRequestInternal(std::move(resource_request), req_data, url,
-                      std::move(response_callback));
-
-  base::PostTask(FROM_HERE, CreateTaskTraits(ThreadID::IO),
-                 base::BindOnce(std::move(request_callback), std::move(request),
-                                access_token_info.has_value()
-                                    ? access_token_info.value().token
-                                    : ""));
+  std::string access_token_string =
+      access_token_info.value_or(signin::AccessTokenInfo()).token;
+  SendRequest(url, access_token_string, std::move(request_callback),
+              std::move(response_callback));
 }
 
 RealTimeUrlLookupService::~RealTimeUrlLookupService() {}
 
-std::unique_ptr<RTLookupRequest> RealTimeUrlLookupService::FillRequestProto(
-    const GURL& url) {
-  auto request = std::make_unique<RTLookupRequest>();
-  request->set_url(SanitizeURL(url).spec());
-  request->set_lookup_type(RTLookupRequest::NAVIGATION);
-
-  ChromeUserPopulation* user_population = request->mutable_population();
-  user_population->set_user_population(
-      IsEnhancedProtectionEnabled(*pref_service_)
-          ? ChromeUserPopulation::ENHANCED_PROTECTION
-          : IsExtendedReportingEnabled(*pref_service_)
-                ? ChromeUserPopulation::EXTENDED_REPORTING
-                : ChromeUserPopulation::SAFE_BROWSING);
-
-  user_population->set_profile_management_status(profile_management_status_);
-  user_population->set_is_history_sync_enabled(IsHistorySyncEnabled());
-#if BUILDFLAG(FULL_SAFE_BROWSING)
-  user_population->set_is_under_advanced_protection(
-      is_under_advanced_protection_);
-#endif
-  user_population->set_is_incognito(is_off_the_record_);
-  return request;
-}
-
 // TODO(bdea): Refactor this method into a util class as multiple SB classes
 // have this method.
 bool RealTimeUrlLookupService::IsHistorySyncEnabled() {
@@ -195,6 +114,30 @@
   return true;
 }
 
+std::unique_ptr<RTLookupRequest> RealTimeUrlLookupService::FillRequestProto(
+    const GURL& url) {
+  auto request = std::make_unique<RTLookupRequest>();
+  request->set_url(SanitizeURL(url).spec());
+  request->set_lookup_type(RTLookupRequest::NAVIGATION);
+
+  ChromeUserPopulation* user_population = request->mutable_population();
+  user_population->set_user_population(
+      IsEnhancedProtectionEnabled(*pref_service_)
+          ? ChromeUserPopulation::ENHANCED_PROTECTION
+          : IsExtendedReportingEnabled(*pref_service_)
+                ? ChromeUserPopulation::EXTENDED_REPORTING
+                : ChromeUserPopulation::SAFE_BROWSING);
+
+  user_population->set_profile_management_status(profile_management_status_);
+  user_population->set_is_history_sync_enabled(IsHistorySyncEnabled());
+#if BUILDFLAG(FULL_SAFE_BROWSING)
+  user_population->set_is_under_advanced_protection(
+      is_under_advanced_protection_);
+#endif
+  user_population->set_is_incognito(is_off_the_record_);
+  return request;
+}
+
 net::NetworkTrafficAnnotationTag
 RealTimeUrlLookupService::GetTrafficAnnotationTag() const {
   return net::DefineNetworkTrafficAnnotation(
diff --git a/components/safe_browsing/core/realtime/url_lookup_service.h b/components/safe_browsing/core/realtime/url_lookup_service.h
index d801631..64003279 100644
--- a/components/safe_browsing/core/realtime/url_lookup_service.h
+++ b/components/safe_browsing/core/realtime/url_lookup_service.h
@@ -69,18 +69,17 @@
 
   // RealTimeUrlLookupServiceBase:
   bool CanPerformFullURLLookup() const override;
-
   bool CanCheckSubresourceURL() const override;
-
   bool CanCheckSafeBrowsingDb() const override;
 
-  void StartLookup(const GURL& url,
-                   RTLookupRequestCallback request_callback,
-                   RTLookupResponseCallback response_callback) override;
-
  private:
   // RealTimeUrlLookupServiceBase:
   net::NetworkTrafficAnnotationTag GetTrafficAnnotationTag() const override;
+  bool CanPerformFullURLLookupWithToken() const override;
+  void GetAccessToken(const GURL& url,
+                      RTLookupRequestCallback request_callback,
+                      RTLookupResponseCallback response_callback) override;
+  std::unique_ptr<RTLookupRequest> FillRequestProto(const GURL& url) override;
 
   // Called when the access token is obtained from |token_fetcher_|.
   void OnGetAccessToken(
@@ -90,21 +89,8 @@
       base::TimeTicks get_token_start_time,
       base::Optional<signin::AccessTokenInfo> access_token_info);
 
-  // Called to send the request to the Safe Browsing backend over the network.
-  // It also attached an auth header if |access_token_info| has a value.
-  void SendRequest(const GURL& url,
-                   base::Optional<signin::AccessTokenInfo> access_token_info,
-                   std::unique_ptr<RTLookupRequest> request,
-                   RTLookupRequestCallback request_callback,
-                   RTLookupResponseCallback response_callback);
-
   bool IsHistorySyncEnabled();
 
-  std::unique_ptr<RTLookupRequest> FillRequestProto(const GURL& url);
-
-  // Returns true if real time URL lookup with GAIA token is enabled.
-  bool CanPerformFullURLLookupWithToken() const;
-
   // Unowned object used for getting access token when real time url check with
   // token is enabled.
   signin::IdentityManager* identity_manager_;
diff --git a/components/safe_browsing/core/realtime/url_lookup_service_base.cc b/components/safe_browsing/core/realtime/url_lookup_service_base.cc
index 7eccccee..444fd4a 100644
--- a/components/safe_browsing/core/realtime/url_lookup_service_base.cc
+++ b/components/safe_browsing/core/realtime/url_lookup_service_base.cc
@@ -32,6 +32,8 @@
 
 const size_t kURLLookupTimeoutDurationInSeconds = 10;  // 10 seconds.
 
+constexpr char kAuthHeaderBearer[] = "Bearer ";
+
 }  // namespace
 
 RealTimeUrlLookupServiceBase::RealTimeUrlLookupServiceBase(
@@ -206,13 +208,63 @@
   }
 }
 
-std::unique_ptr<network::ResourceRequest>
-RealTimeUrlLookupServiceBase::GetResourceRequest() {
-  auto resource_request = std::make_unique<network::ResourceRequest>();
-  resource_request->url = GetRealTimeLookupUrl();
-  resource_request->load_flags = net::LOAD_DISABLE_CACHE;
-  resource_request->method = "POST";
-  return resource_request;
+void RealTimeUrlLookupServiceBase::StartLookup(
+    const GURL& url,
+    RTLookupRequestCallback request_callback,
+    RTLookupResponseCallback response_callback) {
+  DCHECK(CurrentlyOnThread(ThreadID::UI));
+  DCHECK(url.is_valid());
+
+  // Check cache.
+  std::unique_ptr<RTLookupResponse> cache_response =
+      GetCachedRealTimeUrlVerdict(url);
+  if (cache_response) {
+    base::PostTask(FROM_HERE, CreateTaskTraits(ThreadID::IO),
+                   base::BindOnce(std::move(response_callback),
+                                  /* is_rt_lookup_successful */ true,
+                                  std::move(cache_response)));
+    return;
+  }
+
+  if (CanPerformFullURLLookupWithToken()) {
+    GetAccessToken(url, std::move(request_callback),
+                   std::move(response_callback));
+  } else {
+    SendRequest(url, /* access_token_string */ base::nullopt,
+                std::move(request_callback), std::move(response_callback));
+  }
+}
+
+void RealTimeUrlLookupServiceBase::SendRequest(
+    const GURL& url,
+    base::Optional<std::string> access_token_string,
+    RTLookupRequestCallback request_callback,
+    RTLookupResponseCallback response_callback) {
+  DCHECK(CurrentlyOnThread(ThreadID::UI));
+  std::unique_ptr<RTLookupRequest> request = FillRequestProto(url);
+  UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.RT.Request.UserPopulation",
+                            request->population().user_population(),
+                            ChromeUserPopulation::UserPopulation_MAX + 1);
+  std::string req_data;
+  request->SerializeToString(&req_data);
+
+  auto resource_request = GetResourceRequest();
+  if (access_token_string.has_value()) {
+    resource_request->headers.SetHeader(
+        net::HttpRequestHeaders::kAuthorization,
+        base::StrCat({kAuthHeaderBearer, access_token_string.value()}));
+  }
+  base::UmaHistogramBoolean("SafeBrowsing.RT.HasTokenInRequest",
+                            access_token_string.has_value());
+
+  SendRequestInternal(std::move(resource_request), req_data, url,
+                      std::move(response_callback));
+
+  base::PostTask(
+      FROM_HERE, CreateTaskTraits(ThreadID::IO),
+      base::BindOnce(
+          std::move(request_callback), std::move(request),
+          access_token_string.has_value() ? access_token_string.value() : ""));
 }
 
 void RealTimeUrlLookupServiceBase::SendRequestInternal(
@@ -276,6 +328,15 @@
   pending_requests_.erase(it);
 }
 
+std::unique_ptr<network::ResourceRequest>
+RealTimeUrlLookupServiceBase::GetResourceRequest() {
+  auto resource_request = std::make_unique<network::ResourceRequest>();
+  resource_request->url = GetRealTimeLookupUrl();
+  resource_request->load_flags = net::LOAD_DISABLE_CACHE;
+  resource_request->method = "POST";
+  return resource_request;
+}
+
 void RealTimeUrlLookupServiceBase::Shutdown() {
   for (auto& pending : pending_requests_) {
     // Treat all pending requests as safe.
diff --git a/components/safe_browsing/core/realtime/url_lookup_service_base.h b/components/safe_browsing/core/realtime/url_lookup_service_base.h
index cc623a41..32884692 100644
--- a/components/safe_browsing/core/realtime/url_lookup_service_base.h
+++ b/components/safe_browsing/core/realtime/url_lookup_service_base.h
@@ -41,6 +41,8 @@
 
 // This base class implements the backoff and cache logic for real time URL
 // lookup feature.
+// TODO(crbug.com/1085261): Log prefix of subclasses for all metrics in this
+// class.
 class RealTimeUrlLookupServiceBase : public KeyedService {
  public:
   explicit RealTimeUrlLookupServiceBase(
@@ -61,6 +63,16 @@
   // local hash-based method.
   bool IsInBackoffMode() const;
 
+  // Start the full URL lookup for |url|, call |request_callback| on the same
+  // thread when request is sent, call |response_callback| on the same thread
+  // when response is received.
+  // Note that |request_callback| is not called if there's a valid entry in the
+  // cache for |url|.
+  // This function is overridden in unit tests.
+  virtual void StartLookup(const GURL& url,
+                           RTLookupRequestCallback request_callback,
+                           RTLookupResponseCallback response_callback);
+
   // Helper function to return a weak pointer.
   base::WeakPtr<RealTimeUrlLookupServiceBase> GetWeakPtr();
 
@@ -76,18 +88,6 @@
   // check is enabled.
   virtual bool CanCheckSafeBrowsingDb() const = 0;
 
-  // Start the full URL lookup for |url|, call |request_callback| on the same
-  // thread when request is sent, call |response_callback| on the same thread
-  // when response is received.
-  // Note that |request_callback| is not called if there's a valid entry in the
-  // cache for |url|.
-  // This function is overridden in unit tests.
-  // TODO(crbug.com/1085261): To reduce code redundancy, make this function
-  // non-virtual and break it into several private virtual functions.
-  virtual void StartLookup(const GURL& url,
-                           RTLookupRequestCallback request_callback,
-                           RTLookupResponseCallback response_callback) = 0;
-
   // KeyedService:
   // Called before the actual deletion of the object.
   void Shutdown() override;
@@ -98,6 +98,17 @@
   // sensitive.
   static GURL SanitizeURL(const GURL& url);
 
+  // Called to send the request to the Safe Browsing backend over the network.
+  // It also attached an auth header if |access_token_string| has a value.
+  void SendRequest(const GURL& url,
+                   base::Optional<std::string> access_token_string,
+                   RTLookupRequestCallback request_callback,
+                   RTLookupResponseCallback response_callback);
+
+ private:
+  using PendingRTLookupRequests =
+      base::flat_map<network::SimpleURLLoader*, RTLookupResponseCallback>;
+
   // Returns the endpoint that the URL lookup will be sent to.
   static GURL GetRealTimeLookupUrl();
 
@@ -105,9 +116,22 @@
   // loader.
   virtual net::NetworkTrafficAnnotationTag GetTrafficAnnotationTag() const = 0;
 
+  // Returns true if real time URL lookup with GAIA token is enabled.
+  virtual bool CanPerformFullURLLookupWithToken() const = 0;
+
+  // Gets access token, called if |CanPerformFullURLLookupWithToken| returns
+  // true.
+  virtual void GetAccessToken(const GURL& url,
+                              RTLookupRequestCallback request_callback,
+                              RTLookupResponseCallback response_callback) = 0;
+
+  // Fills in fields in |RTLookupRequest|.
+  virtual std::unique_ptr<RTLookupRequest> FillRequestProto(
+      const GURL& url) = 0;
+
   // Returns the duration of the next backoff. Starts at
-  // |kMinBackOffResetDurationInSeconds| and increases exponentially until it
-  // reaches |kMaxBackOffResetDurationInSeconds|.
+  // |kMinBackOffResetDurationInSeconds| and increases exponentially until
+  // it reaches |kMaxBackOffResetDurationInSeconds|.
   size_t GetBackoffDurationInSeconds() const;
 
   // Called when the request to remote endpoint fails. May initiate or extend
@@ -140,10 +164,6 @@
       const GURL& url,
       RTLookupResponseCallback response_callback);
 
- private:
-  using PendingRTLookupRequests =
-      base::flat_map<network::SimpleURLLoader*, RTLookupResponseCallback>;
-
   // Called when the response from the real-time lookup remote endpoint is
   // received. |url_loader| is the unowned loader that was used to send the
   // request. |request_start_time| is the time when the request was sent.
@@ -182,6 +202,9 @@
   // All requests that are sent but haven't received a response yet.
   PendingRTLookupRequests pending_requests_;
 
+  friend class RealTimeUrlLookupServiceTest;
+  friend class ChromeEnterpriseRealTimeUrlLookupServiceTest;
+
   base::WeakPtrFactory<RealTimeUrlLookupServiceBase> weak_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(RealTimeUrlLookupServiceBase);
diff --git a/components/search_engines/template_url.cc b/components/search_engines/template_url.cc
index 08ee376..02f5f64a 100644
--- a/components/search_engines/template_url.cc
+++ b/components/search_engines/template_url.cc
@@ -1317,13 +1317,9 @@
   // properly.  See http://code.google.com/p/chromium/issues/detail?id=6984 .
   // |url|'s hostname may be IDN-encoded. Before generating |keyword| from it,
   // convert to Unicode, so it won't look like a confusing punycode string.
-  base::string16 keyword = url_formatter::StripWWW(
-      url_formatter::IDNToUnicode(url.host()));
-  // Special case: if the host was exactly "www." (not sure this can happen but
-  // perhaps with some weird intranet and custom DNS server?), ensure we at
-  // least don't return the empty string.
-  return keyword.empty() ? base::ASCIIToUTF16("www")
-                         : base::i18n::ToLower(keyword);
+  base::string16 keyword =
+      url_formatter::IDNToUnicode(url_formatter::StripWWW(url.host()));
+  return base::i18n::ToLower(keyword);
 }
 
 // static
diff --git a/components/search_engines/template_url_unittest.cc b/components/search_engines/template_url_unittest.cc
index 78be721..e04b704 100644
--- a/components/search_engines/template_url_unittest.cc
+++ b/components/search_engines/template_url_unittest.cc
@@ -1807,14 +1807,19 @@
 TEST_F(TemplateURLTest, GenerateKeyword) {
   ASSERT_EQ(ASCIIToUTF16("foo"),
             TemplateURL::GenerateKeyword(GURL("http://foo")));
-  // www. should be stripped.
-  ASSERT_EQ(ASCIIToUTF16("foo"),
+  ASSERT_EQ(ASCIIToUTF16("foo."),
+            TemplateURL::GenerateKeyword(GURL("http://foo.")));
+  // www. should be stripped for a public hostname but not a private/intranet
+  // hostname.
+  ASSERT_EQ(ASCIIToUTF16("google.com"),
+            TemplateURL::GenerateKeyword(GURL("http://www.google.com")));
+  ASSERT_EQ(ASCIIToUTF16("www.foo"),
             TemplateURL::GenerateKeyword(GURL("http://www.foo")));
   // Make sure we don't get a trailing '/'.
   ASSERT_EQ(ASCIIToUTF16("blah"),
             TemplateURL::GenerateKeyword(GURL("http://blah/")));
   // Don't generate the empty string.
-  ASSERT_EQ(ASCIIToUTF16("www"),
+  ASSERT_EQ(ASCIIToUTF16("www."),
             TemplateURL::GenerateKeyword(GURL("http://www.")));
   ASSERT_EQ(
       base::UTF8ToUTF16("\xd0\xb0\xd0\xb1\xd0\xb2"),
diff --git a/components/services/app_service/public/cpp/BUILD.gn b/components/services/app_service/public/cpp/BUILD.gn
index 1582e7d..0ee81f6 100644
--- a/components/services/app_service/public/cpp/BUILD.gn
+++ b/components/services/app_service/public/cpp/BUILD.gn
@@ -123,6 +123,20 @@
   deps = [ "//components/services/app_service/public/mojom" ]
 }
 
+source_set("test_support") {
+  testonly = true
+
+  sources = [
+    "intent_test_util.cc",
+    "intent_test_util.h",
+  ]
+
+  deps = [
+    ":intents",
+    "//components/services/app_service/public/mojom",
+  ]
+}
+
 source_set("unit_tests") {
   testonly = true
 
@@ -131,8 +145,6 @@
     "app_update_unittest.cc",
     "icon_cache_unittest.cc",
     "icon_coalescer_unittest.cc",
-    "intent_test_util.cc",
-    "intent_test_util.h",
     "intent_util_unittest.cc",
     "preferred_apps_converter_unittest.cc",
     "preferred_apps_list_unittest.cc",
@@ -144,6 +156,7 @@
     ":intents",
     ":preferred_apps",
     ":publisher",
+    ":test_support",
     "//chrome/test:test_support",
     "//content/test:test_support",
     "//testing/gtest",
diff --git a/components/services/app_service/public/cpp/icon_cache.cc b/components/services/app_service/public/cpp/icon_cache.cc
index 876215f..c8048acb 100644
--- a/components/services/app_service/public/cpp/icon_cache.cc
+++ b/components/services/app_service/public/cpp/icon_cache.cc
@@ -65,7 +65,8 @@
       cache_hit = &iter->second;
     }
 
-    iter->second.ref_count_++;
+    auto new_ref_count = ++iter->second.ref_count_;
+    CHECK(new_ref_count != std::numeric_limits<decltype(new_ref_count)>::max());
     ref_count_incremented = true;
   }
 
@@ -146,8 +147,8 @@
     return;
   }
 
-  uint64_t n = iter->second.ref_count_;
-  DCHECK(n > 0);
+  auto n = iter->second.ref_count_;
+  CHECK(n > 0);
   n--;
   iter->second.ref_count_ = n;
 
diff --git a/components/services/app_service/public/cpp/intent_test_util.cc b/components/services/app_service/public/cpp/intent_test_util.cc
index 11604670..11f3460 100644
--- a/components/services/app_service/public/cpp/intent_test_util.cc
+++ b/components/services/app_service/public/cpp/intent_test_util.cc
@@ -8,6 +8,30 @@
 #include <vector>
 
 #include "components/services/app_service/public/cpp/intent_filter_util.h"
+#include "components/services/app_service/public/cpp/intent_util.h"
+
+namespace {
+
+apps::mojom::IntentFilterPtr CreateIntentFilterForShare(
+    const std::string& action,
+    const std::string& mime_type,
+    const std::string& activity_name) {
+  auto intent_filter = apps::mojom::IntentFilter::New();
+
+  apps_util::AddSingleValueCondition(
+      apps::mojom::ConditionType::kAction, action,
+      apps::mojom::PatternMatchType::kNone, intent_filter);
+
+  apps_util::AddSingleValueCondition(
+      apps::mojom::ConditionType::kMimeType, mime_type,
+      apps::mojom::PatternMatchType::kMimeType, intent_filter);
+
+  intent_filter->activity_name = activity_name;
+
+  return intent_filter;
+}
+
+}  // namespace
 
 namespace apps_util {
 
@@ -46,4 +70,17 @@
   return intent_filter;
 }
 
+apps::mojom::IntentFilterPtr CreateIntentFilterForSend(
+    const std::string& mime_type,
+    const std::string& activity_name) {
+  return CreateIntentFilterForShare(kIntentActionSend, mime_type,
+                                    activity_name);
+}
+
+apps::mojom::IntentFilterPtr CreateIntentFilterForSendMultiple(
+    const std::string& mime_type,
+    const std::string& activity_name) {
+  return CreateIntentFilterForShare(kIntentActionSendMultiple, mime_type,
+                                    activity_name);
+}
 }  // namespace apps_util
diff --git a/components/services/app_service/public/cpp/intent_test_util.h b/components/services/app_service/public/cpp/intent_test_util.h
index c85021b..7580110 100644
--- a/components/services/app_service/public/cpp/intent_test_util.h
+++ b/components/services/app_service/public/cpp/intent_test_util.h
@@ -19,6 +19,16 @@
     const std::string& scheme,
     const std::string& host);
 
+// Create intent filter for send action.
+apps::mojom::IntentFilterPtr CreateIntentFilterForSend(
+    const std::string& mime_types,
+    const std::string& activity_name = "");
+// Create intent filter for send multiple action.
+
+apps::mojom::IntentFilterPtr CreateIntentFilterForSendMultiple(
+    const std::string& mime_types,
+    const std::string& activity_name = "");
+
 }  // namespace apps_util
 
 #endif  // COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_INTENT_TEST_UTIL_H_
diff --git a/components/services/app_service/public/cpp/intent_util_unittest.cc b/components/services/app_service/public/cpp/intent_util_unittest.cc
index 003a6f6..dc3af12 100644
--- a/components/services/app_service/public/cpp/intent_util_unittest.cc
+++ b/components/services/app_service/public/cpp/intent_util_unittest.cc
@@ -25,21 +25,6 @@
     return condition;
   }
 
-  apps::mojom::IntentFilterPtr CreateIntentFilterForShareTarget(
-      const std::string& mime_type) {
-    auto intent_filter = apps::mojom::IntentFilter::New();
-
-    apps_util::AddSingleValueCondition(
-        apps::mojom::ConditionType::kAction, apps_util::kIntentActionSend,
-        apps::mojom::PatternMatchType::kNone, intent_filter);
-
-    apps_util::AddSingleValueCondition(
-        apps::mojom::ConditionType::kMimeType, mime_type,
-        apps::mojom::PatternMatchType::kMimeType, intent_filter);
-
-    return intent_filter;
-  }
-
   // TODO(crbug.com/1092784): Add other things for a completed intent.
   apps::mojom::IntentPtr CreateShareIntent(const std::string& mime_type) {
     auto intent = apps::mojom::Intent::New();
@@ -236,7 +221,7 @@
   auto intent_only_main_type = CreateShareIntent(mime_type_only_main_type);
   auto intent_only_star = CreateShareIntent(mime_type_only_star);
 
-  auto filter1 = CreateIntentFilterForShareTarget(mime_type1);
+  auto filter1 = apps_util::CreateIntentFilterForSend(mime_type1);
 
   EXPECT_TRUE(apps_util::IntentMatchesFilter(intent1, filter1));
   EXPECT_FALSE(apps_util::IntentMatchesFilter(intent2, filter1));
@@ -245,7 +230,7 @@
   EXPECT_FALSE(apps_util::IntentMatchesFilter(intent_only_main_type, filter1));
   EXPECT_FALSE(apps_util::IntentMatchesFilter(intent_only_star, filter1));
 
-  auto filter2 = CreateIntentFilterForShareTarget(mime_type2);
+  auto filter2 = apps_util::CreateIntentFilterForSend(mime_type2);
 
   EXPECT_FALSE(apps_util::IntentMatchesFilter(intent1, filter2));
   EXPECT_TRUE(apps_util::IntentMatchesFilter(intent2, filter2));
@@ -255,7 +240,7 @@
   EXPECT_FALSE(apps_util::IntentMatchesFilter(intent_only_star, filter2));
 
   auto filter_sub_wildcard =
-      CreateIntentFilterForShareTarget(mime_type_sub_wildcard);
+      apps_util::CreateIntentFilterForSend(mime_type_sub_wildcard);
 
   EXPECT_TRUE(apps_util::IntentMatchesFilter(intent1, filter_sub_wildcard));
   EXPECT_FALSE(apps_util::IntentMatchesFilter(intent2, filter_sub_wildcard));
@@ -269,7 +254,7 @@
       apps_util::IntentMatchesFilter(intent_only_star, filter_sub_wildcard));
 
   auto filter_all_wildcard =
-      CreateIntentFilterForShareTarget(mime_type_all_wildcard);
+      apps_util::CreateIntentFilterForSend(mime_type_all_wildcard);
 
   EXPECT_TRUE(apps_util::IntentMatchesFilter(intent1, filter_all_wildcard));
   EXPECT_TRUE(apps_util::IntentMatchesFilter(intent2, filter_all_wildcard));
@@ -283,7 +268,7 @@
       apps_util::IntentMatchesFilter(intent_only_star, filter_all_wildcard));
 
   auto filter_only_main_type =
-      CreateIntentFilterForShareTarget(mime_type_only_main_type);
+      apps_util::CreateIntentFilterForSend(mime_type_only_main_type);
 
   EXPECT_TRUE(apps_util::IntentMatchesFilter(intent1, filter_only_main_type));
   EXPECT_FALSE(apps_util::IntentMatchesFilter(intent2, filter_only_main_type));
@@ -296,7 +281,8 @@
   EXPECT_FALSE(
       apps_util::IntentMatchesFilter(intent_only_star, filter_only_main_type));
 
-  auto filter_only_star = CreateIntentFilterForShareTarget(mime_type_only_star);
+  auto filter_only_star =
+      apps_util::CreateIntentFilterForSend(mime_type_only_star);
 
   EXPECT_TRUE(apps_util::IntentMatchesFilter(intent1, filter_only_star));
   EXPECT_TRUE(apps_util::IntentMatchesFilter(intent2, filter_only_star));
@@ -317,13 +303,13 @@
   std::string mime_type_sub_wildcard = "text/*";
   std::string mime_type_all_wildcard = "*/*";
 
-  auto filter1 = CreateIntentFilterForShareTarget(mime_type1);
-  auto filter2 = CreateIntentFilterForShareTarget(mime_type2);
-  auto filter3 = CreateIntentFilterForShareTarget(mime_type3);
+  auto filter1 = apps_util::CreateIntentFilterForSend(mime_type1);
+  auto filter2 = apps_util::CreateIntentFilterForSend(mime_type2);
+  auto filter3 = apps_util::CreateIntentFilterForSend(mime_type3);
   auto filter_sub_wildcard =
-      CreateIntentFilterForShareTarget(mime_type_sub_wildcard);
+      apps_util::CreateIntentFilterForSend(mime_type_sub_wildcard);
   auto filter_all_wildcard =
-      CreateIntentFilterForShareTarget(mime_type_all_wildcard);
+      apps_util::CreateIntentFilterForSend(mime_type_all_wildcard);
 
   std::vector<GURL> urls;
   std::vector<std::string> mime_types;
diff --git a/components/spellcheck/browser/spell_check_host_impl.cc b/components/spellcheck/browser/spell_check_host_impl.cc
index d585f5d..0aee43c 100644
--- a/components/spellcheck/browser/spell_check_host_impl.cc
+++ b/components/spellcheck/browser/spell_check_host_impl.cc
@@ -79,6 +79,13 @@
 }
 #endif  // defined(OS_WIN)
 
+void SpellCheckHostImpl::InitializeDictionaries(
+    InitializeDictionariesCallback callback) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  NOTREACHED();
+  std::move(callback).Run();
+}
+
 #endif  //  BUILDFLAG(USE_BROWSER_SPELLCHECKER) &&
         //  !BUILDFLAG(ENABLE_SPELLING_SERVICE)
 
diff --git a/components/spellcheck/browser/spell_check_host_impl.h b/components/spellcheck/browser/spell_check_host_impl.h
index efade8a..ab79fac 100644
--- a/components/spellcheck/browser/spell_check_host_impl.h
+++ b/components/spellcheck/browser/spell_check_host_impl.h
@@ -57,6 +57,8 @@
       GetPerLanguageSuggestionsCallback callback) override;
 #endif  // defined(OS_WIN)
 
+  void InitializeDictionaries(InitializeDictionariesCallback callback) override;
+
 #endif  // BUILDFLAG(USE_BROWSER_SPELLCHECKER) &&
         // !BUILDFLAG(ENABLE_SPELLING_SERVICE)
 
diff --git a/components/spellcheck/common/spellcheck.mojom b/components/spellcheck/common/spellcheck.mojom
index 184169c..c57ea3ce 100644
--- a/components/spellcheck/common/spellcheck.mojom
+++ b/components/spellcheck/common/spellcheck.mojom
@@ -80,6 +80,10 @@
   [EnableIf=is_win, Sync]
   GetPerLanguageSuggestions(mojo_base.mojom.String16 word) =>
       (array<array<mojo_base.mojom.String16>> suggestions);
+
+  // Completes initialization of the spellcheck service by loading dictionaries.
+  [EnableIf=USE_BROWSER_SPELLCHECKER]
+  InitializeDictionaries() => ();
 };
 
 enum Decoration {
diff --git a/components/spellcheck/renderer/spellcheck_provider.cc b/components/spellcheck/renderer/spellcheck_provider.cc
index 76f4452..f6ce2ce 100644
--- a/components/spellcheck/renderer/spellcheck_provider.cc
+++ b/components/spellcheck/renderer/spellcheck_provider.cc
@@ -137,45 +137,22 @@
 #if BUILDFLAG(USE_BROWSER_SPELLCHECKER)
   if (spellcheck::UseBrowserSpellChecker()) {
 #if defined(OS_WIN)
-    // Determine whether a hybrid check is needed.
-    bool use_hunspell = spellcheck_->EnabledLanguageCount() > 0;
-    bool use_native =
-        spellcheck_->EnabledLanguageCount() != spellcheck_->LanguageCount();
-
-    if (!use_hunspell && !use_native) {
-      OnRespondTextCheck(last_identifier_, text, /*results=*/{});
+    if (base::FeatureList::IsEnabled(
+            spellcheck::kWinDelaySpellcheckServiceInit) &&
+        !dictionaries_loaded_) {
+      // Initialize the spellcheck service on demand (this spellcheck request
+      // could be the result of the first click in editable content), then
+      // complete the text check request when the dictionaries are loaded.
+      GetSpellCheckHost().InitializeDictionaries(
+          base::BindOnce(&SpellCheckProvider::RequestTextCheckingFromBrowser,
+                         weak_factory_.GetWeakPtr(), text));
       return;
     }
-
-    if (!use_native) {
-      // No language can be handled by the native spell checker. Use the regular
-      // Hunspell code path.
-      GetSpellCheckHost().CallSpellingService(
-          text,
-          base::BindOnce(&SpellCheckProvider::OnRespondSpellingService,
-                         weak_factory_.GetWeakPtr(), last_identifier_, text));
-      return;
-    }
-
-    // Some languages can be handled by the native spell checker. Use the
-    // regular browser spell check code path. If hybrid spell check is
-    // required (i.e. some locales must be checked by Hunspell), misspellings
-    // from the native spell checker will be double-checked with Hunspell in
-    // the |OnRespondTextCheck| callback.
-    hybrid_requests_info_[last_identifier_] = {/*used_hunspell=*/use_hunspell,
-                                               /*used_native=*/use_native,
-                                               base::TimeTicks::Now()};
 #endif  // defined(OS_WIN)
 
-    // Text check (unified request for grammar and spell check) is only
-    // available for browser process, so we ask the system spellchecker
-    // over mojo or return an empty result if the checker is not available.
-    GetSpellCheckHost().RequestTextCheck(
-        text, routing_id(),
-        base::BindOnce(&SpellCheckProvider::OnRespondTextCheck,
-                       weak_factory_.GetWeakPtr(), last_identifier_, text));
+    RequestTextCheckingFromBrowser(text);
   }
-#endif
+#endif  // BUILDFLAG(USE_BROWSER_SPELLCHECKER)
 
 #if BUILDFLAG(USE_RENDERER_SPELLCHECKER)
   if (!spellcheck::UseBrowserSpellChecker()) {
@@ -184,9 +161,56 @@
         base::BindOnce(&SpellCheckProvider::OnRespondSpellingService,
                        weak_factory_.GetWeakPtr(), last_identifier_, text));
   }
-#endif
+#endif  // BUILDFLAG(USE_RENDERER_SPELLCHECKER)
 }
 
+#if BUILDFLAG(USE_BROWSER_SPELLCHECKER)
+void SpellCheckProvider::RequestTextCheckingFromBrowser(
+    const base::string16& text) {
+  DCHECK(spellcheck::UseBrowserSpellChecker());
+#if defined(OS_WIN)
+  dictionaries_loaded_ = true;
+
+  // Determine whether a hybrid check is needed.
+  bool use_hunspell = spellcheck_->EnabledLanguageCount() > 0;
+  bool use_native =
+      spellcheck_->EnabledLanguageCount() != spellcheck_->LanguageCount();
+
+  if (!use_hunspell && !use_native) {
+    OnRespondTextCheck(last_identifier_, text, /*results=*/{});
+    return;
+  }
+
+  if (!use_native) {
+    // No language can be handled by the native spell checker. Use the regular
+    // Hunspell code path.
+    GetSpellCheckHost().CallSpellingService(
+        text,
+        base::BindOnce(&SpellCheckProvider::OnRespondSpellingService,
+                       weak_factory_.GetWeakPtr(), last_identifier_, text));
+    return;
+  }
+
+  // Some languages can be handled by the native spell checker. Use the
+  // regular browser spell check code path. If hybrid spell check is
+  // required (i.e. some locales must be checked by Hunspell), misspellings
+  // from the native spell checker will be double-checked with Hunspell in
+  // the |OnRespondTextCheck| callback.
+  hybrid_requests_info_[last_identifier_] = {/*used_hunspell=*/use_hunspell,
+                                             /*used_native=*/use_native,
+                                             base::TimeTicks::Now()};
+#endif  // defined(OS_WIN)
+
+  // Text check (unified request for grammar and spell check) is only
+  // available for browser process, so we ask the system spellchecker
+  // over mojo or return an empty result if the checker is not available.
+  GetSpellCheckHost().RequestTextCheck(
+      text, routing_id(),
+      base::BindOnce(&SpellCheckProvider::OnRespondTextCheck,
+                     weak_factory_.GetWeakPtr(), last_identifier_, text));
+}
+#endif  // BUILDFLAG(USE_BROWSER_SPELLCHECKER)
+
 void SpellCheckProvider::FocusedElementChanged(
     const blink::WebElement& unused) {
 #if defined(OS_ANDROID)
@@ -367,7 +391,7 @@
   last_request_ = line;
   last_results_.Swap(textcheck_results);
 }
-#endif
+#endif  // BUILDFLAG(USE_BROWSER_SPELLCHECKER)
 
 bool SpellCheckProvider::SatisfyRequestFromCache(
     const base::string16& text,
diff --git a/components/spellcheck/renderer/spellcheck_provider.h b/components/spellcheck/renderer/spellcheck_provider.h
index 43e6dd2..77354e23 100644
--- a/components/spellcheck/renderer/spellcheck_provider.h
+++ b/components/spellcheck/renderer/spellcheck_provider.h
@@ -132,7 +132,17 @@
       int identifier,
       const base::string16& line,
       const std::vector<SpellCheckResult>& results);
-#endif
+
+  // Makes mojo calls to the browser process to perform platform spellchecking.
+  void RequestTextCheckingFromBrowser(const base::string16& text);
+
+#if defined(OS_WIN)
+  // Flag indicating that the spellcheck service has been initialized and
+  // the dictionaries have been loaded initially. Used to avoid an unnecessary
+  // mojo call to determine this in every text check request.
+  bool dictionaries_loaded_ = false;
+#endif  // defined(OS_WIN)
+#endif  // BUILDFLAG(USE_BROWSER_SPELLCHECKER)
 
   // Holds ongoing spellchecking operations.
   WebTextCheckCompletions text_check_completions_;
diff --git a/components/spellcheck/renderer/spellcheck_provider_test.cc b/components/spellcheck/renderer/spellcheck_provider_test.cc
index 9683a995..8583bc5 100644
--- a/components/spellcheck/renderer/spellcheck_provider_test.cc
+++ b/components/spellcheck/renderer/spellcheck_provider_test.cc
@@ -10,6 +10,7 @@
 #include "base/run_loop.h"
 #include "build/build_config.h"
 #include "components/spellcheck/common/spellcheck.mojom.h"
+#include "components/spellcheck/common/spellcheck_features.h"
 #include "components/spellcheck/common/spellcheck_result.h"
 #include "components/spellcheck/renderer/hunspell_engine.h"
 #include "components/spellcheck/renderer/spellcheck.h"
@@ -192,6 +193,18 @@
 }
 #endif  // defined(OS_WIN)
 
+void TestingSpellCheckProvider::InitializeDictionaries(
+    InitializeDictionariesCallback callback) {
+#if defined(OS_WIN)
+  if (base::FeatureList::IsEnabled(
+          spellcheck::kWinDelaySpellcheckServiceInit)) {
+    std::move(callback).Run();
+    return;
+  }
+#endif  // defined(OS_WIN)
+
+  NOTREACHED();
+}
 #endif  // BUILDFLAG(USE_BROWSER_SPELLCHECKER)
 
 #if defined(OS_ANDROID)
diff --git a/components/spellcheck/renderer/spellcheck_provider_test.h b/components/spellcheck/renderer/spellcheck_provider_test.h
index 7d551663..bb7d3360 100644
--- a/components/spellcheck/renderer/spellcheck_provider_test.h
+++ b/components/spellcheck/renderer/spellcheck_provider_test.h
@@ -151,6 +151,8 @@
       GetPerLanguageSuggestionsCallback callback) override;
 #endif  // defined(OS_WIN)
 
+  void InitializeDictionaries(InitializeDictionariesCallback callback) override;
+
 #endif
 
 #if defined(OS_ANDROID)
diff --git a/components/spellcheck/renderer/spellcheck_provider_unittest.cc b/components/spellcheck/renderer/spellcheck_provider_unittest.cc
index 8629702a..dd0000c 100644
--- a/components/spellcheck/renderer/spellcheck_provider_unittest.cc
+++ b/components/spellcheck/renderer/spellcheck_provider_unittest.cc
@@ -65,12 +65,37 @@
   HybridSpellCheckTest() : provider_(&embedder_provider_) {}
   ~HybridSpellCheckTest() override {}
 
+  void SetUp() override {
+    // Don't delay initialization of the SpellcheckService on browser launch.
+    feature_list_.InitWithFeatures(
+        /*enabled_features=*/{spellcheck::kWinUseBrowserSpellChecker},
+        /*disabled_features=*/{spellcheck::kWinDelaySpellcheckServiceInit});
+  }
+
+  void RunShouldUseBrowserSpellCheckOnlyWhenNeededTest();
+
  protected:
+  base::test::ScopedFeatureList feature_list_;
   base::test::SingleThreadTaskEnvironment task_environment_;
   spellcheck::EmptyLocalInterfaceProvider embedder_provider_;
   TestingSpellCheckProvider provider_;
 };
 
+// Test fixture for testing hybrid check cases with delayed initialization of
+// the spellcheck service.
+class HybridSpellCheckTestDelayInit : public HybridSpellCheckTest {
+ public:
+  HybridSpellCheckTestDelayInit() = default;
+
+  void SetUp() override {
+    // Don't initialize the SpellcheckService on browser launch.
+    feature_list_.InitWithFeatures(
+        /*enabled_features=*/{spellcheck::kWinUseBrowserSpellChecker,
+                              spellcheck::kWinDelaySpellcheckServiceInit},
+        /*disabled_features=*/{});
+  }
+};
+
 // Test fixture for testing combining results from both the native spell checker
 // and Hunspell.
 class CombineSpellCheckResultsTest
@@ -156,35 +181,39 @@
   EXPECT_EQ(completion.cancellation_count_, 0U);
 }
 
+static const HybridSpellCheckTestCase kSpellCheckProviderHybridTestsParams[] = {
+    // No languages - should go straight to completion
+    HybridSpellCheckTestCase{0U, 0U, 0U, 0U},
+    // 1 disabled language - should go to browser
+    HybridSpellCheckTestCase{1U, 0U, 0U, 1U},
+    // 1 enabled language - should skip browser
+    HybridSpellCheckTestCase{1U, 1U, 1U, 0U},
+    // 1 disabled language, 1 enabled - should should go to browser
+    HybridSpellCheckTestCase{2U, 1U, 0U, 1U},
+    // 3 enabled languages - should skip browser
+    HybridSpellCheckTestCase{3U, 3U, 1U, 0U},
+    // 3 disabled languages - should go to browser
+    HybridSpellCheckTestCase{3U, 0U, 0U, 1U},
+    // 3 disabled languages, 3 enabled - should go to browser
+    HybridSpellCheckTestCase{6U, 3U, 0U, 1U}};
+
 // Tests that the SpellCheckProvider calls into the native spell checker only
 // when needed.
 INSTANTIATE_TEST_SUITE_P(
     SpellCheckProviderHybridTests,
     HybridSpellCheckTest,
-    testing::Values(
-        // No languages - should go straight to completion
-        HybridSpellCheckTestCase{0U, 0U, 0U, 0U},
-        // 1 disabled language - should go to browser
-        HybridSpellCheckTestCase{1U, 0U, 0U, 1U},
-        // 1 enabled language - should skip browser
-        HybridSpellCheckTestCase{1U, 1U, 1U, 0U},
-        // 1 disabled language, 1 enabled - should should go to browser
-        HybridSpellCheckTestCase{2U, 1U, 0U, 1U},
-        // 3 enabled languages - should skip browser
-        HybridSpellCheckTestCase{3U, 3U, 1U, 0U},
-        // 3 disabled languages - should go to browser
-        HybridSpellCheckTestCase{3U, 0U, 0U, 1U},
-        // 3 disabled languages, 3 enabled - should go to browser
-        HybridSpellCheckTestCase{6U, 3U, 0U, 1U}));
+    testing::ValuesIn(kSpellCheckProviderHybridTestsParams));
 
 TEST_P(HybridSpellCheckTest, ShouldUseBrowserSpellCheckOnlyWhenNeeded) {
+  RunShouldUseBrowserSpellCheckOnlyWhenNeededTest();
+}
+
+void HybridSpellCheckTest::RunShouldUseBrowserSpellCheckOnlyWhenNeededTest() {
   if (!spellcheck::WindowsVersionSupportsSpellchecker()) {
     return;
   }
 
   const auto& test_case = GetParam();
-  base::test::ScopedFeatureList local_features;
-  local_features.InitAndEnableFeature(spellcheck::kWinUseBrowserSpellChecker);
 
   FakeTextCheckingResult completion;
   provider_.spellcheck()->SetFakeLanguageCounts(
@@ -202,6 +231,20 @@
   EXPECT_EQ(completion.cancellation_count_, 0U);
 }
 
+// Tests that the SpellCheckProvider calls into the native spell checker only
+// when needed when the code path through
+// SpellCheckProvider::RequestTextChecking is that used when the spellcheck
+// service is initialized on demand.
+INSTANTIATE_TEST_SUITE_P(
+    SpellCheckProviderHybridTests,
+    HybridSpellCheckTestDelayInit,
+    testing::ValuesIn(kSpellCheckProviderHybridTestsParams));
+
+TEST_P(HybridSpellCheckTestDelayInit,
+       ShouldUseBrowserSpellCheckOnlyWhenNeeded) {
+  RunShouldUseBrowserSpellCheckOnlyWhenNeededTest();
+}
+
 // Tests that the SpellCheckProvider can correctly combine results from the
 // native spell checker and Hunspell.
 INSTANTIATE_TEST_SUITE_P(
diff --git a/components/ssl_errors/error_classification.cc b/components/ssl_errors/error_classification.cc
index 661f5e74..8341d17 100644
--- a/components/ssl_errors/error_classification.cc
+++ b/components/ssl_errors/error_classification.cc
@@ -288,14 +288,12 @@
           !HostNameHasKnownTLD(dns_name)) {
         continue;
       } else if (dns_name.length() > host_name.length()) {
-        if (url_formatter::StripWWW(base::ASCIIToUTF16(dns_name)) ==
-            base::ASCIIToUTF16(host_name)) {
+        if (url_formatter::StripWWW(dns_name) == host_name) {
           *www_match_host_name = dns_name;
           return true;
         }
       } else {
-        if (url_formatter::StripWWW(base::ASCIIToUTF16(host_name)) ==
-            base::ASCIIToUTF16(dns_name)) {
+        if (url_formatter::StripWWW(host_name) == dns_name) {
           *www_match_host_name = dns_name;
           return true;
         }
diff --git a/components/strictmode/OWNERS b/components/strictmode/OWNERS
new file mode 100644
index 0000000..29db243
--- /dev/null
+++ b/components/strictmode/OWNERS
@@ -0,0 +1,3 @@
+file://build/config/android/OWNERS
+
+# COMPONENT: Build
diff --git a/components/strictmode/android/BUILD.gn b/components/strictmode/android/BUILD.gn
new file mode 100644
index 0000000..054bade
--- /dev/null
+++ b/components/strictmode/android/BUILD.gn
@@ -0,0 +1,31 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/android/rules.gni")
+
+android_library("java") {
+  sources = [
+    "java/src/org/chromium/components/strictmode/KnownViolations.java",
+    "java/src/org/chromium/components/strictmode/ReflectiveThreadStrictModeInterceptor.java",
+    "java/src/org/chromium/components/strictmode/StrictModePolicyViolation.java",
+    "java/src/org/chromium/components/strictmode/ThreadStrictModeInterceptor.java",
+    "java/src/org/chromium/components/strictmode/ThreadStrictModeInterceptorP.java",
+    "java/src/org/chromium/components/strictmode/Violation.java",
+  ]
+  deps = [ "//base:base_java" ]
+}
+
+android_library("javatests") {
+  testonly = true
+  sources = [ "javatests/src/org/chromium/components/strictmode/ThreadStrictModeInterceptorTest.java" ]
+  deps = [
+    ":java",
+    "//base:base_java_test_support",
+    "//content/public/test/android:content_java_test_support",
+    "//third_party/android_deps:androidx_appcompat_appcompat_java",
+    "//third_party/android_deps:androidx_core_core_java",
+    "//third_party/android_support_test_runner:runner_java",
+    "//third_party/junit",
+  ]
+}
diff --git a/components/strictmode/android/java/src/org/chromium/components/strictmode/KnownViolations.java b/components/strictmode/android/java/src/org/chromium/components/strictmode/KnownViolations.java
new file mode 100644
index 0000000..4bd6ffa
--- /dev/null
+++ b/components/strictmode/android/java/src/org/chromium/components/strictmode/KnownViolations.java
@@ -0,0 +1,104 @@
+// Copyright 2020 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.components.strictmode;
+
+import static org.chromium.components.strictmode.Violation.DETECT_DISK_IO;
+import static org.chromium.components.strictmode.Violation.DETECT_DISK_READ;
+import static org.chromium.components.strictmode.Violation.DETECT_DISK_WRITE;
+import static org.chromium.components.strictmode.Violation.DETECT_RESOURCE_MISMATCH;
+
+import android.os.Build;
+
+import java.util.Locale;
+
+/**
+ * Collection of known unfixable StrictMode violations. This list should stay in sync with the
+ * list for other apps (http://go/chrome-known-violations-upstream). Add Chrome-specific exemptions
+ * to {@link ChromeStrictMode}.
+ */
+public final class KnownViolations {
+    public static ThreadStrictModeInterceptor.Builder addExemptions(
+            ThreadStrictModeInterceptor.Builder builder) {
+        applyManufacturer(builder);
+        applyVendor(builder);
+        applyPlatform(builder);
+        return builder;
+    }
+
+    private static void applyManufacturer(ThreadStrictModeInterceptor.Builder exemptions) {
+        String manufacturer = Build.MANUFACTURER.toLowerCase(Locale.US);
+        String model = Build.MODEL.toLowerCase(Locale.US);
+        switch (manufacturer) {
+            case "samsung":
+                exemptions.ignoreExternalMethod(DETECT_DISK_READ | DETECT_DISK_WRITE,
+                        "android.util.GeneralUtil#isSupportedGloveModeInternal");
+                exemptions.ignoreExternalMethod(
+                        DETECT_DISK_READ, "android.graphics.Typeface#SetAppTypeFace");
+                exemptions.ignoreExternalMethod(
+                        DETECT_DISK_READ, "android.graphics.Typeface#setAppTypeFace");
+                exemptions.ignoreExternalMethod(DETECT_DISK_READ,
+                        "android.app.ApplicationPackageManager#queryIntentActivities");
+                exemptions.ignoreExternalMethod(
+                        DETECT_DISK_READ, "android.app.ActivityThread#parseCSCAppResource");
+                exemptions.ignoreExternalMethod(
+                        DETECT_DISK_READ, "android.app.ActivityThread#performLaunchActivity");
+                exemptions.ignoreExternalMethod(DETECT_DISK_READ,
+                        "com.samsung.android.knox.custom.ProKioskManager#getProKioskState");
+                if (model.equals("sm-g9350")) {
+                    exemptions.ignoreExternalMethod(
+                            DETECT_DISK_WRITE, "android.content.res.Resources#loadDrawable");
+                }
+                if (model.equals("sm-j700f") && Build.VERSION.SDK_INT == 23) {
+                    exemptions.ignoreExternalMethod(
+                            DETECT_DISK_IO, "android.content.res.Resources#loadDrawable");
+                    exemptions.ignoreExternalMethod(
+                            DETECT_DISK_WRITE, "android.app.ActivityThread#performLaunchActivity");
+                }
+                break;
+            case "oneplus":
+                exemptions.ignoreExternalMethod(DETECT_DISK_READ | DETECT_DISK_WRITE,
+                        "com.android.server.am.ActivityManagerService#checkProcessExist");
+                break;
+            case "vivo":
+                exemptions.ignoreExternalMethod(
+                        DETECT_DISK_READ, "android.content.res.VivoResources#loadThemeValues");
+                break;
+            case "xiaomi":
+                exemptions.ignoreExternalMethod(
+                        DETECT_DISK_READ, "com.android.internal.policy.PhoneWindow#getDecorView");
+                exemptions.ignoreExternalMethod(
+                        DETECT_DISK_WRITE, "miui.content.res.ThemeResourcesSystem#checkUpdate");
+                exemptions.ignoreExternalMethod(
+                        DETECT_DISK_READ, "android.util.BoostFramework#<init>");
+                break;
+            default:
+                // fall through
+        }
+    }
+
+    private static void applyVendor(ThreadStrictModeInterceptor.Builder exemptions) {
+        exemptions.ignoreExternalMethod(DETECT_DISK_READ, "com.qualcomm.qti.Performance#<clinit>");
+    }
+
+    private static void applyPlatform(ThreadStrictModeInterceptor.Builder exemptions) {
+        exemptions.ignoreExternalMethod(
+                DETECT_DISK_READ, "com.android.messageformat.MessageFormat#formatNamedArgs");
+        exemptions.ignoreExternalMethod(
+                DETECT_RESOURCE_MISMATCH, "com.android.internal.widget.SwipeDismissLayout#init");
+        exemptions.ignoreExternalMethod(DETECT_DISK_IO, "java.lang.ThreadGroup#uncaughtException");
+        exemptions.ignoreExternalMethod(DETECT_DISK_IO, "android.widget.VideoView#openVideo");
+        exemptions.ignoreExternalMethod(DETECT_DISK_IO,
+                "com.android.server.inputmethod.InputMethodManagerService#startInputOrWindowGainedFocus");
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+            exemptions.ignoreExternalMethod(DETECT_DISK_WRITE,
+                    "com.android.server.clipboard.HostClipboardMonitor#setHostClipboard");
+        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            exemptions.ignoreExternalMethod(
+                    DETECT_DISK_WRITE, "android.content.ClipboardManager#setPrimaryClip");
+        }
+    }
+
+    private KnownViolations() {}
+}
diff --git a/components/strictmode/android/java/src/org/chromium/components/strictmode/ReflectiveThreadStrictModeInterceptor.java b/components/strictmode/android/java/src/org/chromium/components/strictmode/ReflectiveThreadStrictModeInterceptor.java
new file mode 100644
index 0000000..acb2cabab
--- /dev/null
+++ b/components/strictmode/android/java/src/org/chromium/components/strictmode/ReflectiveThreadStrictModeInterceptor.java
@@ -0,0 +1,120 @@
+// Copyright 2020 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.components.strictmode;
+
+import android.app.ApplicationErrorReport;
+import android.os.StrictMode;
+import android.os.StrictMode.ThreadPolicy;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.chromium.base.Consumer;
+import org.chromium.base.Function;
+import org.chromium.base.Log;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * StrictMode whitelist installer.
+ *
+ * <p><b>How this works:</b><br>
+ * When StrictMode is enabled without the death penalty, it queues up all ThreadPolicy violations
+ * into a ThreadLocal ArrayList, and then posts a Runnable to the start of the Looper queue to
+ * process them. This is done in order to set a cap to the number of logged/handled violations per
+ * event loop, and avoid overflowing the log buffer or other penalty handlers with violations. <br>
+ * Because the violations are queued into a ThreadLocal ArrayList, they must be queued on the
+ * offending thread, and thus the offending stack frame will exist in the stack trace. The
+ * whitelisting mechanism works by using reflection to set a custom ArrayList into the ThreadLocal.
+ * When StrictMode is adding a new item to the ArrayList, our custom ArrayList checks the stack
+ * trace for any whitelisted frames, and if one is found, no-ops the addition. Then, when the
+ * processing runnable executes, it sees there are no items, and no-ops. <br>
+ * However, if the death penalty is enabled, the concern about handling too many violations no
+ * longer exists (since death will occur after the first one), so the queue is bypassed, and death
+ * occurs instantly without allowing the whitelisting system to intercept it. In order to retain the
+ * death penalty, the whitelisting mechanism itself can be configured to execute the death penalty
+ * after the first non-whitelisted violation.
+ */
+final class ReflectiveThreadStrictModeInterceptor implements ThreadStrictModeInterceptor {
+    private static final String TAG = "ThreadStrictMode";
+
+    @NonNull
+    private final List<Function<Violation, Integer>> mWhitelistEntries;
+    @Nullable
+    private final Consumer mCustomPenalty;
+
+    ReflectiveThreadStrictModeInterceptor(
+            @NonNull List<Function<Violation, Integer>> whitelistEntries,
+            @Nullable Consumer customPenalty) {
+        mWhitelistEntries = whitelistEntries;
+        mCustomPenalty = customPenalty;
+    }
+
+    @Override
+    public void install(ThreadPolicy detectors) {
+        interceptWithReflection();
+        StrictMode.setThreadPolicy(new ThreadPolicy.Builder(detectors).penaltyLog().build());
+    }
+
+    private void interceptWithReflection() {
+        ThreadLocal<ArrayList<Object>> violationsBeingTimed;
+        try {
+            violationsBeingTimed = getViolationsBeingTimed();
+        } catch (Exception e) {
+            throw new RuntimeException(null, e);
+        }
+        violationsBeingTimed.get().clear();
+        violationsBeingTimed.set(new ArrayList<Object>() {
+            @Override
+            public boolean add(Object o) {
+                int violationType = getViolationType(o);
+                StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
+                Violation violation =
+                        new Violation(violationType, Arrays.copyOf(stackTrace, stackTrace.length));
+                if (violationType != Violation.DETECT_UNKNOWN
+                        && violation.isInWhitelist(mWhitelistEntries)) {
+                    return true;
+                }
+                if (mCustomPenalty != null) {
+                    mCustomPenalty.accept(violation);
+                }
+                return super.add(o);
+            }
+        });
+    }
+
+    @SuppressWarnings({"unchecked"})
+    private static ThreadLocal<ArrayList<Object>> getViolationsBeingTimed()
+            throws IllegalAccessException, NoSuchFieldException {
+        Field violationTimingField = StrictMode.class.getDeclaredField("violationsBeingTimed");
+        violationTimingField.setAccessible(true);
+        return (ThreadLocal<ArrayList<Object>>) violationTimingField.get(null);
+    }
+
+    /** @param o {@code android.os.StrictMode.ViolationInfo} */
+    @SuppressWarnings({"unchecked", "PrivateApi"})
+    private static int getViolationType(Object o) {
+        try {
+            Class<?> violationInfo = Class.forName("android.os.StrictMode$ViolationInfo");
+            Field crashInfoField = violationInfo.getDeclaredField("crashInfo");
+            crashInfoField.setAccessible(true);
+            ApplicationErrorReport.CrashInfo crashInfo =
+                    (ApplicationErrorReport.CrashInfo) crashInfoField.get(o);
+            Method parseViolationFromMessage =
+                    StrictMode.class.getDeclaredMethod("parseViolationFromMessage", String.class);
+            parseViolationFromMessage.setAccessible(true);
+            int mask = (int) parseViolationFromMessage.invoke(
+                    null /* static */, crashInfo.exceptionMessage);
+            return mask & Violation.DETECT_ALL_KNOWN;
+        } catch (Exception e) {
+            Log.e(TAG, "Unable to get violation.", e);
+            return Violation.DETECT_UNKNOWN;
+        }
+    }
+}
diff --git a/components/strictmode/android/java/src/org/chromium/components/strictmode/StrictModePolicyViolation.java b/components/strictmode/android/java/src/org/chromium/components/strictmode/StrictModePolicyViolation.java
new file mode 100644
index 0000000..e9cd38b
--- /dev/null
+++ b/components/strictmode/android/java/src/org/chromium/components/strictmode/StrictModePolicyViolation.java
@@ -0,0 +1,22 @@
+// Copyright 2020 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.components.strictmode;
+
+/** Dummy exception thrown for the custom death penalty. */
+public final class StrictModePolicyViolation extends Error {
+    public StrictModePolicyViolation(Violation v) {
+        super(v.violationString());
+        if (v.stackTrace().length == 0) {
+            super.fillInStackTrace();
+        } else {
+            setStackTrace(v.stackTrace());
+        }
+    }
+
+    @Override
+    public synchronized Throwable fillInStackTrace() {
+        return this;
+    }
+}
diff --git a/components/strictmode/android/java/src/org/chromium/components/strictmode/ThreadStrictModeInterceptor.java b/components/strictmode/android/java/src/org/chromium/components/strictmode/ThreadStrictModeInterceptor.java
new file mode 100644
index 0000000..27882dc
--- /dev/null
+++ b/components/strictmode/android/java/src/org/chromium/components/strictmode/ThreadStrictModeInterceptor.java
@@ -0,0 +1,113 @@
+// Copyright 2020 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.components.strictmode;
+
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.StrictMode.ThreadPolicy;
+
+import androidx.annotation.Nullable;
+
+import org.chromium.base.Consumer;
+import org.chromium.base.Function;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Installs a whitelist configuration for StrictMode's ThreadPolicy feature. */
+public interface ThreadStrictModeInterceptor {
+    /**
+     * Install this interceptor and it's whitelists.
+     *
+     * Pre-P, this uses reflection.
+     */
+    void install(ThreadPolicy detectors);
+
+    /**
+     * Builds a configuration for StrictMode enforcement.
+     *
+     * The API (but not the implementation) should stay in sync with the API used by
+     * 'KnownViolations' for other apps (http://go/chrome-known-violations-upstream).
+     */
+    final class Builder {
+        private final List<Function<Violation, Integer>> mWhitelistEntries = new ArrayList<>();
+        private @Nullable Consumer<Violation> mCustomPenalty;
+
+        /**
+         * Ignore a violation that occurs outside of your app.
+         *
+         * @param violationType A mask containing one or more of the DETECT_* constants.
+         * @param classNameWithMethod The name of the class and method to ignore StrictMode
+         *         violations
+         *     in. The format must be "package.Class#method", for example,
+         *     "com.google.foo.ThreadStrictModeInterceptor#addAllowedMethod".
+         */
+        public Builder ignoreExternalMethod(int violationType, final String classNameWithMethod) {
+            String[] parts = classNameWithMethod.split("#");
+            String className = parts[0];
+            String methodName = parts[1];
+            mWhitelistEntries.add(violation -> {
+                if ((violation.violationType() & violationType) == 0) {
+                    return null;
+                }
+                for (StackTraceElement frame : violation.stackTrace()) {
+                    if (frame.getClassName().equals(className)
+                            && frame.getMethodName().equals(methodName)) {
+                        return violationType;
+                    }
+                }
+                return null;
+            });
+            return this;
+        }
+
+        /**
+         * Ignore a violation that occurs inside your app.
+         *
+         * @param violationType A mask containing one or more of the DETECT_* constants.
+         * @param classNameWithMethod The name of the class and method to ignore StrictMode
+         *         violations
+         *     in. The format must be "package.Class#method", for example,
+         *     "com.google.foo.StrictModeWhitelist#addAllowedMethod".
+         */
+        public Builder addAllowedMethod(int violationType, final String classNameWithMethod) {
+            return ignoreExternalMethod(violationType, classNameWithMethod);
+        }
+
+        /** Set the custom penalty that will be notified when an unwhitelisted violation occurs. */
+        public Builder setCustomPenalty(Consumer<Violation> penalty) {
+            mCustomPenalty = penalty;
+            return this;
+        }
+
+        /**
+         * Replaces all penalties with the death penalty.
+         *
+         * <p>Installing whitelists requires that StrictMode does not have the death penalty. If
+         * your app requires the death penalty, you can set this, which will attempt to emulate the
+         * system behavior if possible.
+         *
+         * <p>Death is not guaranteed, since it relies on reflection to work.
+         */
+        public Builder replaceAllPenaltiesWithDeathPenalty() {
+            mCustomPenalty = info -> {
+                StrictModePolicyViolation toThrow = new StrictModePolicyViolation(info);
+                // Post task so that no one has a chance to catch the thrown exception.
+                new Handler(Looper.getMainLooper()).post(() -> { throw toThrow; });
+            };
+            return this;
+        }
+
+        /** Make immutable. */
+        public ThreadStrictModeInterceptor build() {
+            if (Build.VERSION.SDK_INT >= 28) {
+                return new ThreadStrictModeInterceptorP(mWhitelistEntries, mCustomPenalty);
+            } else {
+                return new ReflectiveThreadStrictModeInterceptor(mWhitelistEntries, mCustomPenalty);
+            }
+        }
+    }
+}
diff --git a/components/strictmode/android/java/src/org/chromium/components/strictmode/ThreadStrictModeInterceptorP.java b/components/strictmode/android/java/src/org/chromium/components/strictmode/ThreadStrictModeInterceptorP.java
new file mode 100644
index 0000000..50093785
--- /dev/null
+++ b/components/strictmode/android/java/src/org/chromium/components/strictmode/ThreadStrictModeInterceptorP.java
@@ -0,0 +1,69 @@
+// Copyright 2020 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.components.strictmode;
+
+import android.annotation.TargetApi;
+import android.os.StrictMode;
+import android.os.StrictMode.ThreadPolicy;
+import android.os.strictmode.DiskReadViolation;
+import android.os.strictmode.DiskWriteViolation;
+import android.os.strictmode.ResourceMismatchViolation;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.chromium.base.Consumer;
+import org.chromium.base.Function;
+
+import java.util.List;
+
+/**
+ * Android P+ {@ThreadStrictModeInterceptor} implementation.
+ */
+@TargetApi(28)
+final class ThreadStrictModeInterceptorP implements ThreadStrictModeInterceptor {
+    @NonNull
+    private final List<Function<Violation, Integer>> mWhitelistEntries;
+    @Nullable
+    private final Consumer mCustomPenalty;
+
+    ThreadStrictModeInterceptorP(@NonNull List<Function<Violation, Integer>> whitelistEntries,
+            @Nullable Consumer customPenalty) {
+        mWhitelistEntries = whitelistEntries;
+        mCustomPenalty = customPenalty;
+    }
+
+    @Override
+    @TargetApi(28)
+    public void install(ThreadPolicy threadPolicy) {
+        StrictMode.OnThreadViolationListener listener = v -> {
+            handleThreadViolation(new Violation(computeType(v), v.getStackTrace()));
+        };
+        ThreadPolicy.Builder builder = new ThreadPolicy.Builder(threadPolicy);
+        builder.penaltyListener(Runnable::run, listener);
+        StrictMode.setThreadPolicy(builder.build());
+    }
+
+    private void handleThreadViolation(Violation violation) {
+        if (violation.isInWhitelist(mWhitelistEntries)) {
+            return;
+        }
+        if (mCustomPenalty != null) {
+            mCustomPenalty.accept(violation);
+        }
+    }
+
+    private static int computeType(android.os.strictmode.Violation violation) {
+        if (violation instanceof DiskReadViolation) {
+            return Violation.DETECT_DISK_READ;
+        } else if (violation instanceof DiskWriteViolation) {
+            return Violation.DETECT_DISK_WRITE;
+        } else if (violation instanceof ResourceMismatchViolation) {
+            return Violation.DETECT_RESOURCE_MISMATCH;
+        } else {
+            return Violation.DETECT_UNKNOWN;
+        }
+    }
+}
diff --git a/components/strictmode/android/java/src/org/chromium/components/strictmode/Violation.java b/components/strictmode/android/java/src/org/chromium/components/strictmode/Violation.java
new file mode 100644
index 0000000..065e29c
--- /dev/null
+++ b/components/strictmode/android/java/src/org/chromium/components/strictmode/Violation.java
@@ -0,0 +1,70 @@
+// Copyright 2020 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.components.strictmode;
+
+import org.chromium.base.Function;
+
+import java.util.List;
+
+/** Violation that occurred. */
+public class Violation {
+    public static final int DETECT_UNKNOWN = 0x00;
+    // Taken from android.os.StrictMode
+    public static final int DETECT_DISK_WRITE = 0x01;
+    public static final int DETECT_DISK_READ = 0x02;
+    // In almost all cases that writes needs to be masked, so do reads. Simplify that case.
+    public static final int DETECT_DISK_IO = DETECT_DISK_WRITE | DETECT_DISK_READ;
+
+    // Package since apps should always fix this.
+    static final int DETECT_RESOURCE_MISMATCH = 0x10;
+
+    // Bitmask with all of the thread strict mode violations that we are interested in.
+    public static final int DETECT_ALL_KNOWN = DETECT_DISK_IO | DETECT_RESOURCE_MISMATCH;
+
+    private final int mViolationType;
+    private final StackTraceElement[] mStackTrace;
+
+    public Violation(int violationType, StackTraceElement[] stackTrace) {
+        mViolationType = violationType;
+        mStackTrace = stackTrace;
+    }
+
+    boolean isInWhitelist(List<Function<Violation, Integer>> whitelist) {
+        for (Function<Violation, Integer> whitelistEntry : whitelist) {
+            Integer mask = whitelistEntry.apply(this);
+            if (mask != null && (~mask & violationType()) == 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Type of violation occurred, bitmask containing 0 or more of the DETECT_* constants from AOSP.
+     */
+    public int violationType() {
+        return mViolationType;
+    }
+
+    public StackTraceElement[] stackTrace() {
+        return mStackTrace;
+    }
+
+    /**
+     * A human readable string describing the type of violation that occurred. If multiple
+     * violations occurred, this will only contain one of them.
+     */
+    public final String violationString() {
+        int type = violationType();
+        if ((type & DETECT_DISK_WRITE) != 0) {
+            return "DISK_WRITE";
+        } else if ((type & DETECT_DISK_READ) != 0) {
+            return "DISK_READ";
+        } else if ((type & DETECT_RESOURCE_MISMATCH) != 0) {
+            return "RESOURCE_MISMATCH";
+        }
+        return "UNKNOWN";
+    }
+}
diff --git a/components/strictmode/android/javatests/DEPS b/components/strictmode/android/javatests/DEPS
new file mode 100644
index 0000000..aa93591
--- /dev/null
+++ b/components/strictmode/android/javatests/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+content/public/test/android",
+]
diff --git a/components/strictmode/android/javatests/src/org/chromium/components/strictmode/ThreadStrictModeInterceptorTest.java b/components/strictmode/android/javatests/src/org/chromium/components/strictmode/ThreadStrictModeInterceptorTest.java
new file mode 100644
index 0000000..32885ea
--- /dev/null
+++ b/components/strictmode/android/javatests/src/org/chromium/components/strictmode/ThreadStrictModeInterceptorTest.java
@@ -0,0 +1,91 @@
+// Copyright 2020 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.components.strictmode;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.StrictMode;
+import android.os.StrictMode.ThreadPolicy;
+import android.support.test.InstrumentationRegistry;
+
+import androidx.core.content.ContextCompat;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+
+/**
+ * Tests for {@link ThreadStrictModeInterceptor}.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class ThreadStrictModeInterceptorTest {
+    /**
+     * Test that the penalty is not notified about whitelisted strict mode exceptions.
+     */
+    @Test
+    @SmallTest
+    public void testWhitelisted() {
+        CallbackHelper strictModeDetector = new CallbackHelper();
+        ThreadStrictModeInterceptor.Builder threadInterceptor =
+                new ThreadStrictModeInterceptor.Builder();
+        threadInterceptor.addAllowedMethod(Violation.DETECT_DISK_IO,
+                "org.chromium.components.strictmode.ThreadStrictModeInterceptorTest#doDiskWrite");
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            installThreadInterceptor(threadInterceptor, strictModeDetector);
+            doDiskWrite();
+        });
+
+        // Wait for any tasks posted to the main thread by android.os.StrictMode to complete.
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        assertEquals(0, strictModeDetector.getCallCount());
+    }
+
+    /**
+     * Test that the penalty is notified about non-whitelisted strict mode exceptions.
+     */
+    @Test
+    @SmallTest
+    public void testNotWhitelisted() {
+        CallbackHelper strictModeDetector = new CallbackHelper();
+        ThreadStrictModeInterceptor.Builder threadInterceptor =
+                new ThreadStrictModeInterceptor.Builder();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            installThreadInterceptor(threadInterceptor, strictModeDetector);
+            doDiskWrite();
+        });
+
+        // Wait for any tasks posted to the main thread by android.os.StrictMode to complete.
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        assertTrue(strictModeDetector.getCallCount() >= 1);
+    }
+
+    private void doDiskWrite() {
+        File dataDir = ContextCompat.getDataDir(InstrumentationRegistry.getTargetContext());
+        File prefsDir = new File(dataDir, "shared_prefs");
+        File outFile = new File(prefsDir, "random.txt");
+        try (FileOutputStream out = new FileOutputStream(outFile)) {
+            out.write(1);
+        } catch (Exception e) {
+        }
+    }
+
+    private void installThreadInterceptor(ThreadStrictModeInterceptor.Builder threadInterceptor,
+            CallbackHelper strictModeDetector) {
+        ThreadPolicy.Builder threadPolicy =
+                new ThreadPolicy.Builder(StrictMode.getThreadPolicy()).penaltyLog().detectAll();
+        threadInterceptor.setCustomPenalty(violation -> { strictModeDetector.notifyCalled(); });
+        threadInterceptor.build().install(threadPolicy.build());
+    }
+}
diff --git a/components/sync/model_impl/blocking_model_type_store_impl.cc b/components/sync/model_impl/blocking_model_type_store_impl.cc
index 4af2cdb9..5f9b3f8 100644
--- a/components/sync/model_impl/blocking_model_type_store_impl.cc
+++ b/components/sync/model_impl/blocking_model_type_store_impl.cc
@@ -9,11 +9,14 @@
 #include "base/check_op.h"
 #include "base/location.h"
 #include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/optional.h"
+#include "base/strings/strcat.h"
 #include "components/sync/model/model_error.h"
 #include "components/sync/model_impl/model_type_store_backend.h"
 #include "components/sync/protocol/entity_metadata.pb.h"
 #include "components/sync/protocol/model_type_state.pb.h"
+#include "third_party/leveldatabase/env_chromium.h"
 #include "third_party/leveldatabase/src/include/leveldb/env.h"
 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
 
@@ -239,8 +242,23 @@
   std::unique_ptr<LevelDbWriteBatch> write_batch_impl(
       static_cast<LevelDbWriteBatch*>(write_batch.release()));
   DCHECK_EQ(write_batch_impl->GetModelType(), type_);
-  return backend_->WriteModifications(
-      LevelDbWriteBatch::ToLevelDbWriteBatch(std::move(write_batch_impl)));
+
+  static constexpr char kCommitWriteBatchResultHistogramPrefix[] =
+      "Sync.ModelTypeStoreCommitWriteBatchOutcome.";
+  std::string histogram_name =
+      base::StrCat({kCommitWriteBatchResultHistogramPrefix,
+                    ModelTypeToHistogramSuffix(type_)});
+
+  leveldb::Status status;
+  const auto result = backend_->WriteModifications(
+      LevelDbWriteBatch::ToLevelDbWriteBatch(std::move(write_batch_impl)),
+      &status);
+
+  base::UmaHistogramEnumeration(histogram_name,
+                                leveldb_env::GetLevelDBStatusUMAValue(status),
+                                leveldb_env::LEVELDB_STATUS_MAX);
+
+  return result;
 }
 
 base::Optional<ModelError>
diff --git a/components/sync/model_impl/model_type_store_backend.cc b/components/sync/model_impl/model_type_store_backend.cc
index e97eba07..b7a4c76 100644
--- a/components/sync/model_impl/model_type_store_backend.cc
+++ b/components/sync/model_impl/model_type_store_backend.cc
@@ -167,11 +167,14 @@
 }
 
 base::Optional<ModelError> ModelTypeStoreBackend::WriteModifications(
-    std::unique_ptr<leveldb::WriteBatch> write_batch) {
+    std::unique_ptr<leveldb::WriteBatch> write_batch,
+    leveldb::Status* outcome) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(db_);
   leveldb::Status status =
       db_->Write(leveldb::WriteOptions(), write_batch.get());
+  if (outcome)
+    *outcome = status;
   return status.ok()
              ? base::nullopt
              : base::Optional<ModelError>({FROM_HERE, status.ToString()});
diff --git a/components/sync/model_impl/model_type_store_backend.h b/components/sync/model_impl/model_type_store_backend.h
index 860dc9d..c966d08 100644
--- a/components/sync/model_impl/model_type_store_backend.h
+++ b/components/sync/model_impl/model_type_store_backend.h
@@ -64,9 +64,11 @@
       const std::string& prefix,
       ModelTypeStore::RecordList* record_list);
 
-  // Writes modifications accumulated in |write_batch| to database.
+  // Writes modifications accumulated in |write_batch| to database. If |outcome|
+  // is not null, it will contain the leveldb::Status of this operation.
   base::Optional<ModelError> WriteModifications(
-      std::unique_ptr<leveldb::WriteBatch> write_batch);
+      std::unique_ptr<leveldb::WriteBatch> write_batch,
+      leveldb::Status* outcome = nullptr);
 
   base::Optional<ModelError> DeleteDataAndMetadataForPrefix(
       const std::string& prefix);
diff --git a/components/sync/model_impl/model_type_store_impl_unittest.cc b/components/sync/model_impl/model_type_store_impl_unittest.cc
index 588f3dbf..28ecdf7 100644
--- a/components/sync/model_impl/model_type_store_impl_unittest.cc
+++ b/components/sync/model_impl/model_type_store_impl_unittest.cc
@@ -12,6 +12,7 @@
 #include "base/optional.h"
 #include "base/run_loop.h"
 #include "base/test/bind_test_util.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
 #include "components/sync/model/model_error.h"
 #include "components/sync/model/model_type_store_test_util.h"
@@ -20,6 +21,7 @@
 #include "components/sync/test/test_matchers.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/leveldatabase/env_chromium.h"
 
 namespace syncer {
 
@@ -402,4 +404,24 @@
   }
 }
 
+TEST_F(ModelTypeStoreImplTest, CommitWriteBatchOutcomeHistogram) {
+  base::HistogramTester histograms;
+  static constexpr char kWebAppCommitWriteBatchOutcomeHistogram[] =
+      "Sync.ModelTypeStoreCommitWriteBatchOutcome.WEB_APP";
+
+  auto store =
+      ModelTypeStoreTestUtil::CreateInMemoryStoreForTest(ModelType::WEB_APPS);
+
+  base::Optional<ModelError> error;
+  auto write_batch = store->CreateWriteBatch();
+  write_batch->WriteData("test", "test");
+  store->CommitWriteBatch(std::move(write_batch),
+                          base::BindOnce(&CaptureError, &error));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_FALSE(error) << error->ToString();
+
+  histograms.ExpectBucketCount(kWebAppCommitWriteBatchOutcomeHistogram,
+                               leveldb_env::LEVELDB_STATUS_OK, 1);
+}
+
 }  // namespace syncer
diff --git a/components/sync/protocol/bookmark_model_metadata.proto b/components/sync/protocol/bookmark_model_metadata.proto
index d900c87fd..d07ee5c 100644
--- a/components/sync/protocol/bookmark_model_metadata.proto
+++ b/components/sync/protocol/bookmark_model_metadata.proto
@@ -39,4 +39,11 @@
   // TODO(crbug.com/1066962): remove this code when most of bookmarks are
   // reuploaded.
   optional bool bookmarks_full_title_reuploaded = 3;
+
+  // The local timestamp corresponding to the last time remote updates were
+  // received, in milliseconds since Unix epoch. Introduced in M86.
+  // TODO(crbug.com/1032052): Remove this code once all local sync metadata
+  // is required to populate the client tag (and be considered invalid
+  // otherwise).
+  optional int64 last_sync_time = 4;
 }
diff --git a/components/sync_bookmarks/bookmark_model_type_processor.cc b/components/sync_bookmarks/bookmark_model_type_processor.cc
index 9934a1c..c2df80a 100644
--- a/components/sync_bookmarks/bookmark_model_type_processor.cc
+++ b/components/sync_bookmarks/bookmark_model_type_processor.cc
@@ -244,6 +244,7 @@
       bookmark_tracker_->model_type_state().encryption_key_name() !=
       model_type_state.encryption_key_name();
   bookmark_tracker_->set_model_type_state(model_type_state);
+  bookmark_tracker_->UpdateLastSyncTime();
   updates_handler.Process(updates, got_new_encryption_requirements);
   // There are cases when we receive non-empty updates that don't result in
   // model changes (e.g. reflections). In that case, issue a write to persit the
diff --git a/components/sync_bookmarks/synced_bookmark_tracker.cc b/components/sync_bookmarks/synced_bookmark_tracker.cc
index 539a4ff..0f52a56661 100644
--- a/components/sync_bookmarks/synced_bookmark_tracker.cc
+++ b/components/sync_bookmarks/synced_bookmark_tracker.cc
@@ -42,6 +42,14 @@
 extern const base::Feature kInvalidateBookmarkSyncMetadataIfClientTagMissing{
     "InvalidateBookmarkSyncMetadataIfClientTagMissing",
     base::FEATURE_DISABLED_BY_DEFAULT};
+// Soft version of the above: it does treat local sync metadata as obsolete if
+// client tags are missing, but only if the local client is in sync with the
+// server, for some definition of in-sync (see implementation in
+// ShouldInvalidateMetadataDueToMissingClientTags()).
+extern const base::Feature
+    kInvalidateBookmarkSyncMetadataIfClientTagMissingWhileInSync{
+        "InvalidateBookmarkSyncMetadataIfClientTagMissingWhileInSync",
+        base::FEATURE_ENABLED_BY_DEFAULT};
 
 namespace {
 
@@ -91,6 +99,37 @@
   return id_to_bookmark_node_map;
 }
 
+// Predicate that determines whether a last-synced-time is considered recent
+// enough to activate the logic for
+// |kInvalidateBookmarkSyncMetadataIfClientTagMissingWhileInSync|.
+bool IsRecentEnoughTimeToConsiderInSync(base::Time time) {
+  return base::Time::Now() - time < base::TimeDelta::FromDays(2);
+}
+
+bool ShouldInvalidateMetadataDueToMissingClientTags(
+    bool bookmark_without_client_tag_found,
+    bool has_local_changes,
+    base::Time last_sync_time) {
+  if (!bookmark_without_client_tag_found) {
+    // All good, nothing to invalidate.
+    return false;
+  }
+
+  if (!has_local_changes &&
+      IsRecentEnoughTimeToConsiderInSync(last_sync_time) &&
+      base::FeatureList::IsEnabled(
+          kInvalidateBookmarkSyncMetadataIfClientTagMissingWhileInSync)) {
+    // This seems like a very good time to invalidate metadata, since it's very
+    // likely that the local state is in sync with the remote (server-side)
+    // state. This means there's low change to run into conflicts.
+    return true;
+  }
+
+  // Force-invalidate if the corresponding feature toggle is enabled.
+  return base::FeatureList::IsEnabled(
+      kInvalidateBookmarkSyncMetadataIfClientTagMissing);
+}
+
 }  // namespace
 
 SyncedBookmarkTracker::Entity::Entity(
@@ -182,7 +221,8 @@
     sync_pb::ModelTypeState model_type_state) {
   // base::WrapUnique() used because the constructor is private.
   auto tracker = base::WrapUnique(new SyncedBookmarkTracker(
-      std::move(model_type_state), /*bookmarks_full_title_reuploaded=*/false));
+      std::move(model_type_state), /*bookmarks_full_title_reuploaded=*/false,
+      /*last_sync_time=*/base::Time::Now()));
   return tracker;
 }
 
@@ -209,6 +249,11 @@
     tracker->SetBookmarksFullTitleReuploaded();
   }
 
+  // If the field is not present, |last_sync_time| will be initialized with the
+  // Unix epoch.
+  tracker->last_sync_time_ =
+      syncer::ProtoTimeToTime(model_metadata.last_sync_time());
+
   const CorruptionReason corruption_reason =
       tracker->InitEntitiesFromModelAndMetadata(model,
                                                 std::move(model_metadata));
@@ -489,9 +534,11 @@
 
 SyncedBookmarkTracker::SyncedBookmarkTracker(
     sync_pb::ModelTypeState model_type_state,
-    bool bookmarks_full_title_reuploaded)
+    bool bookmarks_full_title_reuploaded,
+    base::Time last_sync_time)
     : model_type_state_(std::move(model_type_state)),
-      bookmarks_full_title_reuploaded_(bookmarks_full_title_reuploaded) {}
+      bookmarks_full_title_reuploaded_(bookmarks_full_title_reuploaded),
+      last_sync_time_(last_sync_time) {}
 
 SyncedBookmarkTracker::CorruptionReason
 SyncedBookmarkTracker::InitEntitiesFromModelAndMetadata(
@@ -639,9 +686,9 @@
       GetMetadataClientTagHashHistogramBucket(
           client_tag_mismatch_found, bookmark_without_client_tag_found));
 
-  if (bookmark_without_client_tag_found &&
-      base::FeatureList::IsEnabled(
-          kInvalidateBookmarkSyncMetadataIfClientTagMissing)) {
+  if (ShouldInvalidateMetadataDueToMissingClientTags(
+          bookmark_without_client_tag_found, HasLocalChanges(),
+          last_sync_time_)) {
     return CorruptionReason::MISSING_CLIENT_TAG_HASH;
   }
 
diff --git a/components/sync_bookmarks/synced_bookmark_tracker.h b/components/sync_bookmarks/synced_bookmark_tracker.h
index b3e772d5..05d3f486 100644
--- a/components/sync_bookmarks/synced_bookmark_tracker.h
+++ b/components/sync_bookmarks/synced_bookmark_tracker.h
@@ -224,6 +224,11 @@
     model_type_state_ = std::move(model_type_state);
   }
 
+  // Treats the current time as last sync time.
+  // TODO(crbug.com/1032052): Remove this code once all local sync metadata is
+  // required to populate the client tag (and be considered invalid otherwise).
+  void UpdateLastSyncTime() { last_sync_time_ = base::Time::Now(); }
+
   std::vector<const Entity*> GetAllEntities() const;
 
   std::vector<const Entity*> GetEntitiesWithLocalChanges(
@@ -309,7 +314,8 @@
   };
 
   SyncedBookmarkTracker(sync_pb::ModelTypeState model_type_state,
-                        bool bookmarks_full_title_reuploaded);
+                        bool bookmarks_full_title_reuploaded,
+                        base::Time last_sync_time);
 
   // Add entities to |this| tracker based on the content of |*model| and
   // |model_metadata|. Validates the integrity of |*model| and |model_metadata|
@@ -369,6 +375,12 @@
   // reuploaded.
   bool bookmarks_full_title_reuploaded_ = false;
 
+  // The local timestamp corresponding to the last time remote updates were
+  // received.
+  // TODO(crbug.com/1032052): Remove this code once all local sync metadata is
+  // required to populate the client tag (and be considered invalid otherwise).
+  base::Time last_sync_time_;
+
   DISALLOW_COPY_AND_ASSIGN(SyncedBookmarkTracker);
 };
 
diff --git a/components/sync_bookmarks/synced_bookmark_tracker_unittest.cc b/components/sync_bookmarks/synced_bookmark_tracker_unittest.cc
index edec2bc..7fb34920 100644
--- a/components/sync_bookmarks/synced_bookmark_tracker_unittest.cc
+++ b/components/sync_bookmarks/synced_bookmark_tracker_unittest.cc
@@ -75,6 +75,7 @@
   sync_pb::BookmarkMetadata bookmark_metadata;
   bookmark_metadata.mutable_metadata()->set_server_id(server_id);
   bookmark_metadata.mutable_metadata()->set_is_deleted(true);
+  bookmark_metadata.mutable_metadata()->set_sequence_number(1);
   return bookmark_metadata;
 }
 
@@ -543,7 +544,7 @@
   EXPECT_THAT(entities_with_local_change[3]->metadata()->server_id(), Eq(kId3));
 }
 
-TEST(SyncedBookmarkTrackerTest, ShouldMatchModelAndMetadata) {
+TEST(SyncedBookmarkTrackerTest, ShouldNotInvalidateMetadata) {
   std::unique_ptr<bookmarks::BookmarkModel> model =
       bookmarks::TestBookmarkClient::CreateModel();
 
@@ -573,8 +574,7 @@
       /*sample=*/ExpectedCorruptionReason::NO_CORRUPTION, /*count=*/1);
 }
 
-TEST(SyncedBookmarkTrackerTest,
-     ShouldNotMatchModelAndMetadataIfMissingMobileFolder) {
+TEST(SyncedBookmarkTrackerTest, ShouldInvalidateMetadataIfMissingMobileFolder) {
   std::unique_ptr<bookmarks::BookmarkModel> model =
       bookmarks::TestBookmarkClient::CreateModel();
 
@@ -601,8 +601,7 @@
       /*sample=*/ExpectedCorruptionReason::UNTRACKED_BOOKMARK, /*count=*/1);
 }
 
-TEST(SyncedBookmarkTrackerTest,
-     ShouldNotMatchModelAndMetadataIfMissingServerId) {
+TEST(SyncedBookmarkTrackerTest, ShouldInvalidateMetadataIfMissingServerId) {
   std::unique_ptr<bookmarks::BookmarkModel> model =
       bookmarks::TestBookmarkClient::CreateModel();
 
@@ -626,7 +625,7 @@
 }
 
 TEST(SyncedBookmarkTrackerTest,
-     ShouldNotMatchModelAndMetadataIfMissingLocalBookmarkId) {
+     ShouldInvalidateMetadataIfMissingLocalBookmarkId) {
   std::unique_ptr<bookmarks::BookmarkModel> model =
       bookmarks::TestBookmarkClient::CreateModel();
 
@@ -654,7 +653,7 @@
 }
 
 TEST(SyncedBookmarkTrackerTest,
-     ShouldNotMatchModelAndMetadataIfTombstoneHasBookmarkId) {
+     ShouldInvalidateMetadataIfTombstoneHasBookmarkId) {
   std::unique_ptr<bookmarks::BookmarkModel> model =
       bookmarks::TestBookmarkClient::CreateModel();
 
@@ -680,7 +679,7 @@
 }
 
 TEST(SyncedBookmarkTrackerTest,
-     ShouldNotMatchModelAndMetadataIfUnknownLocalBookmarkId) {
+     ShouldInvalidateMetadataIfUnknownLocalBookmarkId) {
   std::unique_ptr<bookmarks::BookmarkModel> model =
       bookmarks::TestBookmarkClient::CreateModel();
 
@@ -708,7 +707,7 @@
       /*count=*/1);
 }
 
-TEST(SyncedBookmarkTrackerTest, ShouldNotMatchModelAndMetadataIfGuidMismatch) {
+TEST(SyncedBookmarkTrackerTest, ShouldInvalidateMetadataIfGuidMismatch) {
   base::test::ScopedFeatureList override_features;
   override_features.InitAndEnableFeature(
       kInvalidateBookmarkSyncMetadataIfMismatchingGuid);
@@ -741,7 +740,7 @@
 }
 
 TEST(SyncedBookmarkTrackerTest,
-     ShouldNotMatchModelAndMetadataIfTombstoneHasDuplicatedClientTagHash) {
+     ShouldInvalidateMetadataIfTombstoneHasDuplicatedClientTagHash) {
   std::unique_ptr<bookmarks::BookmarkModel> model =
       bookmarks::TestBookmarkClient::CreateModel();
 
@@ -781,7 +780,105 @@
 }
 
 TEST(SyncedBookmarkTrackerTest,
-     ShouldNotMatchModelAndMetadataIfMissingClientTagHash) {
+     ShouldInvalidateMetadataIfMissingClientTagHashWhileClientInSync) {
+  std::unique_ptr<bookmarks::BookmarkModel> model =
+      bookmarks::TestBookmarkClient::CreateModel();
+
+  const bookmarks::BookmarkNode* bookmark_bar_node = model->bookmark_bar_node();
+  const bookmarks::BookmarkNode* node0 = model->AddFolder(
+      /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16("node0"));
+
+  sync_pb::BookmarkModelMetadata model_metadata =
+      CreateMetadataForPermanentNodes(model.get());
+  // Sync happened 23 hours ago, which is considered recent enough.
+  model_metadata.set_last_sync_time(syncer::TimeToProtoTime(
+      base::Time::Now() - base::TimeDelta::FromHours(23)));
+
+  sync_pb::BookmarkMetadata* node0_metadata =
+      model_metadata.add_bookmarks_metadata();
+  *node0_metadata = CreateNodeMetadata(node0->id(), /*server_id=*/"id0");
+
+  node0_metadata->mutable_metadata()->clear_client_tag_hash();
+
+  base::HistogramTester histogram_tester;
+  EXPECT_THAT(SyncedBookmarkTracker::CreateFromBookmarkModelAndMetadata(
+                  model.get(), std::move(model_metadata)),
+              IsNull());
+
+  histogram_tester.ExpectUniqueSample(
+      "Sync.BookmarksModelMetadataCorruptionReason",
+      /*sample=*/ExpectedCorruptionReason::MISSING_CLIENT_TAG_HASH,
+      /*count=*/1);
+}
+
+TEST(
+    SyncedBookmarkTrackerTest,
+    ShouldNotInvalidateMetadataDespiteMissingClientTagHashIfLastSyncedLongAgo) {
+  std::unique_ptr<bookmarks::BookmarkModel> model =
+      bookmarks::TestBookmarkClient::CreateModel();
+
+  const bookmarks::BookmarkNode* bookmark_bar_node = model->bookmark_bar_node();
+  const bookmarks::BookmarkNode* node0 = model->AddFolder(
+      /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16("node0"));
+
+  sync_pb::BookmarkModelMetadata model_metadata =
+      CreateMetadataForPermanentNodes(model.get());
+  // Sync happened 10 days ago, which is too long ago to invalidate metadata.
+  model_metadata.set_last_sync_time(syncer::TimeToProtoTime(
+      base::Time::Now() - base::TimeDelta::FromDays(10)));
+
+  sync_pb::BookmarkMetadata* node0_metadata =
+      model_metadata.add_bookmarks_metadata();
+  *node0_metadata = CreateNodeMetadata(node0->id(), /*server_id=*/"id0");
+
+  node0_metadata->mutable_metadata()->clear_client_tag_hash();
+
+  base::HistogramTester histogram_tester;
+  EXPECT_THAT(SyncedBookmarkTracker::CreateFromBookmarkModelAndMetadata(
+                  model.get(), std::move(model_metadata)),
+              NotNull());
+
+  histogram_tester.ExpectUniqueSample(
+      "Sync.BookmarksModelMetadataCorruptionReason",
+      /*sample=*/ExpectedCorruptionReason::NO_CORRUPTION, /*count=*/1);
+}
+
+TEST(SyncedBookmarkTrackerTest,
+     ShouldNotInvalidateMetadataDespiteMissingClientTagIfPendingLocalChanges) {
+  std::unique_ptr<bookmarks::BookmarkModel> model =
+      bookmarks::TestBookmarkClient::CreateModel();
+
+  const bookmarks::BookmarkNode* bookmark_bar_node = model->bookmark_bar_node();
+  const bookmarks::BookmarkNode* node0 = model->AddFolder(
+      /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16("node0"));
+
+  sync_pb::BookmarkModelMetadata model_metadata =
+      CreateMetadataForPermanentNodes(model.get());
+  // Sync 1 hour ago, which is very recent. However, there is a pending local
+  // tombstone that prevents sync metadata from being invalidated.
+  model_metadata.set_last_sync_time(syncer::TimeToProtoTime(
+      base::Time::Now() - base::TimeDelta::FromHours(1)));
+  *model_metadata.add_bookmarks_metadata() =
+      CreateTombstoneMetadata(/*server_id=*/"id0");
+
+  sync_pb::BookmarkMetadata* node0_metadata =
+      model_metadata.add_bookmarks_metadata();
+  *node0_metadata = CreateNodeMetadata(node0->id(), /*server_id=*/"id1");
+
+  node0_metadata->mutable_metadata()->clear_client_tag_hash();
+
+  base::HistogramTester histogram_tester;
+  EXPECT_THAT(SyncedBookmarkTracker::CreateFromBookmarkModelAndMetadata(
+                  model.get(), std::move(model_metadata)),
+              NotNull());
+
+  histogram_tester.ExpectUniqueSample(
+      "Sync.BookmarksModelMetadataCorruptionReason",
+      /*sample=*/ExpectedCorruptionReason::NO_CORRUPTION, /*count=*/1);
+}
+
+TEST(SyncedBookmarkTrackerTest,
+     ShouldInvalidateMetadataIfMissingClientTagHash) {
   base::test::ScopedFeatureList override_features;
   override_features.InitAndEnableFeature(
       kInvalidateBookmarkSyncMetadataIfClientTagMissing);
@@ -813,7 +910,7 @@
 }
 
 TEST(SyncedBookmarkTrackerTest,
-     ShouldNotMatchModelAndMetadataIfUnsyncableNodeIsTracked) {
+     ShouldInvalidateMetadataIfUnsyncableNodeIsTracked) {
   auto client = std::make_unique<bookmarks::TestBookmarkClient>();
   bookmarks::BookmarkNode* managed_node = client->EnableManagedNode();
 
@@ -844,7 +941,7 @@
 }
 
 TEST(SyncedBookmarkTrackerTest,
-     ShouldMatchModelAndMetadataDespiteGuidMismatch) {
+     ShouldNotInvalidateMetadataDespiteGuidMismatch) {
   base::test::ScopedFeatureList override_features;
   override_features.InitAndDisableFeature(
       kInvalidateBookmarkSyncMetadataIfMismatchingGuid);
diff --git a/components/url_formatter/url_formatter.cc b/components/url_formatter/url_formatter.cc
index faaa7a05..d7819aac 100644
--- a/components/url_formatter/url_formatter.cc
+++ b/components/url_formatter/url_formatter.cc
@@ -27,6 +27,9 @@
 
 namespace {
 
+const char kWww[] = "www.";
+constexpr size_t kWwwLength = 4;
+
 IDNConversionResult IDNToUnicodeWithAdjustments(
     base::StringPiece host,
     base::OffsetAdjuster::Adjustments* adjustments);
@@ -75,32 +78,18 @@
     if (!trim_trivial_subdomains_)
       return IDNToUnicodeWithAdjustments(component_text, adjustments).result;
 
-    // Exclude the registry and domain from trivial subdomain stripping.
-    // To get the adjustment offset calculations correct, we need to transform
-    // the registry and domain portion of the host as well.
-    std::string domain_and_registry =
-        net::registry_controlled_domains::GetDomainAndRegistry(
-            component_text,
-            net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
-
-    // If there is no domain and registry, we may be looking at an intranet
-    // or otherwise non-standard host. Leave those alone.
-    if (domain_and_registry.empty())
+    std::string www_stripped_component_text = StripWWW(component_text);
+    // If StripWWW() did nothing, then "www." wasn't a prefix, or it otherwise
+    // didn't meet conditions for stripping "www." (such as intranet hostnames).
+    // In this case, no adjustments for trivial subdomains are needed.
+    if (www_stripped_component_text == component_text)
       return IDNToUnicodeWithAdjustments(component_text, adjustments).result;
-
     base::OffsetAdjuster::Adjustments trivial_subdomains_adjustments;
-    std::string transformed_host = component_text;
-    constexpr char kWww[] = "www.";
-    constexpr size_t kWwwLength = 4;
-    if (component_text.size() - domain_and_registry.length() >= kWwwLength &&
-        StartsWith(component_text, kWww, base::CompareCase::SENSITIVE)) {
-      transformed_host.erase(0, kWwwLength);
-      trivial_subdomains_adjustments.push_back(
-          base::OffsetAdjuster::Adjustment(0, kWwwLength, 0));
-    }
-
+    trivial_subdomains_adjustments.push_back(
+        base::OffsetAdjuster::Adjustment(0, kWwwLength, 0));
     base::string16 unicode_result =
-        IDNToUnicodeWithAdjustments(transformed_host, adjustments).result;
+        IDNToUnicodeWithAdjustments(www_stripped_component_text, adjustments)
+            .result;
     base::OffsetAdjuster::MergeSequentialAdjustments(
         trivial_subdomains_adjustments, adjustments);
     return unicode_result;
@@ -741,15 +730,19 @@
   return IDNToUnicodeWithAdjustments(host, nullptr).result;
 }
 
-base::string16 StripWWW(const base::string16& text) {
-  const base::string16 www(base::ASCIIToUTF16("www."));
-  return base::StartsWith(text, www, base::CompareCase::SENSITIVE)
-      ? text.substr(www.length()) : text;
-}
-
-base::string16 StripWWWFromHost(const GURL& url) {
-  DCHECK(url.is_valid());
-  return StripWWW(base::ASCIIToUTF16(url.host_piece()));
+std::string StripWWW(const std::string& text) {
+  // Exclude the registry and domain from trivial subdomain stripping.
+  std::string domain_and_registry =
+      net::registry_controlled_domains::GetDomainAndRegistry(
+          text, net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
+  // If there is no domain and registry, we may be looking at an intranet
+  // or otherwise non-standard host. Leave those alone.
+  if (domain_and_registry.empty())
+    return text;
+  return text.size() - domain_and_registry.length() >= kWwwLength &&
+                 base::StartsWith(text, kWww, base::CompareCase::SENSITIVE)
+             ? text.substr(kWwwLength)
+             : text;
 }
 
 Skeletons GetSkeletons(const base::string16& host) {
diff --git a/components/url_formatter/url_formatter.h b/components/url_formatter/url_formatter.h
index 59c28bb..9d5df9f 100644
--- a/components/url_formatter/url_formatter.h
+++ b/components/url_formatter/url_formatter.h
@@ -187,12 +187,10 @@
 // DO NOT use this for displaying URLs.
 IDNConversionResult UnsafeIDNToUnicodeWithDetails(base::StringPiece host);
 
-// If |text| starts with "www." it is removed, otherwise |text| is returned
-// unmodified.
-base::string16 StripWWW(const base::string16& text);
-
-// Runs |url|'s host through StripWWW().  |url| must be valid.
-base::string16 StripWWWFromHost(const GURL& url);
+// Strips a "www." prefix from |host| if present and if |host| is eligible.
+// |host| is only eligible for www-stripping if it is not a private or intranet
+// hostname, and if "www." is part of the subdomain (not the eTLD+1).
+std::string StripWWW(const std::string& host);
 
 // Returns skeleton strings computed from |host| for spoof checking.
 Skeletons GetSkeletons(const base::string16& host);
diff --git a/components/viz/common/features.cc b/components/viz/common/features.cc
index ba807b6..6c11f50 100644
--- a/components/viz/common/features.cc
+++ b/components/viz/common/features.cc
@@ -16,6 +16,9 @@
 
 namespace features {
 
+const base::Feature kForcePreferredIntervalForVideo{
+    "ForcePreferredIntervalForVideo", base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kUseSkiaForGLReadback{"UseSkiaForGLReadback",
                                           base::FEATURE_ENABLED_BY_DEFAULT};
 
@@ -79,6 +82,10 @@
 const base::FeatureParam<int> kNumOfFramesToToggleInterval{
     &kUsePreferredIntervalForVideo, "NumOfFramesToToggleInterval", 60};
 
+bool IsForcePreferredIntervalForVideoEnabled() {
+  return base::FeatureList::IsEnabled(kForcePreferredIntervalForVideo);
+}
+
 bool IsVizHitTestingDebugEnabled() {
   return base::CommandLine::ForCurrentProcess()->HasSwitch(
       switches::kEnableVizHitTestDebug);
@@ -140,7 +147,8 @@
 }
 
 bool IsUsingPreferredIntervalForVideo() {
-  return base::FeatureList::IsEnabled(kUsePreferredIntervalForVideo);
+  return IsForcePreferredIntervalForVideoEnabled() ||
+         base::FeatureList::IsEnabled(kUsePreferredIntervalForVideo);
 }
 
 int NumOfFramesToToggleInterval() {
diff --git a/components/viz/common/features.h b/components/viz/common/features.h
index 1b077aa..0abf47c 100644
--- a/components/viz/common/features.h
+++ b/components/viz/common/features.h
@@ -13,6 +13,7 @@
 
 namespace features {
 
+VIZ_COMMON_EXPORT extern const base::Feature kForcePreferredIntervalForVideo;
 VIZ_COMMON_EXPORT extern const base::Feature kUseSkiaForGLReadback;
 VIZ_COMMON_EXPORT extern const base::Feature kUseSkiaRenderer;
 VIZ_COMMON_EXPORT extern const base::Feature kRecordSkPicture;
@@ -30,6 +31,7 @@
 VIZ_COMMON_EXPORT extern const base::Feature kSplitPartiallyOccludedQuads;
 VIZ_COMMON_EXPORT extern const base::Feature kWebRtcLogCapturePipeline;
 
+VIZ_COMMON_EXPORT bool IsForcePreferredIntervalForVideoEnabled();
 VIZ_COMMON_EXPORT bool IsVizHitTestingDebugEnabled();
 VIZ_COMMON_EXPORT bool IsUsingSkiaForGLReadback();
 VIZ_COMMON_EXPORT bool IsUsingSkiaRenderer();
diff --git a/components/viz/common/quads/draw_quad.cc b/components/viz/common/quads/draw_quad.cc
index 7de9198..a0b0255 100644
--- a/components/viz/common/quads/draw_quad.cc
+++ b/components/viz/common/quads/draw_quad.cc
@@ -27,9 +27,7 @@
                       const gfx::Rect& rect,
                       const gfx::Rect& visible_rect,
                       bool needs_blending) {
-  // TODO(boliu): Temporarily making this a release check to catch
-  // crbug.com/1072407.
-  CHECK(rect.Contains(visible_rect))
+  DCHECK(rect.Contains(visible_rect))
       << "rect: " << rect.ToString()
       << " visible_rect: " << visible_rect.ToString();
 
diff --git a/components/viz/common/quads/render_pass.cc b/components/viz/common/quads/render_pass.cc
index 9ff2158..2e08e45 100644
--- a/components/viz/common/quads/render_pass.cc
+++ b/components/viz/common/quads/render_pass.cc
@@ -229,9 +229,7 @@
                         const gfx::Rect& output_rect,
                         const gfx::Rect& damage_rect,
                         const gfx::Transform& transform_to_root_target) {
-  // TODO(boliu): Temporarily making this a release check to catch
-  // crbug.com/1072407.
-  CHECK(id);
+  DCHECK(id);
   DCHECK(damage_rect.IsEmpty() || output_rect.Contains(damage_rect))
       << "damage_rect: " << damage_rect.ToString()
       << " output_rect: " << output_rect.ToString();
@@ -258,9 +256,7 @@
     bool cache_render_pass,
     bool has_damage_from_contributing_content,
     bool generate_mipmap) {
-  // TODO(boliu): Temporarily making this a release check to catch
-  // crbug.com/1072407.
-  CHECK(id);
+  DCHECK(id);
 
   this->id = id;
   this->output_rect = output_rect;
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl.cc b/components/viz/service/display_embedder/skia_output_surface_impl.cc
index 912dd55..aa4554e 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl.cc
@@ -272,16 +272,19 @@
   DCHECK(!overdraw_surface_recorder_);
   DCHECK(debug_settings_->show_overdraw_feedback);
 
+  nway_canvas_.emplace(characterization_.width(), characterization_.height());
+  nway_canvas_->addCanvas(current_paint_->recorder()->getCanvas());
+
   SkSurfaceCharacterization characterization = CreateSkSurfaceCharacterization(
       gfx::Size(characterization_.width(), characterization_.height()),
       BGRA_8888, false /* mipmap */, characterization_.refColorSpace(),
       false /* is_root_render_pass */);
-  overdraw_surface_recorder_.emplace(characterization);
-  overdraw_canvas_.emplace((overdraw_surface_recorder_->getCanvas()));
+  if (characterization.isValid()) {
+    overdraw_surface_recorder_.emplace(characterization);
+    overdraw_canvas_.emplace((overdraw_surface_recorder_->getCanvas()));
+    nway_canvas_->addCanvas(&overdraw_canvas_.value());
+  }
 
-  nway_canvas_.emplace(characterization_.width(), characterization_.height());
-  nway_canvas_->addCanvas(current_paint_->recorder()->getCanvas());
-  nway_canvas_->addCanvas(&overdraw_canvas_.value());
   return &nway_canvas_.value();
 }
 
@@ -476,10 +479,13 @@
   DCHECK(!current_paint_);
   DCHECK(resource_sync_tokens_.empty());
 
-  SkSurfaceCharacterization c = CreateSkSurfaceCharacterization(
+  SkSurfaceCharacterization characterization = CreateSkSurfaceCharacterization(
       surface_size, format, mipmap, std::move(color_space),
       false /* is_root_render_pass */);
-  current_paint_.emplace(c, id);
+  if (!characterization.isValid())
+    return nullptr;
+
+  current_paint_.emplace(characterization, id);
   return current_paint_->recorder()->getCanvas();
 }
 
@@ -727,7 +733,6 @@
       damage_of_buffers_.resize(capabilities_.number_of_buffers);
     }
   }
-
   return result;
 }
 
@@ -759,6 +764,7 @@
   } else {
     capabilities_ = impl_on_gpu_->capabilities();
     is_displayed_as_overlay_ = impl_on_gpu_->IsDisplayedAsOverlay();
+    gr_context_thread_safe_ = impl_on_gpu_->GetGrContextThreadSafeProxy();
     *result = true;
   }
 }
@@ -770,7 +776,9 @@
     bool mipmap,
     sk_sp<SkColorSpace> color_space,
     bool is_root_render_pass) {
-  auto gr_context_thread_safe = impl_on_gpu_->GetGrContextThreadSafeProxy();
+  if (!gr_context_thread_safe_)
+    return SkSurfaceCharacterization();
+
   auto cache_max_resource_bytes = impl_on_gpu_->max_resource_cache_bytes();
   // LegacyFontHost will get LCD text and skia figures out what type to use.
   SkSurfaceProps surface_props(0 /*flags */,
@@ -795,7 +803,7 @@
     DCHECK((capabilities_.uses_default_gl_framebuffer &&
             dependency_->gr_context_type() == gpu::GrContextType::kGL) ||
            !capabilities_.uses_default_gl_framebuffer);
-    auto characterization = gr_context_thread_safe->createCharacterization(
+    auto characterization = gr_context_thread_safe_->createCharacterization(
         cache_max_resource_bytes, image_info, backend_format,
         0 /* sampleCount */, surface_origin, surface_props, mipmap,
         capabilities_.uses_default_gl_framebuffer, false /* isTextureable */,
@@ -825,14 +833,14 @@
 
   auto color_type =
       ResourceFormatToClosestSkColorType(true /* gpu_compositing */, format);
-  auto backend_format = gr_context_thread_safe->defaultBackendFormat(
+  auto backend_format = gr_context_thread_safe_->defaultBackendFormat(
       color_type, GrRenderable::kYes);
   DCHECK(backend_format.isValid());
   auto image_info =
       SkImageInfo::Make(surface_size.width(), surface_size.height(), color_type,
                         kPremul_SkAlphaType, std::move(color_space));
 
-  auto characterization = gr_context_thread_safe->createCharacterization(
+  auto characterization = gr_context_thread_safe_->createCharacterization(
       cache_max_resource_bytes, image_info, backend_format, 0 /* sampleCount */,
       kTopLeft_GrSurfaceOrigin, surface_props, mipmap,
       false /* willUseGLFBO0 */, true /* isTextureable */,
@@ -1037,6 +1045,7 @@
 
 void SkiaOutputSurfaceImpl::ContextLost() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  gr_context_thread_safe_.reset();
   for (auto& observer : observers_)
     observer.OnContextLost();
 }
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl.h b/components/viz/service/display_embedder/skia_output_surface_impl.h
index 8ff30653..1cc936b 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl.h
+++ b/components/viz/service/display_embedder/skia_output_surface_impl.h
@@ -268,6 +268,8 @@
   // |impl_on_gpu| is created and destroyed on the GPU thread.
   std::unique_ptr<SkiaOutputSurfaceImplOnGpu> impl_on_gpu_;
 
+  sk_sp<GrContextThreadSafeProxy> gr_context_thread_safe_;
+
   bool has_set_draw_rectangle_for_frame_ = false;
   base::Optional<gfx::Rect> draw_rectangle_;
 
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
index 6c1f1771b..4bdefd0e 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
@@ -1379,7 +1379,7 @@
 
 sk_sp<GrContextThreadSafeProxy>
 SkiaOutputSurfaceImplOnGpu::GetGrContextThreadSafeProxy() {
-  return gr_context()->threadSafeProxy();
+  return gr_context() ? gr_context()->threadSafeProxy() : nullptr;
 }
 
 void SkiaOutputSurfaceImplOnGpu::ReleaseImageContexts(
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 9ad3fd4..1641239 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -1320,6 +1320,8 @@
     "notifications/platform_notification_context_impl.h",
     "notifications/platform_notification_service_proxy.cc",
     "notifications/platform_notification_service_proxy.h",
+    "payments/installed_payment_apps_finder_impl.cc",
+    "payments/installed_payment_apps_finder_impl.h",
     "payments/payment_app_context_impl.cc",
     "payments/payment_app_context_impl.h",
     "payments/payment_app_database.cc",
diff --git a/content/browser/back_forward_cache_browsertest.cc b/content/browser/back_forward_cache_browsertest.cc
index 36845a9..d7ebfab 100644
--- a/content/browser/back_forward_cache_browsertest.cc
+++ b/content/browser/back_forward_cache_browsertest.cc
@@ -120,6 +120,9 @@
     // TODO(sreejakshetty): Initialize ScopedFeatureLists from test constructor.
     EnableFeatureAndSetParams(features::kBackForwardCache,
                               "TimeToLiveInBackForwardCacheInSeconds", "3600");
+    EnableFeatureAndSetParams(
+        features::kBackForwardCache, "enable_same_site",
+        same_site_back_forward_cache_enabled_ ? "true" : "false");
 #if defined(OS_ANDROID)
     EnableFeatureAndSetParams(features::kBackForwardCache,
                               "process_binding_strength", "NORMAL");
@@ -403,6 +406,9 @@
 
   base::HistogramTester histogram_tester_;
 
+ protected:
+  bool same_site_back_forward_cache_enabled_ = true;
+
  private:
   void AddSampleToBuckets(std::vector<base::Bucket>* buckets,
                           base::HistogramBase::Sample sample) {
@@ -703,30 +709,6 @@
                 FROM_HERE);
 }
 
-// The BackForwardCache does not cache same-website navigations for now.
-IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
-                       DoesNotCacheSameWebsiteNavigations) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-  GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html"));
-  GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html"));
-
-  // 1) Navigate to A1.
-  EXPECT_TRUE(NavigateToURL(shell(), url_a1));
-  RenderFrameHostImpl* rfh_a1 = current_frame_host();
-  RenderFrameDeletedObserver delete_rfh_a1(rfh_a1);
-  int browsing_instance_id = rfh_a1->GetSiteInstance()->GetBrowsingInstanceId();
-
-  // 2) Navigate to A2.
-  EXPECT_TRUE(NavigateToURL(shell(), url_a2));
-  RenderFrameHostImpl* rfh_a2 = current_frame_host();
-  // The BrowsingInstance shouldn't have changed.
-  EXPECT_EQ(browsing_instance_id,
-            rfh_a2->GetSiteInstance()->GetBrowsingInstanceId());
-  EXPECT_FALSE(rfh_a1->IsInBackForwardCache());
-  // The main frame should have been reused for the navigation.
-  EXPECT_EQ(rfh_a1, rfh_a2);
-}
-
 // The current page can't enter the BackForwardCache if another page can script
 // it. This can happen when one document opens a popup using window.open() for
 // instance. It prevents the BackForwardCache from being used.
@@ -3782,8 +3764,26 @@
   delete_rfh_a2.WaitUntilDeleted();
 }
 
-IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
+class BackForwardCacheBrowserTestWithSameSiteDisabled
+    : public BackForwardCacheBrowserTest {
+ public:
+  BackForwardCacheBrowserTestWithSameSiteDisabled() = default;
+  ~BackForwardCacheBrowserTestWithSameSiteDisabled() override = default;
+
+ protected:
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    same_site_back_forward_cache_enabled_ = false;
+    DisableFeature(features::kProactivelySwapBrowsingInstance);
+    BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithSameSiteDisabled,
                        ConflictingBrowsingInstances) {
+  // This test assumes navigation from A1 to A2 will not switch
+  // BrowsingInstances, which is not true when either BackForwardCache or
+  // ProactivelySwapBrowsingInstance is enabled on same-site navigations.
+  DCHECK(!CanSameSiteMainFrameNavigationsChangeSiteInstances());
   ASSERT_TRUE(embedded_test_server()->Start());
   GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html"));
   GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html"));
@@ -3837,6 +3837,101 @@
       FROM_HERE);
 }
 
+// When same-site bfcache is disabled, we should not cache on same-site
+// navigations.
+IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithSameSiteDisabled,
+                       DoesNotCacheOnSameSiteNavigation) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html"));
+  GURL url_a3(
+      embedded_test_server()->GetURL("subdomain.a.com", "/title3.html"));
+
+  // 1) Navigate to A1.
+  EXPECT_TRUE(NavigateToURL(shell(), url_a1));
+  RenderFrameHostImpl* rfh_a1 = current_frame_host();
+  RenderFrameDeletedObserver delete_rfh_a1(rfh_a1);
+  int browsing_instance_id = rfh_a1->GetSiteInstance()->GetBrowsingInstanceId();
+
+  // 2) Navigate same-site and same-origin to A2.
+  EXPECT_TRUE(NavigateToURL(shell(), url_a2));
+  RenderFrameHostImpl* rfh_a2 = current_frame_host();
+  // The BrowsingInstance shouldn't have changed.
+  EXPECT_EQ(browsing_instance_id,
+            rfh_a2->GetSiteInstance()->GetBrowsingInstanceId());
+  // The previous page should not be cached.
+  EXPECT_FALSE(rfh_a1->IsInBackForwardCache());
+
+  // 2) Navigate same-site but cross-origin to A3.
+  EXPECT_TRUE(NavigateToURL(shell(), url_a3));
+  RenderFrameHostImpl* rfh_a3 = current_frame_host();
+  // The BrowsingInstance shouldn't have changed.
+  EXPECT_EQ(browsing_instance_id,
+            rfh_a3->GetSiteInstance()->GetBrowsingInstanceId());
+  // The previous page should not be cached.
+  EXPECT_FALSE(rfh_a2->IsInBackForwardCache());
+}
+
+// Check that during a same-RenderFrameHost cross-document navigation, the
+// disabled reasons is still tracked.
+IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithSameSiteDisabled,
+                       DisableForRenderFrameHostPersistsAcrossNavigations) {
+  // This test assumes navigation from A1 to A2 will not switch
+  // RenderFrameHosts which is not true when BackForwardCache,
+  // ProactivelySwapBrowsingInstance or RenderDocument is enabled on same-site
+  // main frame navigations.
+  DCHECK(!CanSameSiteMainFrameNavigationsChangeRenderFrameHosts());
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html"));
+  GURL url_b3(embedded_test_server()->GetURL("b.com", "/title1.html"));
+
+  // 1) Navigate to A1.
+  EXPECT_TRUE(NavigateToURL(shell(), url_a1));
+  RenderFrameHostImpl* rfh_a1 = current_frame_host();
+  RenderFrameDeletedObserver deleted_observer_rfh_a1(rfh_a1);
+  // Disable back-forward cache for A.
+  BackForwardCache::DisableForRenderFrameHost(rfh_a1, kDisabledReasonForTest);
+
+  // 2) Navigate to A2.
+  EXPECT_TRUE(NavigateToURL(shell(), url_a2));
+  EXPECT_FALSE(deleted_observer_rfh_a1.deleted());
+  EXPECT_EQ(rfh_a1, current_frame_host());
+
+  // 3) Navigate to B3.
+  EXPECT_TRUE(NavigateToURL(shell(), url_b3));
+  deleted_observer_rfh_a1.WaitUntilDeleted();
+
+  // 4) Go back to A2.
+  web_contents()->GetController().GoBack();
+  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+  ExpectDisabledWithReason(kDisabledReasonForTest, FROM_HERE);
+  ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
+                         kDisableForRenderFrameHostCalled},
+                    FROM_HERE);
+}
+
+// The BackForwardCache caches same-website navigations.
+IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, SameSiteNavigationCaching) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html"));
+
+  // 1) Navigate to A1.
+  EXPECT_TRUE(NavigateToURL(shell(), url_a1));
+  RenderFrameHostImpl* rfh_a1 = current_frame_host();
+  RenderFrameDeletedObserver delete_rfh_a1(rfh_a1);
+  int browsing_instance_id = rfh_a1->GetSiteInstance()->GetBrowsingInstanceId();
+
+  // 2) Navigate to A2.
+  EXPECT_TRUE(NavigateToURL(shell(), url_a2));
+  RenderFrameHostImpl* rfh_a2 = current_frame_host();
+  EXPECT_NE(browsing_instance_id,
+            rfh_a2->GetSiteInstance()->GetBrowsingInstanceId());
+  EXPECT_TRUE(rfh_a1->IsInBackForwardCache());
+  EXPECT_NE(rfh_a1, rfh_a2);
+}
+
 IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                        CanCacheMultiplesPagesOnSameDomain) {
   ASSERT_TRUE(embedded_test_server()->Start());
@@ -4209,40 +4304,6 @@
                     FROM_HERE);
 }
 
-// Check that during a same-RenderFrameHost cross-document navigation, the
-// disabled reasons is still tracked.
-IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
-                       DisableForRenderFrameHostPersistsAcrossNavigations) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-  GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html"));
-  GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html"));
-  GURL url_b3(embedded_test_server()->GetURL("b.com", "/title1.html"));
-
-  // 1) Navigate to A1.
-  EXPECT_TRUE(NavigateToURL(shell(), url_a1));
-  RenderFrameHostImpl* rfh_a1 = current_frame_host();
-  RenderFrameDeletedObserver deleted_observer_rfh_a1(rfh_a1);
-  // Disable back-forward cache for A.
-  BackForwardCache::DisableForRenderFrameHost(rfh_a1, kDisabledReasonForTest);
-
-  // 2) Navigate to A2.
-  EXPECT_TRUE(NavigateToURL(shell(), url_a2));
-  EXPECT_FALSE(deleted_observer_rfh_a1.deleted());
-  EXPECT_EQ(rfh_a1, current_frame_host());
-
-  // 3) Navigate to B3.
-  EXPECT_TRUE(NavigateToURL(shell(), url_b3));
-  deleted_observer_rfh_a1.WaitUntilDeleted();
-
-  // 4) Go back to A2.
-  web_contents()->GetController().GoBack();
-  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
-  ExpectDisabledWithReason(kDisabledReasonForTest, FROM_HERE);
-  ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
-                         kDisableForRenderFrameHostCalled},
-                    FROM_HERE);
-}
-
 // Confirm that same-document navigation and not history-navigation does not
 // record metrics.
 IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, MetricsNotRecorded) {
diff --git a/content/browser/browsing_instance.cc b/content/browser/browsing_instance.cc
index bf11dcb..d7e1a9e 100644
--- a/content/browser/browsing_instance.cc
+++ b/content/browser/browsing_instance.cc
@@ -84,22 +84,15 @@
   return instance;
 }
 
-void BrowsingInstance::GetSiteAndLockForURL(const GURL& url,
-                                            bool allow_default_instance,
-                                            GURL* site_url,
-                                            GURL* lock_url) {
+SiteInfo BrowsingInstance::GetSiteInfoForURL(const GURL& url,
+                                             bool allow_default_instance) {
   scoped_refptr<SiteInstanceImpl> site_instance =
       GetSiteInstanceForURLHelper(url, allow_default_instance);
 
-  if (site_instance) {
-    *site_url = site_instance->GetSiteURL();
-    *lock_url = site_instance->lock_url();
-    return;
-  }
+  if (site_instance)
+    return site_instance->GetSiteInfo();
 
-  *site_url = GetSiteInfoForURL(url).site_url();
-  *lock_url =
-      SiteInstanceImpl::DetermineProcessLockURL(isolation_context_, url);
+  return SiteInstanceImpl::ComputeSiteInfo(isolation_context_, url);
 }
 
 bool BrowsingInstance::TrySettingDefaultSiteInstance(
diff --git a/content/browser/browsing_instance.h b/content/browser/browsing_instance.h
index 6b03c07..d2e734c2 100644
--- a/content/browser/browsing_instance.h
+++ b/content/browser/browsing_instance.h
@@ -116,18 +116,14 @@
       const GURL& url,
       bool allow_default_instance);
 
-  // Gets site and lock URLs for |url| that are identical with what these
-  // values would be if we called GetSiteInstanceForURL() with the same
-  // |url| and |allow_default_instance|. This method is used when we need this
-  // information, but do not want to create a SiteInstance yet.
-  // TODO(wjmaclean): Convert the |site_url| parameter to be SiteInfo. See
-  // https://crbug.com/1085275/#c2.
-  void GetSiteAndLockForURL(const GURL& url,
-                            bool allow_default_instance,
-                            GURL* site_url,
-                            GURL* lock_url);
+  // Returns a SiteInfo with site and process-lock URLs for |url| that are
+  // identical with what these values would be if we called
+  // GetSiteInstanceForURL() with the same |url| and |allow_default_instance|.
+  // This method is used when we need this information, but do not want to
+  // create a SiteInstance yet.
+  SiteInfo GetSiteInfoForURL(const GURL& url, bool allow_default_instance);
 
-  // Helper function used by GetSiteInstanceForURL() and GetSiteAndLockForURL()
+  // Helper function used by GetSiteInstanceForURL() and GetSiteInfoForURL()
   // that returns an existing SiteInstance from |site_instance_map_| or
   // returns |default_site_instance_| if |allow_default_instance| is true and
   // other conditions are met. If there is no existing SiteInstance that is
diff --git a/content/browser/child_process_security_policy_impl.cc b/content/browser/child_process_security_policy_impl.cc
index ca111c5..2d9c4fc 100644
--- a/content/browser/child_process_security_policy_impl.cc
+++ b/content/browser/child_process_security_policy_impl.cc
@@ -23,7 +23,6 @@
 #include "content/browser/bad_message.h"
 #include "content/browser/isolated_origin_util.h"
 #include "content/browser/renderer_host/render_process_host_impl.h"
-#include "content/browser/site_instance_impl.h"
 #include "content/browser/webui/url_data_manager_backend.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_or_resource_context.h"
@@ -163,6 +162,44 @@
 
 }  // namespace
 
+// static
+ProcessLock ProcessLock::CreateForErrorPage() {
+  return ProcessLock(SiteInfo::CreateForErrorPage());
+}
+
+ProcessLock::ProcessLock(const SiteInfo& site_info) : site_info_(site_info) {}
+
+ProcessLock::ProcessLock() = default;
+
+bool ProcessLock::IsASiteOrOrigin() const {
+  const GURL& lock_url = ProcessLock::lock_url();
+  return lock_url.has_scheme() && lock_url.has_host();
+}
+
+bool ProcessLock::HasOpaqueOrigin() const {
+  DCHECK(!lock_url().is_empty());
+  return url::Origin::Create(lock_url()).opaque();
+}
+
+bool ProcessLock::MatchesOrigin(const url::Origin& origin) const {
+  url::Origin process_lock_origin = url::Origin::Create(lock_url());
+  return origin == process_lock_origin;
+}
+
+bool ProcessLock::operator==(const ProcessLock& rhs) const {
+  // As we add additional features, like site-/origin-keying, we'll expand this
+  // comparison.
+  // Note that this should *not* compare site_url() values from the SiteInfo,
+  // since those include effective URLs which may differ even if the actual
+  // document origins match. We use process_lock_url() comparisons to account
+  // for this.
+  return site_info_.process_lock_url() == rhs.site_info_.process_lock_url();
+}
+
+bool ProcessLock::operator!=(const ProcessLock& rhs) const {
+  return !(*this == rhs);
+}
+
 ChildProcessSecurityPolicyImpl::Handle::Handle()
     : child_id_(ChildProcessHost::kInvalidUniqueID) {}
 
@@ -443,11 +480,12 @@
     return false;
   }
 
-  void LockToOrigin(const GURL& lock_url,
-                    BrowsingInstanceId browsing_instance_id) {
-    DCHECK(origin_lock_.is_empty());
-    DCHECK_NE(SiteInstanceImpl::GetDefaultSiteURL(), lock_url);
-    origin_lock_ = lock_url;
+  void SetProcessLock(const ProcessLock& lock,
+                      BrowsingInstanceId browsing_instance_id) {
+    DCHECK(process_lock_.is_empty());
+    DCHECK_NE(SiteInstanceImpl::GetDefaultSiteURL(), process_lock_.lock_url());
+    process_lock_ = lock;
+    DCHECK(lowest_browsing_instance_id_.is_null());
     lowest_browsing_instance_id_ = browsing_instance_id;
   }
 
@@ -460,7 +498,7 @@
     }
   }
 
-  const GURL& origin_lock() const { return origin_lock_; }
+  const ProcessLock& process_lock() const { return process_lock_; }
 
   BrowsingInstanceId lowest_browsing_instance_id() {
     return lowest_browsing_instance_id_;
@@ -540,7 +578,7 @@
 
   bool can_send_midi_sysex_;
 
-  GURL origin_lock_;
+  ProcessLock process_lock_;
 
   // The ID of the BrowsingInstance which locked this process to |origin_lock|.
   // Only valid when |origin_lock_| is non-empty.
@@ -1032,8 +1070,8 @@
         GetContentClient()->browser()->DoesWebUISchemeRequireProcessLock(
             url.scheme());
     if (should_be_locked) {
-      const GURL& lock_url = GetOriginLock(child_id);
-      if (lock_url.is_empty() || !lock_url.SchemeIs(url.scheme()))
+      const ProcessLock lock = GetProcessLock(child_id);
+      if (lock.is_empty() || !lock.matches_scheme(url.scheme()))
         return false;
     }
   }
@@ -1360,10 +1398,14 @@
     // Check for special cases, like blob:null/ and data: URLs, where the
     // origin does not contain information to match against the process lock,
     // but using the whole URL can result in a process lock match.
-    const GURL expected_origin_lock =
+    // TODO(wjmaclean): at present, DetermineProcessLockURL() just returns the
+    // lock url, and not an entire ProcessLock (including the related SiteInfo),
+    // so below we have to just compare URLs. It would be nice to directly
+    // compare ProcessLock objects instead.
+    const GURL expected_process_lock_url =
         SiteInstanceImpl::DetermineProcessLockURL(isolation_context, url);
-    const GURL actual_origin_lock = GetOriginLock(child_id);
-    if (actual_origin_lock == expected_origin_lock)
+    const ProcessLock& actual_process_lock = GetProcessLock(child_id);
+    if (actual_process_lock.lock_url() == expected_process_lock_url)
       return CanCommitStatus::CAN_COMMIT_ORIGIN_AND_URL;
 
     return CanCommitStatus::CANNOT_COMMIT_URL;
@@ -1465,7 +1507,7 @@
   if (security_state)
     browser_or_resource_context = security_state->GetBrowserOrResourceContext();
 
-  GURL expected_process_lock;
+  GURL expected_process_lock_url;
   std::string failure_reason;
 
   if (!security_state) {
@@ -1476,27 +1518,35 @@
     IsolationContext isolation_context(
         security_state->lowest_browsing_instance_id(),
         browser_or_resource_context);
-    expected_process_lock =
+    // TODO(wjmaclean): at present, DetermineProcessLockURL() just returns the
+    // lock url, and not an entire ProcessLock (including the related SiteInfo),
+    // so below we have to just compare URLs. It would be nice to directly
+    // compare ProcessLock objects instead.
+    // NOTE: we can't use ComputeSiteInfo to get the lock from here, because we
+    // could be on the IO thread, where we aren't able to determine a SiteInfo's
+    // site URL.
+    expected_process_lock_url =
         SiteInstanceImpl::DetermineProcessLockURL(isolation_context, url);
 
-    GURL actual_process_lock = security_state->origin_lock();
+    ProcessLock actual_process_lock = security_state->process_lock();
     if (!actual_process_lock.is_empty()) {
       // Jail-style enforcement - a process with a lock can only access data
       // from origins that require exactly the same lock.
-      if (actual_process_lock == expected_process_lock)
+      if (actual_process_lock.lock_url() == expected_process_lock_url)
         return true;
 
       // TODO(acolwell, nasko): https://crbug.com/1029092: Ensure the precursor
       // of opaque origins matches the renderer's origin lock.
       if (url_is_precursor_of_opaque_origin) {
+        const GURL& lock_url = actual_process_lock.lock_url();
         // SitePerProcessBrowserTest.TwoBlobURLsWithNullOriginDontShareProcess.
-        if (actual_process_lock.SchemeIsBlob() &&
-            actual_process_lock.path_piece().starts_with("null/")) {
+        if (lock_url.SchemeIsBlob() &&
+            lock_url.path_piece().starts_with("null/")) {
           return true;
         }
 
         // DeclarativeApiTest.PersistRules.
-        if (actual_process_lock.SchemeIs(url::kDataScheme))
+        if (actual_process_lock.matches_scheme(url::kDataScheme))
           return true;
       }
 
@@ -1513,7 +1563,7 @@
       return true;
 #else
       // TODO(acolwell, lukasza): https://crbug.com/764958: Make it possible to
-      // call ShouldLockToOrigin (and GetSiteForURL?) on the IO thread.
+      // call ShouldLockProcess (and GetSiteForURL?) on the IO thread.
       if (BrowserThread::CurrentlyOn(BrowserThread::IO))
         return true;
       DCHECK_CURRENTLY_ON(BrowserThread::UI);
@@ -1533,7 +1583,7 @@
       }
 
       // TODO(alexmos, lukasza): https://crbug.com/764958: Consider making
-      // ShouldLockToOrigin work with |expected_process_lock| instead of
+      // ShouldLockProcess work with |expected_process_lock_url| instead of
       // |site_url|.
       GURL site_url =
           SiteInstanceImpl::ComputeSiteInfo(isolation_context, url).site_url();
@@ -1541,8 +1591,8 @@
       // A process with no lock can only access data from origins that do not
       // require a locked process.
       bool should_lock_target =
-          SiteInstanceImpl::ShouldLockToOrigin(isolation_context, site_url,
-                                               /* is_guest= */ false);
+          SiteInstanceImpl::ShouldLockProcess(isolation_context, site_url,
+                                              /* is_guest= */ false);
       if (!should_lock_target)
         return true;
       failure_reason = " citadel_enforcement";
@@ -1553,7 +1603,7 @@
   // Returning false here will result in a renderer kill.  Set some crash
   // keys that will help understand the circumstances of that kill.
   LogCanAccessDataForOriginCrashKeys(
-      expected_process_lock.possibly_invalid_spec(),
+      expected_process_lock_url.possibly_invalid_spec(),
       GetKilledProcessOriginLock(security_state), url.GetOrigin().spec(),
       failure_reason);
   return false;
@@ -1569,43 +1619,35 @@
   state->SetLowestBrowsingInstanceId(isolation_context.browsing_instance_id());
 }
 
-void ChildProcessSecurityPolicyImpl::LockToOrigin(
+void ChildProcessSecurityPolicyImpl::LockProcess(
     const IsolationContext& context,
     int child_id,
-    const GURL& lock_url) {
-  // LockToOrigin should only be called on the UI thread (OTOH, it is okay to
-  // call GetOriginLock from any thread).
+    const ProcessLock& process_lock) {
+  // LockProcess should only be called on the UI thread (OTOH, it is okay to
+  // call GetProcessLock from any thread).
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-#if DCHECK_IS_ON()
-  // Sanity-check that the |gurl| argument can be used as a lock.
-  RenderProcessHost* rph = RenderProcessHostImpl::FromID(child_id);
-  if (rph)  // |rph| can be null in unittests.
-    DCHECK_EQ(SiteInstanceImpl::DetermineProcessLockURL(context, lock_url),
-              lock_url);
-#endif
-
   base::AutoLock lock(lock_);
   auto state = security_state_.find(child_id);
   DCHECK(state != security_state_.end());
-  state->second->LockToOrigin(lock_url, context.browsing_instance_id());
+  state->second->SetProcessLock(process_lock, context.browsing_instance_id());
 }
 
 void ChildProcessSecurityPolicyImpl::LockProcessForTesting(
     const IsolationContext& isolation_context,
     int child_id,
     const GURL& url) {
-  auto lock_url =
-      SiteInstanceImpl::DetermineProcessLockURL(isolation_context, url);
-  LockToOrigin(isolation_context, child_id, lock_url);
+  SiteInfo site_info =
+      SiteInstanceImpl::ComputeSiteInfo(isolation_context, url);
+  LockProcess(isolation_context, child_id, ProcessLock(site_info));
 }
 
-GURL ChildProcessSecurityPolicyImpl::GetOriginLock(int child_id) {
+ProcessLock ChildProcessSecurityPolicyImpl::GetProcessLock(int child_id) {
   base::AutoLock lock(lock_);
   auto state = security_state_.find(child_id);
   if (state == security_state_.end())
-    return GURL();
-  return state->second->origin_lock();
+    return ProcessLock();
+  return state->second->process_lock();
 }
 
 void ChildProcessSecurityPolicyImpl::GrantPermissionsForFileSystem(
@@ -2174,13 +2216,13 @@
   if (!security_state)
     return "(child id not found)";
 
-  if (security_state->origin_lock().is_empty()) {
+  if (security_state->process_lock().is_empty()) {
     return security_state->GetBrowserOrResourceContext()
                ? "(empty)"
                : "(empty and null context)";
   }
 
-  return security_state->origin_lock().possibly_invalid_spec();
+  return security_state->process_lock().ToString();
 }
 
 void ChildProcessSecurityPolicyImpl::LogKilledProcessOriginLock(int child_id) {
diff --git a/content/browser/child_process_security_policy_impl.h b/content/browser/child_process_security_policy_impl.h
index 8de88ae..669d1f7 100644
--- a/content/browser/child_process_security_policy_impl.h
+++ b/content/browser/child_process_security_policy_impl.h
@@ -23,6 +23,7 @@
 #include "content/browser/can_commit_status.h"
 #include "content/browser/isolated_origin_util.h"
 #include "content/browser/isolation_context.h"
+#include "content/browser/site_instance_impl.h"
 #include "content/public/browser/child_process_security_policy.h"
 #include "storage/common/file_system/file_system_types.h"
 #include "url/origin.h"
@@ -47,7 +48,69 @@
 class BrowserContext;
 class IsolationContext;
 class ResourceContext;
-class SiteInstance;
+
+// ProcessLock is a core part of Site Isolation, which is used to determine
+// which documents are allowed to load in a process and which site data the
+// process is allowed to access, based on the SiteInfo principal. If a process
+// has a non-empty ProcessLock, documents with incompatible SiteInfos will not
+// be allowed into the process, and the process will not be able to access site
+// data from other sites.
+//
+// ProcessLock is currently defined in terms of a single SiteInfo with a process
+// lock URL, but it could be possible to define it in terms of multiple
+// SiteInfos that are compatible with each other (e.g., multiple extensions
+// sharing an extension process).
+//
+// TODO(wjmaclean): Move this into its own .h file.
+class CONTENT_EXPORT ProcessLock {
+ public:
+  // Error page processes are locked to a special error URL, to avoid loading
+  // real pages into the process.
+  static ProcessLock CreateForErrorPage();
+
+  explicit ProcessLock(const SiteInfo& site_info);
+  ProcessLock();
+
+  // An empty ProcessLock indicates that a process is not restricted to pages
+  // from a particular SiteInfo.
+  bool is_empty() const { return lock_url().is_empty(); }
+
+  // Returns the url that corresponds to the SiteInfo the lock is used with. It
+  // will always be the same as the site URL, except in cases where effective
+  // urls are in use. Always empty if the SiteInfo uses the default site url.
+  // TODO(wjmaclean): Delete this accessor once we get to the point where we can
+  // safely just compare ProcessLocks directly.
+  const GURL lock_url() const { return site_info_.process_lock_url(); }
+
+  // Returns whether lock_url() is at least at the granularity of a site (i.e.,
+  // a scheme plus eTLD+1, like https://google.com).  Also returns true if the
+  // lock is to a more specific origin (e.g., https://accounts.google.com), but
+  // not if the lock is empty or applies to an entire scheme (e.g., file://).
+  bool IsASiteOrOrigin() const;
+
+  bool matches_scheme(const std::string& scheme) const {
+    return scheme == lock_url().scheme();
+  }
+
+  // Returns true if lock_url() has an opaque origin.
+  bool HasOpaqueOrigin() const;
+
+  // Returns true if |origin| matches the lock's origin.
+  bool MatchesOrigin(const url::Origin& origin) const;
+
+  bool operator==(const ProcessLock& rhs) const;
+  bool operator!=(const ProcessLock& rhs) const;
+
+  std::string ToString() const {
+    return site_info_.process_lock_url().possibly_invalid_spec();
+  }
+
+ private:
+  // TODO(creis): Consider tracking multiple compatible SiteInfos in ProcessLock
+  // (e.g., multiple extensions). This can better restrict what the process has
+  // access to in cases that we don't currently use a ProcessLock.
+  SiteInfo site_info_;
+};
 
 class CONTENT_EXPORT ChildProcessSecurityPolicyImpl
     : public ChildProcessSecurityPolicy {
@@ -399,25 +462,25 @@
                                const IsolationContext& isolation_context);
 
   // Sets the process identified by |child_id| as only permitted to access data
-  // for the origin specified by |lock_url|. Most callers should use
-  // RenderProcessHostImpl::LockToOrigin instead of calling this directly.
-  // |isolation_context| provides the context, such as BrowsingInstance, from
-  // which this process was locked to origin.  This information is used when
-  // making isolation decisions for this process, such as determining which
-  // isolated origins pertain to it.
-  void LockToOrigin(const IsolationContext& isolation_context,
-                    int child_id,
-                    const GURL& lock_url);
+  // for the origin specified by |site_info|'s process_lock_url(). Most callers
+  // should use RenderProcessHostImpl::SetProcessLock instead of calling this
+  // directly. |isolation_context| provides the context, such as
+  // BrowsingInstance, from which this process locked was created. This
+  // information is used when making isolation decisions for this process, such
+  // as determining which isolated origins pertain to it.
+  void LockProcess(const IsolationContext& isolation_context,
+                   int child_id,
+                   const ProcessLock& process_lock);
 
   // Testing helper method that generates a lock_url from |url| and then
-  // calls LockToOrigin() with that lock URL.
+  // calls LockProcess() with that lock URL.
   void LockProcessForTesting(const IsolationContext& isolation_context,
                              int child_id,
                              const GURL& url);
 
-  // Retrieves the current origin lock of process |child_id|.  Returns an empty
-  // GURL if the process does not exist or if it is not locked to an origin.
-  GURL GetOriginLock(int child_id);
+  // Retrieves the current ProcessLock of process |child_id|.  Returns an empty
+  // lock if the process does not exist or if it is not locked.
+  ProcessLock GetProcessLock(int child_id);
 
   // Register FileSystem type and permission policy which should be used
   // for the type.  The |policy| must be a bitwise-or'd value of
diff --git a/content/browser/child_process_security_policy_unittest.cc b/content/browser/child_process_security_policy_unittest.cc
index 3ccc636..4078b87 100644
--- a/content/browser/child_process_security_policy_unittest.cc
+++ b/content/browser/child_process_security_policy_unittest.cc
@@ -85,12 +85,12 @@
   scoped_refptr<SiteInstanceImpl> site_instance =
       SiteInstanceImpl::CreateForURL(browser_context, url);
   if (site_instance->RequiresDedicatedProcess() &&
-      SiteInstanceImpl::ShouldLockToOrigin(site_instance->GetIsolationContext(),
-                                           site_instance->GetSiteURL(),
-                                           site_instance->IsGuest())) {
-    ChildProcessSecurityPolicyImpl::GetInstance()->LockToOrigin(
+      SiteInstanceImpl::ShouldLockProcess(site_instance->GetIsolationContext(),
+                                          site_instance->GetSiteURL(),
+                                          site_instance->IsGuest())) {
+    ChildProcessSecurityPolicyImpl::GetInstance()->LockProcess(
         site_instance->GetIsolationContext(), process_id,
-        site_instance->lock_url());
+        site_instance->GetProcessLock());
   }
 }
 
diff --git a/content/browser/devtools/devtools_instrumentation.cc b/content/browser/devtools/devtools_instrumentation.cc
index 120aefd..76b494c 100644
--- a/content/browser/devtools/devtools_instrumentation.cc
+++ b/content/browser/devtools/devtools_instrumentation.cc
@@ -654,7 +654,7 @@
     int process_id,
     int routing_id,
     const std::string& devtools_request_id,
-    const net::CookieAndLineStatusList& response_cookie_list,
+    const net::CookieAndLineAccessResultList& response_cookie_list,
     const std::vector<network::mojom::HttpRawHeaderPairPtr>& response_headers,
     const base::Optional<std::string>& response_headers_text) {
   FrameTreeNode* ftn = GetFtnForNetworkRequest(process_id, routing_id);
@@ -819,7 +819,7 @@
 
 void ReportSameSiteCookieIssue(
     RenderFrameHostImpl* render_frame_host_impl,
-    const net::CookieWithStatus& excluded_cookie,
+    const net::CookieWithAccessResult& excluded_cookie,
     const GURL& url,
     const net::SiteForCookies& site_for_cookies,
     blink::mojom::SameSiteCookieOperation operation,
@@ -844,8 +844,9 @@
       protocol::Audits::SameSiteCookieIssueDetails::Create()
           .SetCookie(std::move(affected_cookie))
           .SetCookieExclusionReasons(
-              BuildExclusionReasons(excluded_cookie.status))
-          .SetCookieWarningReasons(BuildWarningReasons(excluded_cookie.status))
+              BuildExclusionReasons(excluded_cookie.access_result.status))
+          .SetCookieWarningReasons(
+              BuildWarningReasons(excluded_cookie.access_result.status))
           .SetOperation(BuildCookieOperation(operation))
           .SetCookieUrl(url.spec())
           .SetRequest(std::move(affected_request))
diff --git a/content/browser/devtools/devtools_instrumentation.h b/content/browser/devtools/devtools_instrumentation.h
index 3746ac1..a8ad8c8 100644
--- a/content/browser/devtools/devtools_instrumentation.h
+++ b/content/browser/devtools/devtools_instrumentation.h
@@ -33,7 +33,6 @@
 namespace net {
 class SSLInfo;
 class X509Certificate;
-struct CookieWithStatus;
 struct QuicTransportError;
 }  // namespace net
 
@@ -142,7 +141,7 @@
     int process_id,
     int routing_id,
     const std::string& devtools_request_id,
-    const net::CookieAndLineStatusList& response_cookie_list,
+    const net::CookieAndLineAccessResultList& response_cookie_list,
     const std::vector<network::mojom::HttpRawHeaderPairPtr>& response_headers,
     const base::Optional<std::string>& response_headers_text);
 void OnCorsPreflightRequest(int32_t process_id,
@@ -181,7 +180,7 @@
 
 void ReportSameSiteCookieIssue(
     RenderFrameHostImpl* render_frame_host_impl,
-    const net::CookieWithStatus& excluded_cookie,
+    const net::CookieWithAccessResult& excluded_cookie,
     const GURL& url,
     const net::SiteForCookies& site_for_cookies,
     blink::mojom::SameSiteCookieOperation operation,
diff --git a/content/browser/devtools/protocol/emulation_handler.cc b/content/browser/devtools/protocol/emulation_handler.cc
index cebfbf3..1a8e5b84 100644
--- a/content/browser/devtools/protocol/emulation_handler.cc
+++ b/content/browser/devtools/protocol/emulation_handler.cc
@@ -21,6 +21,7 @@
 #include "services/device/public/cpp/geolocation/geoposition.h"
 #include "services/device/public/mojom/geolocation_context.mojom.h"
 #include "services/device/public/mojom/geoposition.mojom.h"
+#include "third_party/blink/public/mojom/widget/screen_orientation.mojom.h"
 #include "ui/events/gesture_detection/gesture_provider_config_helper.h"
 
 namespace content {
@@ -28,17 +29,17 @@
 
 namespace {
 
-blink::WebScreenOrientationType WebScreenOrientationTypeFromString(
+blink::mojom::ScreenOrientation WebScreenOrientationTypeFromString(
     const std::string& type) {
   if (type == Emulation::ScreenOrientation::TypeEnum::PortraitPrimary)
-    return blink::kWebScreenOrientationPortraitPrimary;
+    return blink::mojom::ScreenOrientation::kPortraitPrimary;
   if (type == Emulation::ScreenOrientation::TypeEnum::PortraitSecondary)
-    return blink::kWebScreenOrientationPortraitSecondary;
+    return blink::mojom::ScreenOrientation::kPortraitSecondary;
   if (type == Emulation::ScreenOrientation::TypeEnum::LandscapePrimary)
-    return blink::kWebScreenOrientationLandscapePrimary;
+    return blink::mojom::ScreenOrientation::kLandscapePrimary;
   if (type == Emulation::ScreenOrientation::TypeEnum::LandscapeSecondary)
-    return blink::kWebScreenOrientationLandscapeSecondary;
-  return blink::kWebScreenOrientationUndefined;
+    return blink::mojom::ScreenOrientation::kLandscapeSecondary;
+  return blink::mojom::ScreenOrientation::kUndefined;
 }
 
 base::Optional<content::DisplayFeature::Orientation>
@@ -228,14 +229,14 @@
                                    base::NumberToString(max_scale));
   }
 
-  blink::WebScreenOrientationType orientationType =
-      blink::kWebScreenOrientationUndefined;
+  blink::mojom::ScreenOrientation orientationType =
+      blink::mojom::ScreenOrientation::kUndefined;
   int orientationAngle = 0;
   if (screen_orientation.isJust()) {
     Emulation::ScreenOrientation* orientation = screen_orientation.fromJust();
     orientationType = WebScreenOrientationTypeFromString(
         orientation->GetType());
-    if (orientationType == blink::kWebScreenOrientationUndefined)
+    if (orientationType == blink::mojom::ScreenOrientation::kUndefined)
       return Response::InvalidParams("Invalid screen orientation type value");
     orientationAngle = orientation->GetAngle();
     if (orientationAngle < 0 || orientationAngle >= max_orientation_angle) {
diff --git a/content/browser/devtools/protocol/network_handler.cc b/content/browser/devtools/protocol/network_handler.cc
index 7af15a1..f68bbfc2b 100644
--- a/content/browser/devtools/protocol/network_handler.cc
+++ b/content/browser/devtools/protocol/network_handler.cc
@@ -664,13 +664,14 @@
 }
 
 std::unique_ptr<Array<Network::BlockedSetCookieWithReason>>
-BuildProtocolBlockedSetCookies(const net::CookieAndLineStatusList& net_list) {
+BuildProtocolBlockedSetCookies(
+    const net::CookieAndLineAccessResultList& net_list) {
   std::unique_ptr<Array<Network::BlockedSetCookieWithReason>> protocol_list =
       std::make_unique<Array<Network::BlockedSetCookieWithReason>>();
 
-  for (const net::CookieAndLineWithStatus& cookie : net_list) {
+  for (const net::CookieAndLineWithAccessResult& cookie : net_list) {
     std::unique_ptr<Array<Network::SetCookieBlockedReason>> blocked_reasons =
-        GetProtocolBlockedSetCookieReason(cookie.status);
+        GetProtocolBlockedSetCookieReason(cookie.access_result.status);
     if (!blocked_reasons->size())
       continue;
 
@@ -2132,7 +2133,7 @@
 
 void NetworkHandler::OnResponseReceivedExtraInfo(
     const std::string& devtools_request_id,
-    const net::CookieAndLineStatusList& response_cookie_list,
+    const net::CookieAndLineAccessResultList& response_cookie_list,
     const std::vector<network::mojom::HttpRawHeaderPairPtr>& response_headers,
     const base::Optional<std::string>& response_headers_text) {
   if (!enabled_)
diff --git a/content/browser/devtools/protocol/network_handler.h b/content/browser/devtools/protocol/network_handler.h
index e5f15a7..5e70e4e 100644
--- a/content/browser/devtools/protocol/network_handler.h
+++ b/content/browser/devtools/protocol/network_handler.h
@@ -202,7 +202,7 @@
       const std::vector<network::mojom::HttpRawHeaderPairPtr>& request_headers);
   void OnResponseReceivedExtraInfo(
       const std::string& devtools_request_id,
-      const net::CookieAndLineStatusList& response_cookie_list,
+      const net::CookieAndLineAccessResultList& response_cookie_list,
       const std::vector<network::mojom::HttpRawHeaderPairPtr>& response_headers,
       const base::Optional<std::string>& response_headers_text);
 
diff --git a/content/browser/download/download_browsertest.cc b/content/browser/download/download_browsertest.cc
index 78baea1..62cf65a 100644
--- a/content/browser/download/download_browsertest.cc
+++ b/content/browser/download/download_browsertest.cc
@@ -1336,7 +1336,7 @@
                               parameters.size, download->GetTargetFilePath());
   }
 
-  // Verifies parallel download completion.
+  // Kicks off the verifies parallel download completion
   void RunCompletionTest(TestDownloadHttpResponse::Parameters& parameters) {
     ErrorStreamCountingObserver observer;
     EXPECT_TRUE(
@@ -4306,6 +4306,62 @@
   RunResumptionTestWithParameters(received_slices, kTestRequestCount + 1,
                                   parameters);
 }
+
+#if defined(OS_WIN)
+// Flaky https://crbug.com/1105429, windows probably use a large receiving
+// buffer size and cause the first slice to start at a offset > 0.
+#define MAYBE_MiddleSliceDelayedError DISABLED_MiddleSliceDelayedError
+#else
+#define MAYBE_MiddleSliceDelayedError MiddleSliceDelayedError
+#endif
+// Verify that if the second request fails after the beginning request takes
+// over and completes its slice, download should complete.
+IN_PROC_BROWSER_TEST_F(ParallelDownloadTest, MAYBE_MiddleSliceDelayedError) {
+  scoped_refptr<TestFileErrorInjector> injector(
+      TestFileErrorInjector::Create(DownloadManagerForShell(shell())));
+
+  TestFileErrorInjector::FileErrorInfo err = {
+      TestFileErrorInjector::FILE_OPERATION_WRITE, 0,
+      download::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE};
+  err.data_write_offset = 1699050;
+  injector->InjectError(err);
+  TestDownloadHttpResponse::Parameters parameters;
+  parameters.etag = "ABC";
+  parameters.size = 5097152;
+  parameters.connection_type = net::HttpResponseInfo::CONNECTION_INFO_HTTP1_1;
+  // The 2nd response will be dalyed.
+  parameters.SetResponseForRangeRequest(1699000, 2000000, k404Response,
+                                        true /* is_transient */,
+                                        true /* delay_response */);
+
+  GURL url = TestDownloadHttpResponse::GetNextURLForDownload();
+  GURL server_url = embedded_test_server()->GetURL(url.host(), url.path());
+  TestRequestPauseHandler request_pause_handler;
+  parameters.on_pause_handler = request_pause_handler.GetOnPauseHandler();
+  // Send some data for the first request and pause it so download won't
+  // complete before other parallel requests are created.
+  parameters.pause_offset = kPauseOffset;
+  TestDownloadHttpResponse::StartServing(parameters, server_url);
+
+  download::DownloadItem* download =
+      StartDownloadAndReturnItem(shell(), server_url);
+
+  // Wait for the 3rd request to complete first.
+  test_response_handler()->WaitUntilCompletion(1);
+  // Now resume the first request and wait for it to complete.
+  request_pause_handler.Resume();
+  test_response_handler()->WaitUntilCompletion(2);
+  // Dispatch the delayed response, and wait for download to complete.
+  test_response_handler()->DispatchDelayedResponses();
+  WaitForCompletion(download);
+  test_response_handler()->WaitUntilCompletion(3u);
+  const TestDownloadResponseHandler::CompletedRequests& completed_requests =
+      test_response_handler()->completed_requests();
+  EXPECT_EQ(3u, completed_requests.size());
+  ReadAndVerifyFileContents(parameters.pattern_generator_seed, parameters.size,
+                            download->GetTargetFilePath());
+}
+
 // Test to verify that the browser-side enforcement of X-Frame-Options does
 // not impact downloads. Since XFO is only checked for subframes, this test
 // initiates a download in an iframe and expects it to succeed.
diff --git a/content/browser/frame_host/back_forward_cache_impl.cc b/content/browser/frame_host/back_forward_cache_impl.cc
index 0bfb680..f640ef50 100644
--- a/content/browser/frame_host/back_forward_cache_impl.cc
+++ b/content/browser/frame_host/back_forward_cache_impl.cc
@@ -121,46 +121,45 @@
   // TODO(https://crbug.com/1015784): Finalize disallowed feature list, and test
   // for each disallowed feature.
   constexpr uint64_t kAlwaysDisallowedFeatures =
-      FeatureToBit(WebSchedulerTrackedFeature::kWebSocket) |
-      FeatureToBit(WebSchedulerTrackedFeature::kWebRTC) |
+      FeatureToBit(WebSchedulerTrackedFeature::kAppBanner) |
+      FeatureToBit(WebSchedulerTrackedFeature::kBroadcastChannel) |
       FeatureToBit(WebSchedulerTrackedFeature::kContainsPlugins) |
       FeatureToBit(WebSchedulerTrackedFeature::kDedicatedWorkerOrWorklet) |
+      FeatureToBit(WebSchedulerTrackedFeature::kIdleManager) |
+      FeatureToBit(WebSchedulerTrackedFeature::kIndexedDBConnection) |
+      FeatureToBit(WebSchedulerTrackedFeature::kKeyboardLock) |
       FeatureToBit(
           WebSchedulerTrackedFeature::kOutstandingIndexedDBTransaction) |
-      FeatureToBit(
-          WebSchedulerTrackedFeature::kRequestedNotificationsPermission) |
-      FeatureToBit(WebSchedulerTrackedFeature::kRequestedMIDIPermission) |
+      FeatureToBit(WebSchedulerTrackedFeature::kPaymentManager) |
+      FeatureToBit(WebSchedulerTrackedFeature::kPictureInPicture) |
+      FeatureToBit(WebSchedulerTrackedFeature::kPortal) |
+      FeatureToBit(WebSchedulerTrackedFeature::kPrinting) |
       FeatureToBit(
           WebSchedulerTrackedFeature::kRequestedAudioCapturePermission) |
-      FeatureToBit(
-          WebSchedulerTrackedFeature::kRequestedVideoCapturePermission) |
       FeatureToBit(WebSchedulerTrackedFeature::
                        kRequestedBackForwardCacheBlockedSensors) |
       FeatureToBit(
           WebSchedulerTrackedFeature::kRequestedBackgroundWorkPermission) |
-      FeatureToBit(WebSchedulerTrackedFeature::kBroadcastChannel) |
-      FeatureToBit(WebSchedulerTrackedFeature::kIndexedDBConnection) |
-      FeatureToBit(WebSchedulerTrackedFeature::kWebGL) |
-      FeatureToBit(WebSchedulerTrackedFeature::kWebVR) |
-      FeatureToBit(WebSchedulerTrackedFeature::kWebXR) |
+      FeatureToBit(WebSchedulerTrackedFeature::kRequestedMIDIPermission) |
+      FeatureToBit(
+          WebSchedulerTrackedFeature::kRequestedNotificationsPermission) |
+      FeatureToBit(
+          WebSchedulerTrackedFeature::kRequestedVideoCapturePermission) |
       FeatureToBit(WebSchedulerTrackedFeature::kSharedWorker) |
-      FeatureToBit(WebSchedulerTrackedFeature::kWebXR) |
-      FeatureToBit(WebSchedulerTrackedFeature::kWebLocks) |
-      FeatureToBit(WebSchedulerTrackedFeature::kWebHID) |
-      FeatureToBit(WebSchedulerTrackedFeature::kWakeLock) |
-      FeatureToBit(WebSchedulerTrackedFeature::kWebShare) |
-      FeatureToBit(WebSchedulerTrackedFeature::kWebFileSystem) |
-      FeatureToBit(WebSchedulerTrackedFeature::kAppBanner) |
-      FeatureToBit(WebSchedulerTrackedFeature::kPrinting) |
-      FeatureToBit(WebSchedulerTrackedFeature::kWebDatabase) |
-      FeatureToBit(WebSchedulerTrackedFeature::kPictureInPicture) |
-      FeatureToBit(WebSchedulerTrackedFeature::kPortal) |
+      FeatureToBit(WebSchedulerTrackedFeature::kSmsService) |
       FeatureToBit(WebSchedulerTrackedFeature::kSpeechRecognizer) |
-      FeatureToBit(WebSchedulerTrackedFeature::kIdleManager) |
-      FeatureToBit(WebSchedulerTrackedFeature::kPaymentManager) |
       FeatureToBit(WebSchedulerTrackedFeature::kSpeechSynthesis) |
-      FeatureToBit(WebSchedulerTrackedFeature::kKeyboardLock) |
-      FeatureToBit(WebSchedulerTrackedFeature::kSmsService);
+      FeatureToBit(WebSchedulerTrackedFeature::kWakeLock) |
+      FeatureToBit(WebSchedulerTrackedFeature::kWebDatabase) |
+      FeatureToBit(WebSchedulerTrackedFeature::kWebFileSystem) |
+      FeatureToBit(WebSchedulerTrackedFeature::kWebGL) |
+      FeatureToBit(WebSchedulerTrackedFeature::kWebHID) |
+      FeatureToBit(WebSchedulerTrackedFeature::kWebLocks) |
+      FeatureToBit(WebSchedulerTrackedFeature::kWebRTC) |
+      FeatureToBit(WebSchedulerTrackedFeature::kWebShare) |
+      FeatureToBit(WebSchedulerTrackedFeature::kWebSocket) |
+      FeatureToBit(WebSchedulerTrackedFeature::kWebVR) |
+      FeatureToBit(WebSchedulerTrackedFeature::kWebXR);
 
   uint64_t result = kAlwaysDisallowedFeatures;
 
diff --git a/content/browser/frame_host/cookie_utils.cc b/content/browser/frame_host/cookie_utils.cc
index c1ed555..8736a3f 100644
--- a/content/browser/frame_host/cookie_utils.cc
+++ b/content/browser/frame_host/cookie_utils.cc
@@ -55,12 +55,14 @@
                            {},
                            /* blocked_by_policy=*/true});
 
-  for (auto& cookie_and_status : cookie_details->cookie_list) {
-    if (cookie_and_status.status.HasExclusionReason(
+  for (auto& cookie_and_access_result : cookie_details->cookie_list) {
+    if (cookie_and_access_result.access_result.status.HasExclusionReason(
             net::CookieInclusionStatus::EXCLUDE_USER_PREFERENCES)) {
-      blocked->cookie_list.push_back(std::move(cookie_and_status.cookie));
-    } else if (cookie_and_status.status.IsInclude()) {
-      allowed->cookie_list.push_back(std::move(cookie_and_status.cookie));
+      blocked->cookie_list.push_back(
+          std::move(cookie_and_access_result.cookie));
+    } else if (cookie_and_access_result.access_result.status.IsInclude()) {
+      allowed->cookie_list.push_back(
+          std::move(cookie_and_access_result.cookie));
     }
   }
 }
@@ -77,21 +79,21 @@
   bool samesite_none_insecure_cookies = false;
   bool breaking_context_downgrade = false;
 
-  for (const net::CookieWithStatus& excluded_cookie :
+  for (const net::CookieWithAccessResult& excluded_cookie :
        cookie_details->cookie_list) {
-    if (excluded_cookie.status.ShouldWarn()) {
+    if (excluded_cookie.access_result.status.ShouldWarn()) {
       samesite_treated_as_lax_cookies =
           samesite_treated_as_lax_cookies ||
-          excluded_cookie.status.HasWarningReason(
+          excluded_cookie.access_result.status.HasWarningReason(
               net::CookieInclusionStatus::
                   WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT) ||
-          excluded_cookie.status.HasWarningReason(
+          excluded_cookie.access_result.status.HasWarningReason(
               net::CookieInclusionStatus::
                   WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE);
 
       samesite_none_insecure_cookies =
           samesite_none_insecure_cookies ||
-          excluded_cookie.status.HasWarningReason(
+          excluded_cookie.access_result.status.HasWarningReason(
               net::CookieInclusionStatus::WARN_SAMESITE_NONE_INSECURE);
 
       devtools_instrumentation::ReportSameSiteCookieIssue(
@@ -103,13 +105,15 @@
           cookie_details->devtools_request_id);
     }
 
-    breaking_context_downgrade = breaking_context_downgrade ||
-                                 excluded_cookie.status.HasDowngradeWarning();
+    breaking_context_downgrade =
+        breaking_context_downgrade ||
+        excluded_cookie.access_result.status.HasDowngradeWarning();
 
-    if (excluded_cookie.status.HasDowngradeWarning()) {
+    if (excluded_cookie.access_result.status.HasDowngradeWarning()) {
       // Unlike with UMA, do not record cookies that have no downgrade warning.
       RecordContextDowngradeUKM(rfh, cookie_details->type,
-                                excluded_cookie.status, cookie_details->url);
+                                excluded_cookie.access_result.status,
+                                cookie_details->url);
     }
   }
 
diff --git a/content/browser/frame_host/navigation_controller_impl.cc b/content/browser/frame_host/navigation_controller_impl.cc
index 5342160..954d540 100644
--- a/content/browser/frame_host/navigation_controller_impl.cc
+++ b/content/browser/frame_host/navigation_controller_impl.cc
@@ -2238,13 +2238,15 @@
   FrameNavigationEntry* frame_entry = entry->GetFrameEntry(frame_tree_node);
   if (!frame_entry)
     return false;
+  ReloadType reload_type = ReloadType::NORMAL;
+  entry->set_reload_type(reload_type);
   std::unique_ptr<NavigationRequest> request = CreateNavigationRequestFromEntry(
-      frame_tree_node, entry, frame_entry, ReloadType::NORMAL,
+      frame_tree_node, entry, frame_entry, reload_type,
       false /* is_same_document_history_load */,
       false /* is_history_navigation_in_new_child */);
   if (!request)
     return false;
-  frame_tree_node->navigator().Navigate(std::move(request), ReloadType::NORMAL,
+  frame_tree_node->navigator().Navigate(std::move(request), reload_type,
                                         RestoreType::NONE);
   return true;
 }
diff --git a/content/browser/frame_host/navigation_controller_impl_browsertest.cc b/content/browser/frame_host/navigation_controller_impl_browsertest.cc
index d8a77b7..8109993 100644
--- a/content/browser/frame_host/navigation_controller_impl_browsertest.cc
+++ b/content/browser/frame_host/navigation_controller_impl_browsertest.cc
@@ -8639,9 +8639,8 @@
             error_site_instance->GetProcess()->GetID());
   EXPECT_EQ(GURL(kUnreachableWebDataURL), error_site_instance->GetSiteURL());
 
-  // Verify that the error page process is locked to origin.
-  EXPECT_EQ(GURL(kUnreachableWebDataURL),
-            policy->GetOriginLock(error_site_instance->GetProcess()->GetID()));
+  EXPECT_EQ(ProcessLock::CreateForErrorPage(),
+            policy->GetProcessLock(error_site_instance->GetProcess()->GetID()));
 }
 
 // Test to verify that LoadPostCommitErrorPage loads an error page in a subframe
diff --git a/content/browser/frame_host/navigation_request.cc b/content/browser/frame_host/navigation_request.cc
index 81d89ec1..bd1a6f2 100644
--- a/content/browser/frame_host/navigation_request.cc
+++ b/content/browser/frame_host/navigation_request.cc
@@ -478,7 +478,8 @@
   {
     ChildProcessSecurityPolicyImpl* policy =
         ChildProcessSecurityPolicyImpl::GetInstance();
-    GURL process_lock = policy->GetOriginLock(new_rfh->GetProcess()->GetID());
+    ProcessLock process_lock =
+        policy->GetProcessLock(new_rfh->GetProcess()->GetID());
     UMA_HISTOGRAM_BOOLEAN("Navigation.IsLockedProcess",
                           !process_lock.is_empty());
     if (common_params.url.SchemeIsHTTPOrHTTPS()) {
@@ -767,16 +768,8 @@
   // make network requests on behalf of the real origin).
   //
   // TODO(lukasza): Cover MHTML main frames here.
-  if (navigation_request->IsForMhtmlSubframe()) {
-    // TODO(lukasza): https://crbug.com/1056949: Stop using
-    // mhtml.subframe.invalid as the precursor (once https://crbug.com/1056949
-    // is debugged OR once network::VerifyRequestInitiatorLock starts to compare
-    // precursors).
-    url::Origin mhtml_subframe_origin =
-        url::Origin::Create(GURL("https://mhtml.subframe.invalid"))
-            .DeriveNewOpaqueOrigin();
-    return mhtml_subframe_origin;
-  }
+  if (navigation_request->IsForMhtmlSubframe())
+    return url::Origin();
 
   // Srcdoc subframes need to inherit their origin from their parent frame.
   if (navigation_request->GetURL().IsAboutSrcdoc()) {
@@ -788,19 +781,11 @@
     return parent->GetLastCommittedOrigin();
   }
 
-  // TODO(lukasza): https://crbug.com/1056949: Stop using
-  // browser.initiated.invalid as the precursor (once https://crbug.com/1056949
-  // is debugged OR once network::VerifyRequestInitiatorLock starts to compare
-  // precursors).
-  url::Origin browser_initiated_fallback_origin =
-      url::Origin::Create(GURL("https://browser.initiated.invalid"))
-          .DeriveNewOpaqueOrigin();
-
   // In cases not covered above, URLLoaderFactory should be associated with the
   // origin of |common_params.url| and/or |common_params.initiator_origin|.
-  return url::Origin::Resolve(common_params.url,
-                              common_params.initiator_origin.value_or(
-                                  browser_initiated_fallback_origin));
+  return url::Origin::Resolve(
+      common_params.url,
+      common_params.initiator_origin.value_or(url::Origin()));
 }
 
 }  // namespace
@@ -1093,7 +1078,12 @@
       navigation_ui_data_(std::move(navigation_ui_data)),
       state_(NOT_STARTED),
       restore_type_(entry ? entry->restore_type() : RestoreType::NONE),
-      reload_type_(entry ? entry->reload_type() : ReloadType::NONE),
+      // Some navigations, such as renderer-initiated subframe navigations,
+      // won't have a NavigationEntryImpl. Set |reload_type_| if applicable
+      // for them.
+      reload_type_(
+          entry ? entry->reload_type()
+                : NavigationTypeToReloadType(common_params_->navigation_type)),
       nav_entry_id_(entry ? entry->GetUniqueID() : 0),
       is_view_source_(false),
       bindings_(FrameNavigationEntry::kInvalidBindings),
@@ -4218,14 +4208,8 @@
 
   // Check that |result| origin is allowed to be accessed from the process that
   // is the target of this navigation.
-  //
-  // TODO(lukasza): https://crbug.com/1056949: Stop excluding opaque origins
-  // from the security checks once the *.invalid diagnostics are no longer
-  // needed.
-  if (target_frame->ShouldBypassSecurityChecksForErrorPage(this) ||
-      result.opaque()) {
+  if (target_frame->ShouldBypassSecurityChecksForErrorPage(this))
     return result;
-  }
   int process_id = target_frame->GetProcess()->GetID();
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
   CHECK(policy->CanAccessDataForOrigin(process_id, result));
@@ -4679,6 +4663,18 @@
   return static_cast<NavigationRequest*>(handle);
 }
 
+// static
+ReloadType NavigationRequest::NavigationTypeToReloadType(
+    mojom::NavigationType type) {
+  if (type == mojom::NavigationType::RELOAD)
+    return ReloadType::NORMAL;
+  if (type == mojom::NavigationType::RELOAD_BYPASSING_CACHE)
+    return ReloadType::BYPASSING_CACHE;
+  if (type == mojom::NavigationType::RELOAD_ORIGINAL_REQUEST_URL)
+    return ReloadType::ORIGINAL_REQUEST_URL;
+  return ReloadType::NONE;
+}
+
 bool NavigationRequest::IsNavigationStarted() const {
   return navigation_handle_id_;
 }
diff --git a/content/browser/frame_host/navigation_request.h b/content/browser/frame_host/navigation_request.h
index 746e5a1b..ccaa4324 100644
--- a/content/browser/frame_host/navigation_request.h
+++ b/content/browser/frame_host/navigation_request.h
@@ -225,6 +225,10 @@
 
   static NavigationRequest* From(NavigationHandle* handle);
 
+  // If |type| is a reload, returns the equivalent ReloadType. Otherwise returns
+  // ReloadType::NONE.
+  static ReloadType NavigationTypeToReloadType(mojom::NavigationType type);
+
   ~NavigationRequest() override;
 
   // Returns true if this request's URL matches |origin| and the request state
diff --git a/content/browser/frame_host/navigation_request_browsertest.cc b/content/browser/frame_host/navigation_request_browsertest.cc
index 98b3e2f..94d108c2 100644
--- a/content/browser/frame_host/navigation_request_browsertest.cc
+++ b/content/browser/frame_host/navigation_request_browsertest.cc
@@ -9,7 +9,9 @@
 #include "base/strings/stringprintf.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "content/browser/frame_host/debug_urls.h"
+#include "content/browser/frame_host/navigation_controller_impl.h"
 #include "content/browser/frame_host/navigation_request.h"
+#include "content/browser/frame_host/render_frame_host_impl.h"
 #include "content/browser/site_instance_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/public/browser/browser_task_traits.h"
@@ -1549,6 +1551,63 @@
   CheckHttpsUpgradedIframeNavigation(start_url, cross_site_iframe_secure_url);
 }
 
+IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest,
+                       BrowserInitiatedMainFrameReload) {
+  GURL url = embedded_test_server()->GetURL("/hello.html");
+  EXPECT_TRUE(NavigateToURL(shell(), url));
+
+  NavigationHandleObserver handle_observer(shell()->web_contents(), url);
+  TestNavigationObserver navigation_observer(shell()->web_contents(), 1);
+  shell()->Reload();
+  navigation_observer.Wait();
+
+  EXPECT_EQ(handle_observer.reload_type(), ReloadType::NORMAL);
+}
+
+IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest,
+                       BrowserInitiatedSubFrameReload) {
+  GURL url = embedded_test_server()->GetURL("/page_with_iframe.html");
+  EXPECT_TRUE(NavigateToURL(shell(), url));
+
+  NavigationHandleObserver handle_observer(
+      shell()->web_contents(), embedded_test_server()->GetURL("/title1.html"));
+  TestNavigationObserver navigation_observer(shell()->web_contents(), 1);
+
+  auto frames = shell()->web_contents()->GetMainFrame()->GetFramesInSubtree();
+  ASSERT_EQ(frames.size(), 2u);
+  auto* frame = static_cast<RenderFrameHostImpl*>(frames[1]);
+  auto* navigation_controller = static_cast<NavigationControllerImpl*>(
+      &shell()->web_contents()->GetController());
+  navigation_controller->ReloadFrame(frame->frame_tree_node());
+  navigation_observer.Wait();
+
+  EXPECT_EQ(handle_observer.reload_type(), ReloadType::NORMAL);
+  EXPECT_FALSE(handle_observer.is_main_frame());
+}
+
+IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest,
+                       RendererInitiatedMainFrameReload) {
+  GURL url = embedded_test_server()->GetURL("/hello.html");
+  EXPECT_TRUE(NavigateToURL(shell(), url));
+  NavigationHandleObserver observer(shell()->web_contents(), url);
+  EXPECT_TRUE(ExecuteScript(shell(), "location.reload();"));
+  EXPECT_EQ(observer.reload_type(), ReloadType::NORMAL);
+}
+
+IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest,
+                       RendererInitiatedSubFrameReload) {
+  GURL url = embedded_test_server()->GetURL("/page_with_iframe.html");
+  EXPECT_TRUE(NavigateToURL(shell(), url));
+
+  NavigationHandleObserver handle_observer(
+      shell()->web_contents(), embedded_test_server()->GetURL("/title1.html"));
+  EXPECT_TRUE(ExecuteScript(shell(),
+                            "document.getElementById('test_iframe')."
+                            "contentWindow.location.reload();"));
+  EXPECT_EQ(handle_observer.reload_type(), ReloadType::NORMAL);
+  EXPECT_FALSE(handle_observer.is_main_frame());
+}
+
 // Ensure that browser-initiated same-document navigations are detected and
 // don't issue network requests.  See crbug.com/663777.
 IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest,
diff --git a/content/browser/frame_host/navigation_request_unittest.cc b/content/browser/frame_host/navigation_request_unittest.cc
index 5310a9331..abc65a5 100644
--- a/content/browser/frame_host/navigation_request_unittest.cc
+++ b/content/browser/frame_host/navigation_request_unittest.cc
@@ -72,7 +72,7 @@
 
   void TearDown() override {
     // Release the |request_| before destroying the WebContents, to match
-    // the WebContentsObserverSequenceChecker expectations.
+    // the WebContentsObserverConsistencyChecker expectations.
     request_.reset();
     RenderViewHostImplTestHarness::TearDown();
   }
diff --git a/content/browser/frame_host/navigator.cc b/content/browser/frame_host/navigator.cc
index 403544b..b01723b 100644
--- a/content/browser/frame_host/navigator.cc
+++ b/content/browser/frame_host/navigator.cc
@@ -120,11 +120,11 @@
 
   ChildProcessSecurityPolicyImpl* security_policy =
       ChildProcessSecurityPolicyImpl::GetInstance();
-  GURL process_lock_url =
-      security_policy->GetOriginLock(render_frame_host->GetProcess()->GetID());
+  ProcessLock process_lock =
+      security_policy->GetProcessLock(render_frame_host->GetProcess()->GetID());
 
   // In the case of error page process, any URL is allowed to commit.
-  if (process_lock_url == GURL(kUnreachableWebDataURL))
+  if (process_lock == ProcessLock::CreateForErrorPage())
     return true;
 
   bool frame_has_bindings = ((render_frame_host->GetEnabledBindings() &
@@ -135,7 +135,7 @@
 
   // Embedders might disable locking for WebUI URLs, which is bad idea, however
   // this method should take this into account.
-  bool should_lock_to_origin = SiteInstanceImpl::ShouldLockToOrigin(
+  bool should_lock_process = SiteInstanceImpl::ShouldLockProcess(
       render_frame_host->GetSiteInstance()->GetIsolationContext(), url,
       render_frame_host->GetSiteInstance()->IsGuest());
 
@@ -154,7 +154,7 @@
 
     // Check whether the process must be locked and if so that the process lock
     // is indeed in place.
-    if (should_lock_to_origin && process_lock_url.is_empty())
+    if (should_lock_process && process_lock.is_empty())
       return false;
 
     // There must be a WebUI on the frame.
@@ -180,9 +180,8 @@
     url::Origin url_origin = url::Origin::Create(url.GetOrigin());
 
     // Verify |url| matches the origin of the process lock, if one is in place.
-    if (should_lock_to_origin) {
-      url::Origin process_lock_origin = url::Origin::Create(process_lock_url);
-      if (!url_origin.opaque() && process_lock_origin != url_origin)
+    if (should_lock_process) {
+      if (!url_origin.opaque() && !process_lock.MatchesOrigin(url_origin))
         return false;
     }
   }
@@ -990,6 +989,8 @@
               controller_->GetBrowserContext(),
               nullptr /* blob_url_loader_factory */,
               common_params.should_replace_current_entry));
+  entry->set_reload_type(NavigationRequest::NavigationTypeToReloadType(
+      common_params.navigation_type));
 
   controller_->SetPendingEntry(std::move(entry));
   if (delegate_)
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index 464556c..f4d53cf 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -1378,7 +1378,7 @@
   // wanted a factory associated to a navigation about to commit, the params
   // generated won't be correct. There is no good way of fixing this before
   // RenderDocumentHost (ie swapping RenderFrameHost on each navigation).
-  return CreateNetworkServiceDefaultFactoryInternal(
+  return CreateNetworkServiceDefaultFactoryAndObserve(
       CreateURLLoaderFactoryParamsForMainWorld(
           last_committed_origin_,
           mojo::Clone(last_committed_client_security_state_),
@@ -3532,18 +3532,7 @@
   mojo::PendingRemote<network::mojom::URLLoaderFactory> default_factory_remote;
   bool bypass_redirect_checks = false;
   if (recreate_default_url_loader_factory_after_network_service_crash_) {
-    mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter>
-        coep_reporter_remote;
-    if (coep_reporter_) {
-      coep_reporter_->Clone(
-          coep_reporter_remote.InitWithNewPipeAndPassReceiver());
-    }
-    bypass_redirect_checks = CreateNetworkServiceDefaultFactoryAndObserve(
-        CreateURLLoaderFactoryParamsForMainWorld(
-            last_committed_origin_,
-            mojo::Clone(last_committed_client_security_state_),
-            std::move(coep_reporter_remote),
-            DetermineAfterCommitWhetherToForbidTrustTokenRedemption(this)),
+    bypass_redirect_checks = CreateNetworkServiceDefaultFactory(
         default_factory_remote.InitWithNewPipeAndPassReceiver());
   }
 
@@ -5725,8 +5714,8 @@
   // TODO(creis): Remove this check after we've gathered enough information to
   // debug issues with browser-side security checks. https://crbug.com/931895.
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
-  const GURL& lock_url = GetSiteInstance()->lock_url();
-  if (lock_url != GURL(kUnreachableWebDataURL) &&
+  const ProcessLock process_lock = GetSiteInstance()->GetProcessLock();
+  if (process_lock != ProcessLock::CreateForErrorPage() &&
       common_params->url.IsStandard() &&
       !policy->CanAccessDataForOrigin(GetProcess()->GetID(),
                                       common_params->url) &&
@@ -5734,7 +5723,7 @@
     base::debug::SetCrashKeyString(
         base::debug::AllocateCrashKeyString("lock_url",
                                             base::debug::CrashKeySize::Size64),
-        lock_url.possibly_invalid_spec());
+        process_lock.ToString());
     base::debug::SetCrashKeyString(
         base::debug::AllocateCrashKeyString("commit_origin",
                                             base::debug::CrashKeySize::Size64),
@@ -5743,8 +5732,9 @@
         base::debug::AllocateCrashKeyString("is_main_frame",
                                             base::debug::CrashKeySize::Size32),
         frame_tree_node_->IsMainFrame() ? "true" : "false");
-    NOTREACHED() << "Commiting in incompatible process for URL: " << lock_url
-                 << " lock vs " << common_params->url.GetOrigin();
+    NOTREACHED() << "Commiting in incompatible process for URL: "
+                 << process_lock.lock_url() << " lock vs "
+                 << common_params->url.GetOrigin();
     base::debug::DumpWithoutCrashing();
   }
 
@@ -6198,20 +6188,9 @@
 
   // Error page will commit in an opaque origin.
   //
-  // The precursor of the opaque origin can be set arbitrarily, because:
-  // 1) we expect that the error page will not issue network requests
-  // 2) network::VerifyRequestInitiatorLock doesn't compare precursors
-  // This observation lets us improve debuggability by using a hardcoded
-  // precursor below.
-  // TODO(lukasza): https://crbug.com/1056949: Stop using error.page.invalid as
-  // the precursor (once https://crbug.com/1056949 is debugged OR once
-  // network::VerifyRequestInitiatorLock starts to compare precursors).
-  //
   // TODO(lukasza): https://crbug.com/888079: Use this origin when committing
   // later on.
-  url::Origin error_page_origin =
-      url::Origin::Create(GURL("https://error.page.invalid"))
-          .DeriveNewOpaqueOrigin();
+  url::Origin error_page_origin = url::Origin();
   isolation_info_ = net::IsolationInfo::CreateTransient();
 
   std::unique_ptr<blink::PendingURLLoaderFactoryBundle>
@@ -6909,11 +6888,11 @@
 }
 
 // static
-RenderFrameHost* RenderFrameHost::FromPlaceholderId(
+RenderFrameHost* RenderFrameHost::FromPlaceholderToken(
     int render_process_id,
-    int placeholder_routing_id) {
-  RenderFrameProxyHost* rfph =
-      RenderFrameProxyHost::FromID(render_process_id, placeholder_routing_id);
+    const base::UnguessableToken& placeholder_frame_token) {
+  RenderFrameProxyHost* rfph = RenderFrameProxyHost::FromFrameToken(
+      render_process_id, placeholder_frame_token);
   FrameTreeNode* node = rfph ? rfph->frame_tree_node() : nullptr;
   return node ? node->current_frame_host() : nullptr;
 }
@@ -7829,7 +7808,7 @@
       (is_same_document_navigation && IsLoadDataWithBaseURL(*params))) {
     // Allow bypass if the process isn't locked. Otherwise run normal checks.
     bypass_checks_for_webview = ChildProcessSecurityPolicyImpl::GetInstance()
-                                    ->GetOriginLock(process->GetID())
+                                    ->GetProcessLock(process->GetID())
                                     .is_empty();
   }
 
@@ -7847,7 +7826,8 @@
                     << " origin '" << params->origin << "'"
                     << " lock '"
                     << ChildProcessSecurityPolicyImpl::GetInstance()
-                           ->GetOriginLock(process->GetID())
+                           ->GetProcessLock(process->GetID())
+                           .ToString()
                     << "'";
         VLOG(1) << "Blocked URL " << params->url.spec();
         LogCannotCommitUrlCrashKeys(params->url, is_same_document_navigation,
@@ -7862,7 +7842,8 @@
                     << " origin '" << params->origin << "'"
                     << " lock '"
                     << ChildProcessSecurityPolicyImpl::GetInstance()
-                           ->GetOriginLock(process->GetID())
+                           ->GetProcessLock(process->GetID())
+                           .ToString()
                     << "'";
         DEBUG_ALIAS_FOR_ORIGIN(origin_debug_alias, params->origin);
         LogCannotCommitOriginCrashKeys(is_same_document_navigation,
@@ -8209,8 +8190,8 @@
   value->SetInteger("frame_tree_node", frame_tree_node_->frame_tree_node_id());
   value->SetInteger("site id", site_instance_->GetId());
   value->SetString("process lock", ChildProcessSecurityPolicyImpl::GetInstance()
-                                       ->GetOriginLock(process_->GetID())
-                                       .possibly_invalid_spec());
+                                       ->GetProcessLock(process_->GetID())
+                                       .ToString());
   value->SetString("origin", params->origin.Serialize());
   value->SetInteger("transition", params->transition);
 
@@ -8649,7 +8630,7 @@
   base::debug::SetCrashKeyString(
       base::debug::AllocateCrashKeyString("site_lock",
                                           base::debug::CrashKeySize::Size256),
-      GetSiteInstance()->lock_url().possibly_invalid_spec());
+      GetSiteInstance()->GetProcessLock().ToString());
 
   if (!GetSiteInstance()->IsDefaultSiteInstance()) {
     base::debug::SetCrashKeyString(
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h
index a9571cf..63dbfd4 100644
--- a/content/browser/frame_host/render_frame_host_impl.h
+++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -467,7 +467,7 @@
       GURL* blocked_url,
       network::mojom::SourceLocation* source_location) const override;
 
-  // ui::AXActionHandler:
+  // ui::AXActionHandlerBase:
   void PerformAction(const ui::AXActionData& data) override;
   bool RequiresPerformActionPointInPixels() const override;
 
diff --git a/content/browser/frame_host/render_frame_host_manager.cc b/content/browser/frame_host/render_frame_host_manager.cc
index f8066e3..928696e7 100644
--- a/content/browser/frame_host/render_frame_host_manager.cc
+++ b/content/browser/frame_host/render_frame_host_manager.cc
@@ -858,8 +858,9 @@
   // TODO(creis): Remove this check after we've gathered enough information to
   // debug issues with browser-side security checks. https://crbug.com/931895.
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
-  const GURL& lock_url = navigation_rfh->GetSiteInstance()->lock_url();
-  if (lock_url != GURL(kUnreachableWebDataURL) &&
+  const ProcessLock process_lock =
+      navigation_rfh->GetSiteInstance()->GetProcessLock();
+  if (process_lock != ProcessLock::CreateForErrorPage() &&
       request->common_params().url.IsStandard() &&
       !policy->CanAccessDataForOrigin(navigation_rfh->GetProcess()->GetID(),
                                       request->common_params().url) &&
@@ -867,7 +868,7 @@
     base::debug::SetCrashKeyString(
         base::debug::AllocateCrashKeyString("lock_url",
                                             base::debug::CrashKeySize::Size64),
-        lock_url.possibly_invalid_spec());
+        process_lock.ToString());
     base::debug::SetCrashKeyString(
         base::debug::AllocateCrashKeyString("commit_origin",
                                             base::debug::CrashKeySize::Size64),
@@ -880,8 +881,9 @@
         base::debug::AllocateCrashKeyString("use_current_rfh",
                                             base::debug::CrashKeySize::Size32),
         use_current_rfh ? "true" : "false");
-    NOTREACHED() << "Picked an incompatible process for URL: " << lock_url
-                 << " lock vs " << request->common_params().url.GetOrigin();
+    NOTREACHED() << "Picked an incompatible process for URL: "
+                 << process_lock.lock_url() << " lock vs "
+                 << request->common_params().url.GetOrigin();
     base::debug::DumpWithoutCrashing();
   }
 
diff --git a/content/browser/frame_host/render_frame_host_manager_browsertest.cc b/content/browser/frame_host/render_frame_host_manager_browsertest.cc
index 11a763a..597effd 100644
--- a/content/browser/frame_host/render_frame_host_manager_browsertest.cc
+++ b/content/browser/frame_host/render_frame_host_manager_browsertest.cc
@@ -4363,8 +4363,8 @@
 
     // Verify that the error page process is locked to origin
     EXPECT_EQ(
-        GURL(kUnreachableWebDataURL),
-        policy->GetOriginLock(error_site_instance->GetProcess()->GetID()));
+        ProcessLock::CreateForErrorPage(),
+        policy->GetProcessLock(error_site_instance->GetProcess()->GetID()));
     EXPECT_TRUE(
         IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
   }
@@ -4375,8 +4375,8 @@
   success_site_instance =
       shell()->web_contents()->GetMainFrame()->GetSiteInstance();
   EXPECT_NE(
-      GURL(kUnreachableWebDataURL),
-      policy->GetOriginLock(
+      ProcessLock::CreateForErrorPage(),
+      policy->GetProcessLock(
           shell()->web_contents()->GetSiteInstance()->GetProcess()->GetID()));
 
   {
@@ -4399,8 +4399,8 @@
 
     // Verify that the error page process is locked to origin
     EXPECT_EQ(
-        GURL(kUnreachableWebDataURL),
-        policy->GetOriginLock(error_site_instance->GetProcess()->GetID()));
+        ProcessLock::CreateForErrorPage(),
+        policy->GetProcessLock(error_site_instance->GetProcess()->GetID()));
   }
 }
 
@@ -4476,8 +4476,8 @@
   EXPECT_EQ(GURL(kUnreachableWebDataURL), error_site_instance->GetSiteURL());
 
   // Verify that the error page process is locked to origin
-  EXPECT_EQ(GURL(kUnreachableWebDataURL),
-            ChildProcessSecurityPolicyImpl::GetInstance()->GetOriginLock(
+  EXPECT_EQ(ProcessLock::CreateForErrorPage(),
+            ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock(
                 error_site_instance->GetProcess()->GetID()));
   EXPECT_TRUE(
       IsMainFrameOriginOpaqueAndCompatibleWithURL(new_shell, error_url));
@@ -4535,8 +4535,8 @@
 
   // Verify that the process is locked to origin
   EXPECT_EQ(
-      GURL(kUnreachableWebDataURL),
-      ChildProcessSecurityPolicyImpl::GetInstance()->GetOriginLock(
+      ProcessLock::CreateForErrorPage(),
+      ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock(
           shell()->web_contents()->GetSiteInstance()->GetProcess()->GetID()));
 }
 
@@ -4603,7 +4603,8 @@
       shell()->web_contents()->GetMainFrame()->GetSiteInstance()->GetSiteURL());
   int process_id =
       shell()->web_contents()->GetMainFrame()->GetProcess()->GetID();
-  EXPECT_EQ(GURL(kUnreachableWebDataURL), policy->GetOriginLock(process_id));
+  EXPECT_EQ(ProcessLock::CreateForErrorPage(),
+            policy->GetProcessLock(process_id));
   EXPECT_TRUE(IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
 
   // Reload while it will still fail to ensure it stays in the same process.
@@ -4640,8 +4641,8 @@
       GURL(kUnreachableWebDataURL),
       shell()->web_contents()->GetMainFrame()->GetSiteInstance()->GetSiteURL());
   EXPECT_NE(
-      GURL(kUnreachableWebDataURL),
-      policy->GetOriginLock(
+      ProcessLock::CreateForErrorPage(),
+      policy->GetProcessLock(
           shell()->web_contents()->GetSiteInstance()->GetProcess()->GetID()));
   EXPECT_EQ(expected_origin,
             shell()->web_contents()->GetMainFrame()->GetLastCommittedOrigin());
@@ -4666,8 +4667,8 @@
       GURL(kUnreachableWebDataURL),
       shell()->web_contents()->GetMainFrame()->GetSiteInstance()->GetSiteURL());
   EXPECT_EQ(
-      GURL(kUnreachableWebDataURL),
-      policy->GetOriginLock(
+      ProcessLock::CreateForErrorPage(),
+      policy->GetProcessLock(
           shell()->web_contents()->GetSiteInstance()->GetProcess()->GetID()));
   EXPECT_TRUE(IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
 
@@ -4688,8 +4689,8 @@
       GURL(kUnreachableWebDataURL),
       shell()->web_contents()->GetMainFrame()->GetSiteInstance()->GetSiteURL());
   EXPECT_NE(
-      GURL(kUnreachableWebDataURL),
-      policy->GetOriginLock(
+      ProcessLock::CreateForErrorPage(),
+      policy->GetProcessLock(
           shell()->web_contents()->GetSiteInstance()->GetProcess()->GetID()));
   EXPECT_EQ(expected_origin,
             shell()->web_contents()->GetMainFrame()->GetLastCommittedOrigin());
@@ -5020,8 +5021,8 @@
       GURL(kUnreachableWebDataURL),
       shell()->web_contents()->GetMainFrame()->GetSiteInstance()->GetSiteURL());
   EXPECT_EQ(
-      GURL(kUnreachableWebDataURL),
-      ChildProcessSecurityPolicyImpl::GetInstance()->GetOriginLock(
+      ProcessLock::CreateForErrorPage(),
+      ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock(
           shell()->web_contents()->GetSiteInstance()->GetProcess()->GetID()));
   EXPECT_EQ(2, nav_controller.GetEntryCount());
   EXPECT_TRUE(IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
@@ -5056,8 +5057,8 @@
       GURL(kUnreachableWebDataURL),
       shell()->web_contents()->GetMainFrame()->GetSiteInstance()->GetSiteURL());
   EXPECT_EQ(
-      GURL(kUnreachableWebDataURL),
-      ChildProcessSecurityPolicyImpl::GetInstance()->GetOriginLock(
+      ProcessLock::CreateForErrorPage(),
+      ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock(
           shell()->web_contents()->GetSiteInstance()->GetProcess()->GetID()));
   EXPECT_EQ(4, nav_controller.GetEntryCount());
   {
@@ -5101,8 +5102,8 @@
   EXPECT_FALSE(NavigateToURL(shell(), error_url));
   EXPECT_EQ(GURL(kUnreachableWebDataURL),
             web_contents->GetMainFrame()->GetSiteInstance()->GetSiteURL());
-  EXPECT_EQ(GURL(kUnreachableWebDataURL),
-            ChildProcessSecurityPolicyImpl::GetInstance()->GetOriginLock(
+  EXPECT_EQ(ProcessLock::CreateForErrorPage(),
+            ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock(
                 web_contents->GetSiteInstance()->GetProcess()->GetID()));
   EXPECT_EQ(2, nav_controller.GetEntryCount());
   EXPECT_TRUE(IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
@@ -5169,8 +5170,8 @@
 
   EXPECT_EQ(GURL(kUnreachableWebDataURL),
             web_contents->GetMainFrame()->GetSiteInstance()->GetSiteURL());
-  EXPECT_EQ(GURL(kUnreachableWebDataURL),
-            ChildProcessSecurityPolicyImpl::GetInstance()->GetOriginLock(
+  EXPECT_EQ(ProcessLock::CreateForErrorPage(),
+            ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock(
                 web_contents->GetSiteInstance()->GetProcess()->GetID()));
   EXPECT_TRUE(IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), url1));
 }
@@ -5199,8 +5200,8 @@
   EXPECT_FALSE(NavigateToURL(shell(), url2));
   EXPECT_EQ(GURL(kUnreachableWebDataURL),
             web_contents->GetMainFrame()->GetSiteInstance()->GetSiteURL());
-  EXPECT_EQ(GURL(kUnreachableWebDataURL),
-            ChildProcessSecurityPolicyImpl::GetInstance()->GetOriginLock(
+  EXPECT_EQ(ProcessLock::CreateForErrorPage(),
+            ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock(
                 web_contents->GetSiteInstance()->GetProcess()->GetID()));
   EXPECT_TRUE(IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), url2));
 
@@ -5226,8 +5227,8 @@
 
   EXPECT_NE(GURL(kUnreachableWebDataURL),
             web_contents->GetMainFrame()->GetSiteInstance()->GetSiteURL());
-  EXPECT_NE(GURL(kUnreachableWebDataURL),
-            ChildProcessSecurityPolicyImpl::GetInstance()->GetOriginLock(
+  EXPECT_NE(ProcessLock::CreateForErrorPage(),
+            ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock(
                 web_contents->GetSiteInstance()->GetProcess()->GetID()));
 }
 
@@ -5260,8 +5261,8 @@
       shell()->web_contents()->GetMainFrame()->GetSiteInstance();
   EXPECT_TRUE(observer.is_error());
   EXPECT_EQ(GURL(kUnreachableWebDataURL), error_site_instance->GetSiteURL());
-  EXPECT_EQ(GURL(kUnreachableWebDataURL),
-            ChildProcessSecurityPolicyImpl::GetInstance()->GetOriginLock(
+  EXPECT_EQ(ProcessLock::CreateForErrorPage(),
+            ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock(
                 error_site_instance->GetProcess()->GetID()));
   EXPECT_FALSE(ChildProcessSecurityPolicy::GetInstance()->HasWebUIBindings(
       error_site_instance->GetProcess()->GetID()));
@@ -7213,7 +7214,7 @@
   EXPECT_EQ(siteless_url, web_contents->GetMainFrame()->GetLastCommittedURL());
   RenderProcessHost* process1 = web_contents->GetMainFrame()->GetProcess();
   EXPECT_FALSE(web_contents->GetMainFrame()->GetSiteInstance()->HasSite());
-  EXPECT_EQ(GURL(), policy->GetOriginLock(process1->GetID()));
+  EXPECT_EQ(ProcessLock(), policy->GetProcessLock(process1->GetID()));
 
   // Now wait for second navigation to finish and ensure it also succeeds.
   foo_manager.WaitForNavigationFinished();
@@ -7227,7 +7228,9 @@
   EXPECT_NE(process1, process2);
   EXPECT_EQ(GURL("http://foo.com"),
             web_contents->GetMainFrame()->GetSiteInstance()->GetSiteURL());
-  EXPECT_EQ(GURL("http://foo.com"), policy->GetOriginLock(process2->GetID()));
+  EXPECT_EQ(
+      ProcessLock(SiteInfo(GURL("http://foo.com"), GURL("http://foo.com"))),
+      policy->GetProcessLock(process2->GetID()));
 
   // Ensure also that the foo.com process didn't change midway through the
   // navigation.
diff --git a/content/browser/frame_host/render_frame_proxy_host.cc b/content/browser/frame_host/render_frame_proxy_host.cc
index ba3aa600..2c7253a 100644
--- a/content/browser/frame_host/render_frame_proxy_host.cc
+++ b/content/browser/frame_host/render_frame_proxy_host.cc
@@ -200,8 +200,6 @@
   bool handled = true;
   IPC_BEGIN_MESSAGE_MAP(RenderFrameProxyHost, msg)
     IPC_MESSAGE_HANDLER(FrameHostMsg_Detach, OnDetach)
-    IPC_MESSAGE_HANDLER(FrameHostMsg_PrintCrossProcessSubframe,
-                        OnPrintCrossProcessSubframe)
     IPC_MESSAGE_UNHANDLED(handled = false)
   IPC_END_MESSAGE_MAP()
   return handled;
@@ -603,6 +601,12 @@
                                target_origin, std::move(message));
 }
 
+void RenderFrameProxyHost::PrintCrossProcessSubframe(const gfx::Rect& rect,
+                                                     int32_t document_cookie) {
+  RenderFrameHostImpl* rfh = frame_tree_node_->current_frame_host();
+  rfh->delegate()->PrintCrossProcessSubframe(rect, document_cookie, rfh);
+}
+
 void RenderFrameProxyHost::FocusPage() {
   frame_tree_node_->current_frame_host()->FocusPage();
 }
@@ -707,12 +711,6 @@
   return cross_process_frame_connector_->IsInert();
 }
 
-void RenderFrameProxyHost::OnPrintCrossProcessSubframe(const gfx::Rect& rect,
-                                                       int document_cookie) {
-  RenderFrameHostImpl* rfh = frame_tree_node_->current_frame_host();
-  rfh->delegate()->PrintCrossProcessSubframe(rect, document_cookie, rfh);
-}
-
 blink::AssociatedInterfaceProvider*
 RenderFrameProxyHost::GetRemoteAssociatedInterfacesTesting() {
   return GetRemoteAssociatedInterfaces();
diff --git a/content/browser/frame_host/render_frame_proxy_host.h b/content/browser/frame_host/render_frame_proxy_host.h
index 695f183..ea435859 100644
--- a/content/browser/frame_host/render_frame_proxy_host.h
+++ b/content/browser/frame_host/render_frame_proxy_host.h
@@ -169,6 +169,8 @@
       const base::string16& source_origin,
       const base::string16& target_origin,
       blink::TransferableMessage message) override;
+  void PrintCrossProcessSubframe(const gfx::Rect& rect,
+                                 int document_cookie) override;
 
   // blink::mojom::RemoteMainFrameHost overrides:
   void FocusPage() override;
@@ -200,7 +202,6 @@
 
   // IPC Message handlers.
   void OnDetach();
-  void OnPrintCrossProcessSubframe(const gfx::Rect& rect, int document_cookie);
 
   // IPC::Listener
   void OnAssociatedInterfaceRequest(
diff --git a/content/browser/isolated_origin_browsertest.cc b/content/browser/isolated_origin_browsertest.cc
index cf5127f..9216664 100644
--- a/content/browser/isolated_origin_browsertest.cc
+++ b/content/browser/isolated_origin_browsertest.cc
@@ -73,17 +73,22 @@
     return IsIsolatedOrigin(url::Origin::Create(url));
   }
 
+  ProcessLock ProcessLockFromUrl(const std::string& url) {
+    return ProcessLock(SiteInfo(GURL(url), GURL(url)));
+  }
+
   WebContentsImpl* web_contents() const {
     return static_cast<WebContentsImpl*>(shell()->web_contents());
   }
 
-  // Helper function that computes an appropriate origin lock that corresponds
+  // Helper function that computes an appropriate process lock that corresponds
   // to |url|'s origin (without converting to sites, handling effective URLs,
   // etc). This must be equivalent to what
   // SiteInstanceImpl::DetermineProcessLockURL() would return
   // for strict origin isolation.
-  GURL GetStrictOriginLock(const GURL& url) {
-    return url::Origin::Create(url).GetURL();
+  ProcessLock GetStrictProcessLock(const GURL& url) {
+    GURL origin_url = url::Origin::Create(url).GetURL();
+    return ProcessLock(SiteInfo(origin_url, origin_url));
   }
 
  private:
@@ -319,7 +324,7 @@
   GURL isolated_suborigin_url(
       https_server()->GetURL("isolated.foo.com", "/isolate_origin"));
   auto expected_isolated_suborigin_lock =
-      GetStrictOriginLock(isolated_suborigin_url);
+      GetStrictProcessLock(isolated_suborigin_url);
   EXPECT_TRUE(NavigateToURL(shell(), test_url));
   EXPECT_EQ(2u, shell()->web_contents()->GetAllFrames().size());
 
@@ -336,13 +341,15 @@
   EXPECT_EQ(
       expected_isolated_sub_origin,
       child_frame_node->current_frame_host()->GetSiteInstance()->GetSiteURL());
-  EXPECT_EQ(
-      expected_isolated_suborigin_lock,
-      child_frame_node->current_frame_host()->GetSiteInstance()->lock_url());
-  EXPECT_EQ(
-      child_frame_node->current_frame_host()->GetSiteInstance()->lock_url(),
-      ChildProcessSecurityPolicyImpl::GetInstance()->GetOriginLock(
-          child_frame_node->current_frame_host()->GetProcess()->GetID()));
+  EXPECT_EQ(expected_isolated_suborigin_lock,
+            child_frame_node->current_frame_host()
+                ->GetSiteInstance()
+                ->GetProcessLock());
+  EXPECT_EQ(child_frame_node->current_frame_host()
+                ->GetSiteInstance()
+                ->GetProcessLock(),
+            ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock(
+                child_frame_node->current_frame_host()->GetProcess()->GetID()));
 }
 
 // In this test the sub-origin isn't isolated because the origin policy doesn't
@@ -994,9 +1001,9 @@
   }
 
   // Helper function that creates an http URL for |host| that includes the test
-  // server's port and returns the strict origin lock for that URL.
-  GURL GetStrictOriginLockForHost(const std::string& host) {
-    return GetStrictOriginLock(embedded_test_server()->GetURL(host, "/"));
+  // server's port and returns the strict ProcessLock for that URL.
+  ProcessLock GetStrictProcessLockForHost(const std::string& host) {
+    return GetStrictProcessLock(embedded_test_server()->GetURL(host, "/"));
   }
 
  private:
@@ -1032,21 +1039,21 @@
   EXPECT_EQ(main_frame_id, grandchild_frame0_id);
 
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
-  EXPECT_EQ(GetStrictOriginLockForHost("foo.com"),
-            policy->GetOriginLock(main_frame_id));
-  EXPECT_EQ(GetStrictOriginLockForHost("mail.foo.com"),
-            policy->GetOriginLock(child_frame0_id));
-  EXPECT_EQ(GetStrictOriginLockForHost("bar.foo.com"),
-            policy->GetOriginLock(child_frame1_id));
-  EXPECT_EQ(GetStrictOriginLockForHost("foo.com"),
-            policy->GetOriginLock(child_frame2_id));
-  EXPECT_EQ(GetStrictOriginLockForHost("foo.com"),
-            policy->GetOriginLock(grandchild_frame0_id));
+  EXPECT_EQ(GetStrictProcessLockForHost("foo.com"),
+            policy->GetProcessLock(main_frame_id));
+  EXPECT_EQ(GetStrictProcessLockForHost("mail.foo.com"),
+            policy->GetProcessLock(child_frame0_id));
+  EXPECT_EQ(GetStrictProcessLockForHost("bar.foo.com"),
+            policy->GetProcessLock(child_frame1_id));
+  EXPECT_EQ(GetStrictProcessLockForHost("foo.com"),
+            policy->GetProcessLock(child_frame2_id));
+  EXPECT_EQ(GetStrictProcessLockForHost("foo.com"),
+            policy->GetProcessLock(grandchild_frame0_id));
 
   // Navigate child_frame1 to a new origin ... it should get its own process.
   FrameTreeNode* child_frame2_node = root->child_at(2);
   GURL foo_url(embedded_test_server()->GetURL("www.foo.com", "/title1.html"));
-  const auto expected_foo_lock = GetStrictOriginLock(foo_url);
+  const auto expected_foo_lock = GetStrictProcessLock(foo_url);
   NavigateFrameToURL(child_frame2_node, foo_url);
   EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
             child_frame2_node->current_frame_host()->GetSiteInstance());
@@ -1056,32 +1063,32 @@
   EXPECT_NE(main_frame->GetProcess()->GetID(),
             child_frame2->GetProcess()->GetID());
   EXPECT_EQ(expected_foo_lock,
-            policy->GetOriginLock(child_frame2->GetProcess()->GetID()));
+            policy->GetProcessLock(child_frame2->GetProcess()->GetID()));
 }
 
 IN_PROC_BROWSER_TEST_F(StrictOriginIsolationTest, MainframesAreIsolated) {
   GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
-  const auto expected_foo_lock = GetStrictOriginLock(foo_url);
+  const auto expected_foo_lock = GetStrictProcessLock(foo_url);
   EXPECT_TRUE(NavigateToURL(shell(), foo_url));
   EXPECT_EQ(1u, web_contents()->GetAllFrames().size());
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
 
   auto foo_process_id = web_contents()->GetMainFrame()->GetProcess()->GetID();
   SiteInstanceImpl* foo_site_instance = web_contents()->GetSiteInstance();
-  EXPECT_EQ(expected_foo_lock, foo_site_instance->lock_url());
-  EXPECT_EQ(foo_site_instance->lock_url(),
-            policy->GetOriginLock(foo_process_id));
+  EXPECT_EQ(expected_foo_lock, foo_site_instance->GetProcessLock());
+  EXPECT_EQ(foo_site_instance->GetProcessLock(),
+            policy->GetProcessLock(foo_process_id));
 
   GURL sub_foo_url =
       embedded_test_server()->GetURL("sub.foo.com", "/title1.html");
-  const auto expected_sub_foo_lock = GetStrictOriginLock(sub_foo_url);
+  const auto expected_sub_foo_lock = GetStrictProcessLock(sub_foo_url);
   EXPECT_TRUE(NavigateToURL(shell(), sub_foo_url));
   auto sub_foo_process_id =
       shell()->web_contents()->GetMainFrame()->GetProcess()->GetID();
   SiteInstanceImpl* sub_foo_site_instance = web_contents()->GetSiteInstance();
-  EXPECT_EQ(expected_sub_foo_lock, sub_foo_site_instance->lock_url());
-  EXPECT_EQ(sub_foo_site_instance->lock_url(),
-            policy->GetOriginLock(sub_foo_process_id));
+  EXPECT_EQ(expected_sub_foo_lock, sub_foo_site_instance->GetProcessLock());
+  EXPECT_EQ(sub_foo_site_instance->GetProcessLock(),
+            policy->GetProcessLock(sub_foo_process_id));
 
   EXPECT_NE(foo_process_id, sub_foo_process_id);
   EXPECT_NE(foo_site_instance->GetSiteURL(),
@@ -1090,7 +1097,7 @@
   // Now verify with a renderer-initiated navigation.
   GURL another_foo_url(
       embedded_test_server()->GetURL("another.foo.com", "/title2.html"));
-  const auto expected_another_foo_lock = GetStrictOriginLock(another_foo_url);
+  const auto expected_another_foo_lock = GetStrictProcessLock(another_foo_url);
   EXPECT_TRUE(NavigateToURLFromRenderer(shell(), another_foo_url));
   auto another_foo_process_id =
       shell()->web_contents()->GetMainFrame()->GetProcess()->GetID();
@@ -1098,9 +1105,10 @@
       web_contents()->GetSiteInstance();
   EXPECT_NE(another_foo_process_id, sub_foo_process_id);
   EXPECT_NE(another_foo_process_id, foo_process_id);
-  EXPECT_EQ(expected_another_foo_lock, another_foo_site_instance->lock_url());
-  EXPECT_EQ(another_foo_site_instance->lock_url(),
-            policy->GetOriginLock(another_foo_process_id));
+  EXPECT_EQ(expected_another_foo_lock,
+            another_foo_site_instance->GetProcessLock());
+  EXPECT_EQ(another_foo_site_instance->GetProcessLock(),
+            policy->GetProcessLock(another_foo_process_id));
   EXPECT_NE(another_foo_site_instance, foo_site_instance);
 
   EXPECT_NE(expected_foo_lock, expected_sub_foo_lock);
@@ -1618,16 +1626,14 @@
   EXPECT_NE(child->current_frame_host()->GetProcess(), foo_process);
 
   // Sanity-check IsSuitableHost values for the current processes.
-  BrowserContext* browser_context = web_contents()->GetBrowserContext();
   const IsolationContext& isolation_context =
       root->current_frame_host()->GetSiteInstance()->GetIsolationContext();
-  auto is_suitable_host = [browser_context, &isolation_context](
-                              RenderProcessHost* process, const GURL& url) {
-    GURL site_url(SiteInstance::GetSiteForURL(browser_context, url));
-    GURL lock_url(
-        SiteInstanceImpl::DetermineProcessLockURL(isolation_context, url));
+  auto is_suitable_host = [&isolation_context](RenderProcessHost* process,
+                                               const GURL& url) {
     return RenderProcessHostImpl::IsSuitableHost(
-        process, isolation_context, site_url, lock_url, /* is_guest= */ false);
+        process, isolation_context,
+        SiteInstanceImpl::ComputeSiteInfo(isolation_context, url),
+        /* is_guest= */ false);
   };
   EXPECT_TRUE(is_suitable_host(foo_process, foo_url));
   EXPECT_FALSE(is_suitable_host(foo_process, isolated_foo_url));
@@ -2896,8 +2902,9 @@
   // Since foo.com isn't isolated yet, its process shouldn't be locked to
   // anything.
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
-  EXPECT_EQ(GURL(), policy->GetOriginLock(
-                        root->current_frame_host()->GetProcess()->GetID()));
+  EXPECT_TRUE(
+      policy->GetProcessLock(root->current_frame_host()->GetProcess()->GetID())
+          .is_empty());
 
   // Start isolating foo.com.
   policy->AddIsolatedOrigins({url::Origin::Create(foo_url)},
@@ -2915,8 +2922,8 @@
   // The new window's process should be locked to "foo.com".
   int isolated_foo_com_process_id =
       second_root->current_frame_host()->GetProcess()->GetID();
-  EXPECT_EQ(GURL("http://foo.com"),
-            policy->GetOriginLock(isolated_foo_com_process_id));
+  EXPECT_EQ(ProcessLockFromUrl("http://foo.com"),
+            policy->GetProcessLock(isolated_foo_com_process_id));
 
   // Make sure both old and new foo.com processes can access cookies without
   // renderer kills.
@@ -2954,8 +2961,8 @@
   EXPECT_TRUE(NavigateToURL(second_shell, sub_foo_url));
   EXPECT_NE(isolated_foo_com_process_id,
             second_root->current_frame_host()->GetProcess()->GetID());
-  EXPECT_EQ(GURL("http://sub.foo.com"),
-            policy->GetOriginLock(
+  EXPECT_EQ(ProcessLockFromUrl("http://sub.foo.com"),
+            policy->GetProcessLock(
                 second_root->current_frame_host()->GetProcess()->GetID()));
 
   // Make sure that process can also access sub.foo.com cookies.
@@ -3126,12 +3133,12 @@
   EXPECT_TRUE(NavigateToURL(new_shell, foo_url));
   RenderProcessHost* new_process =
       new_shell->web_contents()->GetMainFrame()->GetProcess();
-  EXPECT_EQ(GURL("http://foo.com"),
-            policy->GetOriginLock(new_process->GetID()));
+  EXPECT_EQ(ProcessLockFromUrl("http://foo.com"),
+            policy->GetProcessLock(new_process->GetID()));
 
   // Go to foo.com in the older first tab, where foo.com does not require a
   // dedicated process.  Ensure that the existing locked foo.com process is
-  // *not* reused in that case (if that were the case, LockToOriginIfNeeded
+  // *not* reused in that case (if that were the case, LockProcessIfNeeded
   // would trigger a CHECK here).  Using a history navigation here ensures that
   // the SiteInstance (from session history) will have a foo.com site URL,
   // rather than a default site URL, since this case isn't yet handled by the
@@ -3217,8 +3224,8 @@
   EXPECT_EQ(root->current_frame_host()->GetProcess(),
             child->current_frame_host()->GetProcess());
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
-  EXPECT_EQ(GURL(),
-            policy->GetOriginLock(first_instance->GetProcess()->GetID()));
+  EXPECT_TRUE(
+      policy->GetProcessLock(first_instance->GetProcess()->GetID()).is_empty());
 
   // Start isolating foo.com.
   BrowserContext* context = shell()->web_contents()->GetBrowserContext();
@@ -3237,8 +3244,8 @@
   EXPECT_NE(first_instance, second_instance);
   EXPECT_FALSE(first_instance->IsRelatedSiteInstance(second_instance.get()));
   EXPECT_NE(first_instance->GetProcess(), second_instance->GetProcess());
-  EXPECT_EQ(GURL("http://foo.com"),
-            policy->GetOriginLock(second_instance->GetProcess()->GetID()));
+  EXPECT_EQ(ProcessLockFromUrl("http://foo.com"),
+            policy->GetProcessLock(second_instance->GetProcess()->GetID()));
 
   // The frame on that page should now be an OOPIF.
   child = root->child_at(0);
@@ -3265,8 +3272,8 @@
       root->current_frame_host()->GetSiteInstance();
   EXPECT_FALSE(first_instance->RequiresDedicatedProcess());
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
-  EXPECT_EQ(GURL(),
-            policy->GetOriginLock(first_instance->GetProcess()->GetID()));
+  EXPECT_TRUE(
+      policy->GetProcessLock(first_instance->GetProcess()->GetID()).is_empty());
 
   // Set a sessionStorage value, to sanity check that foo.com's session storage
   // will still be accessible after the BrowsingInstance swap.
@@ -3289,8 +3296,8 @@
   EXPECT_NE(first_instance, second_instance);
   EXPECT_FALSE(first_instance->IsRelatedSiteInstance(second_instance.get()));
   EXPECT_NE(first_instance->GetProcess(), second_instance->GetProcess());
-  EXPECT_EQ(GURL("http://foo.com"),
-            policy->GetOriginLock(second_instance->GetProcess()->GetID()));
+  EXPECT_EQ(ProcessLockFromUrl("http://foo.com"),
+            policy->GetProcessLock(second_instance->GetProcess()->GetID()));
 
   // The frame on that page should be an OOPIF.
   FrameTreeNode* child = root->child_at(0);
@@ -3336,8 +3343,8 @@
   // should still be able to communicate with the opener after the navigation.
   EXPECT_EQ(first_instance, root->current_frame_host()->GetSiteInstance());
   EXPECT_FALSE(first_instance->RequiresDedicatedProcess());
-  EXPECT_EQ(GURL(),
-            policy->GetOriginLock(first_instance->GetProcess()->GetID()));
+  EXPECT_TRUE(
+      policy->GetProcessLock(first_instance->GetProcess()->GetID()).is_empty());
 }
 
 // This test ensures that when a page becomes isolated in the middle of
@@ -3378,8 +3385,8 @@
   // opener after the navigation.
   EXPECT_EQ(first_instance, root->current_frame_host()->GetSiteInstance());
   EXPECT_FALSE(first_instance->RequiresDedicatedProcess());
-  EXPECT_EQ(GURL(),
-            policy->GetOriginLock(first_instance->GetProcess()->GetID()));
+  EXPECT_TRUE(
+      policy->GetProcessLock(first_instance->GetProcess()->GetID()).is_empty());
 }
 
 // This class allows intercepting the BroadcastChannelProvider::ConnectToChannel
@@ -3568,7 +3575,7 @@
   EXPECT_EQ(host, child1->current_frame_host()->GetProcess());
   EXPECT_EQ(host, child2->current_frame_host()->GetProcess());
   EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()
-                  ->GetOriginLock(host->GetID())
+                  ->GetProcessLock(host->GetID())
                   .is_empty());
 }
 
diff --git a/content/browser/loader/content_security_notifier.cc b/content/browser/loader/content_security_notifier.cc
index ef8f6d357..c2e27e7 100644
--- a/content/browser/loader/content_security_notifier.cc
+++ b/content/browser/loader/content_security_notifier.cc
@@ -32,8 +32,7 @@
       WebContents::FromRenderFrameHost(render_frame_host));
   if (!web_contents)
     return;
-  // TODO(nhiroki): |render_frame_host| argument could be removed.
-  web_contents->OnDidDisplayContentWithCertificateErrors(render_frame_host);
+  web_contents->OnDidDisplayContentWithCertificateErrors();
 }
 
 void ContentSecurityNotifier::NotifyInsecureContentRan(
diff --git a/content/browser/network_service_client.cc b/content/browser/network_service_client.cc
index 9c128e9..3723d4a 100644
--- a/content/browser/network_service_client.cc
+++ b/content/browser/network_service_client.cc
@@ -222,12 +222,12 @@
     int32_t process_id,
     int32_t routing_id,
     const std::string& devtools_request_id,
-    const net::CookieAndLineStatusList& cookies_with_status,
+    const net::CookieAndLineAccessResultList& cookies_with_access_result,
     std::vector<network::mojom::HttpRawHeaderPairPtr> headers,
     const base::Optional<std::string>& raw_response_headers) {
   devtools_instrumentation::OnResponseReceivedExtraInfo(
-      process_id, routing_id, devtools_request_id, cookies_with_status, headers,
-      raw_response_headers);
+      process_id, routing_id, devtools_request_id, cookies_with_access_result,
+      headers, raw_response_headers);
 }
 
 void NetworkServiceClient::OnCorsPreflightRequest(
diff --git a/content/browser/network_service_client.h b/content/browser/network_service_client.h
index b023b3b..edf6e12 100644
--- a/content/browser/network_service_client.h
+++ b/content/browser/network_service_client.h
@@ -60,7 +60,7 @@
       int32_t process_id,
       int32_t routing_id,
       const std::string& devtools_request_id,
-      const net::CookieAndLineStatusList& cookies_with_status,
+      const net::CookieAndLineAccessResultList& cookies_with_access_result,
       std::vector<network::mojom::HttpRawHeaderPairPtr> headers,
       const base::Optional<std::string>& raw_response_headers) override;
   void OnCorsPreflightRequest(int32_t process_id,
diff --git a/content/browser/payments/installed_payment_apps_finder_impl.cc b/content/browser/payments/installed_payment_apps_finder_impl.cc
new file mode 100644
index 0000000..0447ad6
--- /dev/null
+++ b/content/browser/payments/installed_payment_apps_finder_impl.cc
@@ -0,0 +1,116 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/payments/installed_payment_apps_finder_impl.h"
+
+#include "base/bind.h"
+#include "base/supports_user_data.h"
+#include "content/browser/payments/payment_app_context_impl.h"
+#include "content/browser/service_worker/service_worker_context_wrapper.h"
+#include "content/browser/storage_partition_impl.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/permission_controller.h"
+#include "content/public/browser/permission_type.h"
+#include "third_party/blink/public/mojom/permissions/permission_status.mojom.h"
+
+namespace content {
+namespace {
+
+const char kInstalledPaymentAppsFinderImplName[] =
+    "installed_payment_apps_finder_impl";
+
+void DidGetAllPaymentAppsOnCoreThread(
+    InstalledPaymentAppsFinder::GetAllPaymentAppsCallback callback,
+    InstalledPaymentAppsFinder::PaymentApps apps) {
+  GetUIThreadTaskRunner({})->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback), std::move(apps)));
+}
+
+void GetAllPaymentAppsOnCoreThread(
+    scoped_refptr<PaymentAppContextImpl> payment_app_context,
+    InstalledPaymentAppsFinder::GetAllPaymentAppsCallback callback) {
+  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+
+  payment_app_context->payment_app_database()->ReadAllPaymentApps(
+      base::BindOnce(&DidGetAllPaymentAppsOnCoreThread, std::move(callback)));
+}
+
+}  // namespace
+
+// static
+base::WeakPtr<InstalledPaymentAppsFinder>
+InstalledPaymentAppsFinder::GetInstance(BrowserContext* context) {
+  return InstalledPaymentAppsFinderImpl::GetInstance(context);
+}
+
+// static
+base::WeakPtr<InstalledPaymentAppsFinderImpl>
+InstalledPaymentAppsFinderImpl::GetInstance(BrowserContext* context) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  base::WeakPtr<InstalledPaymentAppsFinderImpl> result;
+  InstalledPaymentAppsFinderImpl* data =
+      static_cast<InstalledPaymentAppsFinderImpl*>(
+          context->GetUserData(kInstalledPaymentAppsFinderImplName));
+
+  if (!data) {
+    auto owned = base::WrapUnique(new InstalledPaymentAppsFinderImpl(context));
+    result = owned->weak_ptr_factory_.GetWeakPtr();
+    context->SetUserData(kInstalledPaymentAppsFinderImplName, std::move(owned));
+  } else {
+    result = data->weak_ptr_factory_.GetWeakPtr();
+  }
+
+  return result;
+}
+
+void InstalledPaymentAppsFinderImpl::GetAllPaymentApps(
+    GetAllPaymentAppsCallback callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
+      BrowserContext::GetDefaultStoragePartition(browser_context_));
+  scoped_refptr<PaymentAppContextImpl> payment_app_context =
+      partition->GetPaymentAppContext();
+
+  RunOrPostTaskOnThread(
+      FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
+      base::BindOnce(
+          &GetAllPaymentAppsOnCoreThread, payment_app_context,
+          base::BindOnce(
+              &InstalledPaymentAppsFinderImpl::CheckPermissionForPaymentApps,
+              weak_ptr_factory_.GetWeakPtr(), std::move(callback))));
+}
+
+void InstalledPaymentAppsFinderImpl::CheckPermissionForPaymentApps(
+    GetAllPaymentAppsCallback callback,
+    PaymentApps apps) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  PermissionController* permission_controller =
+      BrowserContext::GetPermissionController(browser_context_);
+  DCHECK(permission_controller);
+
+  PaymentApps permitted_apps;
+  for (auto& app : apps) {
+    GURL origin = app.second->scope.GetOrigin();
+    if (permission_controller->GetPermissionStatus(
+            PermissionType::PAYMENT_HANDLER, origin, origin) ==
+        blink::mojom::PermissionStatus::GRANTED) {
+      permitted_apps[app.first] = std::move(app.second);
+    }
+  }
+
+  std::move(callback).Run(std::move(permitted_apps));
+}
+
+InstalledPaymentAppsFinderImpl::InstalledPaymentAppsFinderImpl(
+    BrowserContext* context)
+    : browser_context_(context) {}
+
+InstalledPaymentAppsFinderImpl::~InstalledPaymentAppsFinderImpl() = default;
+
+}  // namespace content
diff --git a/content/browser/payments/installed_payment_apps_finder_impl.h b/content/browser/payments/installed_payment_apps_finder_impl.h
new file mode 100644
index 0000000..6e6e22d
--- /dev/null
+++ b/content/browser/payments/installed_payment_apps_finder_impl.h
@@ -0,0 +1,41 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_PAYMENTS_INSTALLED_PAYMENT_APPS_FINDER_IMPL_H_
+#define CONTENT_BROWSER_PAYMENTS_INSTALLED_PAYMENT_APPS_FINDER_IMPL_H_
+
+#include "content/browser/payments/payment_app_context_impl.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/installed_payment_apps_finder.h"
+
+namespace content {
+
+class CONTENT_EXPORT InstalledPaymentAppsFinderImpl
+    : public InstalledPaymentAppsFinder,
+      public base::SupportsUserData::Data {
+ public:
+  static base::WeakPtr<InstalledPaymentAppsFinderImpl> GetInstance(
+      BrowserContext* context);
+  ~InstalledPaymentAppsFinderImpl() override;
+
+  // Disallow copy and assign.
+  InstalledPaymentAppsFinderImpl(const InstalledPaymentAppsFinderImpl& other) =
+      delete;
+  InstalledPaymentAppsFinderImpl& operator=(
+      const InstalledPaymentAppsFinderImpl& other) = delete;
+
+  void GetAllPaymentApps(GetAllPaymentAppsCallback callback) override;
+
+ private:
+  explicit InstalledPaymentAppsFinderImpl(BrowserContext* context);
+  void CheckPermissionForPaymentApps(GetAllPaymentAppsCallback callback,
+                                     PaymentApps apps);
+
+  BrowserContext* browser_context_;
+  base::WeakPtrFactory<InstalledPaymentAppsFinderImpl> weak_ptr_factory_{this};
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_PAYMENTS_INSTALLED_PAYMENT_APPS_FINDER_IMPL_H_
diff --git a/content/browser/payments/payment_app_browsertest.cc b/content/browser/payments/payment_app_browsertest.cc
index e94d88d7..81f7db3a 100644
--- a/content/browser/payments/payment_app_browsertest.cc
+++ b/content/browser/payments/payment_app_browsertest.cc
@@ -8,6 +8,7 @@
 #include "base/run_loop.h"
 #include "content/browser/storage_partition_impl.h"
 #include "content/public/browser/browser_context.h"
+#include "content/public/browser/installed_payment_apps_finder.h"
 #include "content/public/browser/payment_app_provider.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_switches.h"
@@ -36,9 +37,10 @@
 using ::payments::mojom::PaymentRequestEventData;
 using ::payments::mojom::PaymentRequestEventDataPtr;
 
-void GetAllPaymentAppsCallback(base::OnceClosure done_callback,
-                               PaymentAppProvider::PaymentApps* out_apps,
-                               PaymentAppProvider::PaymentApps apps) {
+void GetAllPaymentAppsCallback(
+    base::OnceClosure done_callback,
+    InstalledPaymentAppsFinder::PaymentApps* out_apps,
+    InstalledPaymentAppsFinder::PaymentApps apps) {
   *out_apps = std::move(apps);
   std::move(done_callback).Run();
 }
@@ -110,11 +112,11 @@
 
   std::vector<int64_t> GetAllPaymentAppRegistrationIDs() {
     base::RunLoop run_loop;
-    PaymentAppProvider::PaymentApps apps;
-    PaymentAppProvider::GetInstance()->GetAllPaymentApps(
-        shell()->web_contents()->GetBrowserContext(),
-        base::BindOnce(&GetAllPaymentAppsCallback, run_loop.QuitClosure(),
-                       &apps));
+    InstalledPaymentAppsFinder::PaymentApps apps;
+    InstalledPaymentAppsFinder::GetInstance(
+        shell()->web_contents()->GetBrowserContext())
+        ->GetAllPaymentApps(base::BindOnce(&GetAllPaymentAppsCallback,
+                                           run_loop.QuitClosure(), &apps));
     run_loop.Run();
 
     std::vector<int64_t> registrationIds;
diff --git a/content/browser/payments/payment_app_provider_impl.cc b/content/browser/payments/payment_app_provider_impl.cc
index 95d1f576..ec0d855 100644
--- a/content/browser/payments/payment_app_provider_impl.cc
+++ b/content/browser/payments/payment_app_provider_impl.cc
@@ -16,7 +16,6 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
-#include "base/supports_user_data.h"
 #include "base/token.h"
 #include "components/payments/core/native_error_strings.h"
 #include "components/payments/core/payments_validators.h"
@@ -32,15 +31,12 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/devtools_background_services_context.h"
-#include "content/public/browser/permission_controller.h"
-#include "content/public/browser/permission_type.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_features.h"
 #include "mojo/public/cpp/bindings/message.h"
 #include "mojo/public/mojom/base/time.mojom.h"
 #include "third_party/blink/public/common/service_worker/service_worker_status_code.h"
-#include "third_party/blink/public/mojom/permissions/permission_status.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_container_type.mojom.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/gfx/image/image.h"
@@ -401,22 +397,6 @@
   PaymentAppProvider::AbortCallback callback_;
 };
 
-void DidGetAllPaymentAppsOnCoreThread(
-    PaymentAppProvider::GetAllPaymentAppsCallback callback,
-    PaymentAppProvider::PaymentApps apps) {
-  GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE, base::BindOnce(std::move(callback), std::move(apps)));
-}
-
-void GetAllPaymentAppsOnCoreThread(
-    scoped_refptr<PaymentAppContextImpl> payment_app_context,
-    PaymentAppProvider::GetAllPaymentAppsCallback callback) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
-
-  payment_app_context->payment_app_database()->ReadAllPaymentApps(
-      base::BindOnce(&DidGetAllPaymentAppsOnCoreThread, std::move(callback)));
-}
-
 void DidUpdatePaymentAppIconOnCoreThread(
     PaymentAppProvider::UpdatePaymentAppIconCallback callback,
     payments::mojom::PaymentHandlerStatus status) {
@@ -603,66 +583,6 @@
   }
 }
 
-// Callbacks for checking permissions asynchronously. Owned by the browser
-// context to avoid using the browser context after it has been freed. Deleted
-// after the callback is invoked.
-// Sample usage:
-//   PostTask(&PermissionChecker::CheckPermissionForPaymentApps,
-//       PermissionChecker::Create(browser_context), std::move(callback));
-class PermissionChecker : public base::SupportsUserData::Data {
- public:
-  static base::WeakPtr<PermissionChecker> Create(
-      BrowserContext* browser_context) {
-    auto owned = std::make_unique<PermissionChecker>(browser_context);
-    auto weak_pointer_result = owned->weak_ptr_factory_.GetWeakPtr();
-    void* key = owned.get();
-    browser_context->SetUserData(key, std::move(owned));
-    return weak_pointer_result;
-  }
-
-  // Do not use this method directly! Use the static PermissionChecker::Create()
-  // method instead. (The constructor must be public for std::make_unique<> in
-  // the Create() method.)
-  explicit PermissionChecker(BrowserContext* browser_context)
-      : browser_context_(browser_context) {}
-  ~PermissionChecker() override = default;
-
-  // Disallow copy and assign.
-  PermissionChecker(const PermissionChecker& other) = delete;
-  PermissionChecker& operator=(const PermissionChecker& other) = delete;
-
-  void CheckPermissionForPaymentApps(
-      PaymentAppProvider::GetAllPaymentAppsCallback callback,
-      PaymentAppProvider::PaymentApps apps) {
-    DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-    PermissionController* permission_controller =
-        BrowserContext::GetPermissionController(browser_context_);
-    DCHECK(permission_controller);
-
-    PaymentAppProvider::PaymentApps permitted_apps;
-    for (auto& app : apps) {
-      GURL origin = app.second->scope.GetOrigin();
-      if (permission_controller->GetPermissionStatus(
-              PermissionType::PAYMENT_HANDLER, origin, origin) ==
-          blink::mojom::PermissionStatus::GRANTED) {
-        permitted_apps[app.first] = std::move(app.second);
-      }
-    }
-
-    std::move(callback).Run(std::move(permitted_apps));
-
-    // Deletes this PermissionChecker object.
-    browser_context_->RemoveUserData(/*key=*/this);
-  }
-
- private:
-  // Owns this PermissionChecker object, so it's always valid.
-  BrowserContext* browser_context_;
-
-  base::WeakPtrFactory<PermissionChecker> weak_ptr_factory_{this};
-};
-
 void AbortInvokePaymentApp(WebContents* web_contents,
                            PaymentEventResponseType reason) {
   DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
@@ -814,25 +734,6 @@
   return base::Singleton<PaymentAppProviderImpl>::get();
 }
 
-void PaymentAppProviderImpl::GetAllPaymentApps(
-    BrowserContext* browser_context,
-    GetAllPaymentAppsCallback callback) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
-      BrowserContext::GetDefaultStoragePartition(browser_context));
-  scoped_refptr<PaymentAppContextImpl> payment_app_context =
-      partition->GetPaymentAppContext();
-
-  RunOrPostTaskOnThread(
-      FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
-      base::BindOnce(
-          &GetAllPaymentAppsOnCoreThread, payment_app_context,
-          base::BindOnce(&PermissionChecker::CheckPermissionForPaymentApps,
-                         PermissionChecker::Create(browser_context),
-                         std::move(callback))));
-}
-
 void PaymentAppProviderImpl::InvokePaymentApp(
     WebContents* web_contents,
     int64_t registration_id,
diff --git a/content/browser/payments/payment_app_provider_impl.h b/content/browser/payments/payment_app_provider_impl.h
index 11353c6..8cc0491 100644
--- a/content/browser/payments/payment_app_provider_impl.h
+++ b/content/browser/payments/payment_app_provider_impl.h
@@ -23,8 +23,6 @@
 
   // PaymentAppProvider implementation:
   // Should be accessed only on the UI thread.
-  void GetAllPaymentApps(BrowserContext* browser_context,
-                         GetAllPaymentAppsCallback callback) override;
   void InvokePaymentApp(WebContents* web_contents,
                         int64_t registration_id,
                         const url::Origin& sw_origin,
diff --git a/content/browser/payments/payment_app_provider_impl_unittest.cc b/content/browser/payments/payment_app_provider_impl_unittest.cc
index 806b090b..802427b 100644
--- a/content/browser/payments/payment_app_provider_impl_unittest.cc
+++ b/content/browser/payments/payment_app_provider_impl_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/bind.h"
 #include "base/macros.h"
 #include "base/run_loop.h"
+#include "content/browser/payments/installed_payment_apps_finder_impl.h"
 #include "content/browser/payments/payment_app_content_unittest_base.h"
 #include "content/browser/payments/payment_app_provider_impl.h"
 #include "content/public/browser/permission_type.h"
@@ -37,8 +38,9 @@
   *out_status = status;
 }
 
-void GetAllPaymentAppsCallback(PaymentAppProvider::PaymentApps* out_apps,
-                               PaymentAppProvider::PaymentApps apps) {
+void GetAllPaymentAppsCallback(
+    InstalledPaymentAppsFinder::PaymentApps* out_apps,
+    InstalledPaymentAppsFinder::PaymentApps apps) {
   *out_apps = std::move(apps);
 }
 
@@ -95,9 +97,9 @@
   }
 
   void GetAllPaymentApps(
-      PaymentAppProvider::GetAllPaymentAppsCallback callback) {
-    PaymentAppProviderImpl::GetInstance()->GetAllPaymentApps(
-        browser_context(), std::move(callback));
+      InstalledPaymentAppsFinder::GetAllPaymentAppsCallback callback) {
+    InstalledPaymentAppsFinderImpl::GetInstance(browser_context())
+        ->GetAllPaymentApps(std::move(callback));
     base::RunLoop().RunUntilIdle();
   }
 
@@ -153,7 +155,7 @@
                        payments::mojom::PaymentInstrument::New(),
                        base::BindOnce(&SetPaymentInstrumentCallback, &status));
 
-  PaymentAppProvider::PaymentApps apps;
+  InstalledPaymentAppsFinder::PaymentApps apps;
   GetAllPaymentApps(base::BindOnce(&GetAllPaymentAppsCallback, &apps));
   ASSERT_EQ(1U, apps.size());
 
@@ -176,7 +178,7 @@
                        payments::mojom::PaymentInstrument::New(),
                        base::BindOnce(&SetPaymentInstrumentCallback, &status));
 
-  PaymentAppProvider::PaymentApps apps;
+  InstalledPaymentAppsFinder::PaymentApps apps;
   GetAllPaymentApps(base::BindOnce(&GetAllPaymentAppsCallback, &apps));
   ASSERT_EQ(1U, apps.size());
 
@@ -215,7 +217,7 @@
                        payments::mojom::PaymentInstrument::New(),
                        base::BindOnce(&SetPaymentInstrumentCallback, &status));
 
-  PaymentAppProvider::PaymentApps apps;
+  InstalledPaymentAppsFinder::PaymentApps apps;
   GetAllPaymentApps(base::BindOnce(&GetAllPaymentAppsCallback, &apps));
   ASSERT_EQ(2U, apps.size());
 
@@ -260,7 +262,7 @@
   SetPaymentInstrument(manager2, "test_key3", std::move(instrument_3),
                        base::BindOnce(&SetPaymentInstrumentCallback, &status));
 
-  PaymentAppProvider::PaymentApps apps;
+  InstalledPaymentAppsFinder::PaymentApps apps;
   GetAllPaymentApps(base::BindOnce(&GetAllPaymentAppsCallback, &apps));
 
   ASSERT_EQ(2U, apps.size());
@@ -293,7 +295,7 @@
   SetPaymentInstrument(manager2, "test_key3", std::move(instrument_3),
                        base::BindOnce(&SetPaymentInstrumentCallback, &status));
 
-  PaymentAppProvider::PaymentApps apps;
+  InstalledPaymentAppsFinder::PaymentApps apps;
   GetAllPaymentApps(base::BindOnce(&GetAllPaymentAppsCallback, &apps));
 
   ASSERT_EQ(2U, apps.size());
@@ -318,7 +320,7 @@
                        payments::mojom::PaymentInstrument::New(),
                        base::BindOnce(&SetPaymentInstrumentCallback, &status));
 
-  PaymentAppProvider::PaymentApps apps;
+  InstalledPaymentAppsFinder::PaymentApps apps;
   GetAllPaymentApps(base::BindOnce(&GetAllPaymentAppsCallback, &apps));
   ASSERT_EQ(2U, apps.size());
 
diff --git a/content/browser/plugin_service_impl.cc b/content/browser/plugin_service_impl.cc
index 2003532..be63053 100644
--- a/content/browser/plugin_service_impl.cc
+++ b/content/browser/plugin_service_impl.cc
@@ -218,8 +218,9 @@
     // web-controlled content.  This is a defense-in-depth for verifying that
     // ShouldAllowPluginCreation called above is doing the right thing.
     auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
-    GURL renderer_lock = policy->GetOriginLock(render_process_id);
-    CHECK(!renderer_lock.SchemeIsHTTPOrHTTPS());
+    ProcessLock renderer_lock = policy->GetProcessLock(render_process_id);
+    CHECK(!renderer_lock.matches_scheme(url::kHttpScheme) &&
+          !renderer_lock.matches_scheme(url::kHttpsScheme));
     CHECK(embedder_origin.scheme() != url::kHttpScheme);
     CHECK(embedder_origin.scheme() != url::kHttpsScheme);
     CHECK(!embedder_origin.opaque());
diff --git a/content/browser/presentation/presentation_service_impl_unittest.cc b/content/browser/presentation/presentation_service_impl_unittest.cc
index 240f785..822d356 100644
--- a/content/browser/presentation/presentation_service_impl_unittest.cc
+++ b/content/browser/presentation/presentation_service_impl_unittest.cc
@@ -52,8 +52,8 @@
 
   void SetUp() override {
     RenderViewHostImplTestHarness::SetUp();
-    // This needed to keep the WebContentsObserverSequenceChecker checks happy
-    // for when AppendChild is called.
+    // This needed to keep the WebContentsObserverConsistencyChecker checks
+    // happy for when AppendChild is called.
     NavigateAndCommit(GURL("about:blank"));
 
     EXPECT_CALL(mock_delegate_, AddObserver(_, _, _)).Times(1);
diff --git a/content/browser/process_internals/process_internals_handler_impl.cc b/content/browser/process_internals/process_internals_handler_impl.cc
index 535427bd..0285ec6 100644
--- a/content/browser/process_internals/process_internals_handler_impl.cc
+++ b/content/browser/process_internals/process_internals_handler_impl.cc
@@ -46,7 +46,7 @@
 
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
   frame_info->site_instance->locked =
-      !policy->GetOriginLock(site_instance->GetProcess()->GetID()).is_empty();
+      !policy->GetProcessLock(site_instance->GetProcess()->GetID()).is_empty();
 
   frame_info->site_instance->site_url =
       site_instance->HasSite()
diff --git a/content/browser/push_messaging/push_messaging_manager.cc b/content/browser/push_messaging/push_messaging_manager.cc
index c5673426..7de8b92 100644
--- a/content/browser/push_messaging/push_messaging_manager.cc
+++ b/content/browser/push_messaging/push_messaging_manager.cc
@@ -609,7 +609,8 @@
 
   std::move(data.callback)
       .Run(status, blink::mojom::PushSubscription::New(
-                       endpoint, std::move(data.options), p256dh, auth));
+                       endpoint, base::nullopt /* expiration_time */,
+                       std::move(data.options), p256dh, auth));
 
   RecordRegistrationStatus(status);
 }
@@ -860,11 +861,14 @@
     blink::mojom::PushGetRegistrationStatus status =
         blink::mojom::PushGetRegistrationStatus::SUCCESS;
 
+    // TODO(crbug.com/1104215): Get expiration_time from push_service in
+    // Core::GetSubscriptionInfoOnUI
     RunOrPostTaskOnThread(
         FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
         base::BindOnce(std::move(callback), status,
                        blink::mojom::PushSubscription::New(
-                           endpoint, std::move(options), p256dh, auth)));
+                           endpoint, base::nullopt /* expiration_time */,
+                           std::move(options), p256dh, auth)));
 
     RecordGetRegistrationStatus(status);
   } else {
diff --git a/content/browser/renderer_host/code_cache_host_impl.cc b/content/browser/renderer_host/code_cache_host_impl.cc
index 184e225..e78d144d 100644
--- a/content/browser/renderer_host/code_cache_host_impl.cc
+++ b/content/browser/renderer_host/code_cache_host_impl.cc
@@ -57,38 +57,39 @@
   if (!resource_url.is_valid() || !resource_url.SchemeIsHTTPOrHTTPS())
     return base::nullopt;
 
-  GURL origin_lock =
-      ChildProcessSecurityPolicyImpl::GetInstance()->GetOriginLock(
+  ProcessLock process_lock =
+      ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock(
           render_process_id);
 
-  // Case 1: If origin lock is empty, it means the render process is not locked
+  // Case 1: If process lock is empty, it means the render process is not locked
   // to any origin. It is safe to just use the |resource_url| of the requested
   // resource as the key. Return an empty GURL as the second key.
-  if (origin_lock.is_empty())
+  if (process_lock.is_empty())
     return GURL::EmptyGURL();
 
-  // Case 2: Don't use invalid origin_lock as a key.
-  if (!origin_lock.is_valid())
+  // Case 2: Don't use invalid lock_url as a key.
+  if (!process_lock.lock_url().is_valid())
     return base::nullopt;
 
   // Case 2: Don't cache the code corresponding to opaque origins. The same
   // origin checks should always fail for opaque origins but the serialized
   // value of opaque origins does not ensure this.
-  if (url::Origin::Create(origin_lock).opaque())
+  if (process_lock.HasOpaqueOrigin())
     return base::nullopt;
 
-  // Case 3: origin_lock is used to enfore site-isolation in code caches.
+  // Case 3: process_lock_url is used to enfore site-isolation in code caches.
   // Http/https/chrome schemes are safe to be used as a secondary key. Other
   // schemes could be enabled if they are known to be safe and if it is
   // required to cache code from those origins.
   //
-  // file:// URLs will have a "file:" origin lock and would thus share a
+  // file:// URLs will have a "file:" process lock and would thus share a
   // cache across all file:// URLs. That would likely be ok for security, but
   // since this case is not performance sensitive we will keep things simple and
   // limit the cache to http/https/chrome processes.
-  if (origin_lock.SchemeIsHTTPOrHTTPS() ||
-      origin_lock.SchemeIs(content::kChromeUIScheme)) {
-    return origin_lock;
+  if (process_lock.matches_scheme(url::kHttpScheme) ||
+      process_lock.matches_scheme(url::kHttpsScheme) ||
+      process_lock.matches_scheme(content::kChromeUIScheme)) {
+    return process_lock.lock_url();
   }
 
   return base::nullopt;
diff --git a/content/browser/renderer_host/display_util.cc b/content/browser/renderer_host/display_util.cc
index b8b0c23..7bec9b6 100644
--- a/content/browser/renderer_host/display_util.cc
+++ b/content/browser/renderer_host/display_util.cc
@@ -72,7 +72,7 @@
 }
 
 // static
-ScreenOrientationValues DisplayUtil::GetOrientationTypeForMobile(
+blink::mojom::ScreenOrientation DisplayUtil::GetOrientationTypeForMobile(
     const display::Display& display) {
   int angle = display.PanelRotationAsDegree();
   const gfx::Rect& bounds = display.bounds();
@@ -86,25 +86,29 @@
 
   switch (angle) {
     case 0:
-      return natural_portrait ? SCREEN_ORIENTATION_VALUES_PORTRAIT_PRIMARY
-                              : SCREEN_ORIENTATION_VALUES_LANDSCAPE_PRIMARY;
+      return natural_portrait
+                 ? blink::mojom::ScreenOrientation::kPortraitPrimary
+                 : blink::mojom::ScreenOrientation::kLandscapePrimary;
     case 90:
-      return natural_portrait ? SCREEN_ORIENTATION_VALUES_LANDSCAPE_PRIMARY
-                              : SCREEN_ORIENTATION_VALUES_PORTRAIT_SECONDARY;
+      return natural_portrait
+                 ? blink::mojom::ScreenOrientation::kLandscapePrimary
+                 : blink::mojom::ScreenOrientation::kPortraitSecondary;
     case 180:
-      return natural_portrait ? SCREEN_ORIENTATION_VALUES_PORTRAIT_SECONDARY
-                              : SCREEN_ORIENTATION_VALUES_LANDSCAPE_SECONDARY;
+      return natural_portrait
+                 ? blink::mojom::ScreenOrientation::kPortraitSecondary
+                 : blink::mojom::ScreenOrientation::kLandscapeSecondary;
     case 270:
-      return natural_portrait ? SCREEN_ORIENTATION_VALUES_LANDSCAPE_SECONDARY
-                              : SCREEN_ORIENTATION_VALUES_PORTRAIT_PRIMARY;
+      return natural_portrait
+                 ? blink::mojom::ScreenOrientation::kLandscapeSecondary
+                 : blink::mojom::ScreenOrientation::kPortraitPrimary;
     default:
       NOTREACHED();
-      return SCREEN_ORIENTATION_VALUES_PORTRAIT_PRIMARY;
+      return blink::mojom::ScreenOrientation::kPortraitPrimary;
   }
 }
 
 // static
-ScreenOrientationValues DisplayUtil::GetOrientationTypeForDesktop(
+blink::mojom::ScreenOrientation DisplayUtil::GetOrientationTypeForDesktop(
     const display::Display& display) {
   static int primary_landscape_angle = -1;
   static int primary_portrait_angle = -1;
@@ -121,13 +125,13 @@
 
   if (is_portrait) {
     return primary_portrait_angle == angle
-               ? SCREEN_ORIENTATION_VALUES_PORTRAIT_PRIMARY
-               : SCREEN_ORIENTATION_VALUES_PORTRAIT_SECONDARY;
+               ? blink::mojom::ScreenOrientation::kPortraitPrimary
+               : blink::mojom::ScreenOrientation::kPortraitSecondary;
   }
 
   return primary_landscape_angle == angle
-             ? SCREEN_ORIENTATION_VALUES_LANDSCAPE_PRIMARY
-             : SCREEN_ORIENTATION_VALUES_LANDSCAPE_SECONDARY;
+             ? blink::mojom::ScreenOrientation::kLandscapePrimary
+             : blink::mojom::ScreenOrientation::kLandscapeSecondary;
 }
 
 }  // namespace content
diff --git a/content/browser/renderer_host/display_util.h b/content/browser/renderer_host/display_util.h
index 796cda7..0cc0fd9 100644
--- a/content/browser/renderer_host/display_util.h
+++ b/content/browser/renderer_host/display_util.h
@@ -23,11 +23,11 @@
   static void GetDefaultScreenInfo(ScreenInfo* screen_info);
 
   // Compute the orientation type of the display assuming it is a mobile device.
-  static ScreenOrientationValues GetOrientationTypeForMobile(
+  static blink::mojom::ScreenOrientation GetOrientationTypeForMobile(
       const display::Display& display);
 
   // Compute the orientation type of the display assuming it is a desktop.
-  static ScreenOrientationValues GetOrientationTypeForDesktop(
+  static blink::mojom::ScreenOrientation GetOrientationTypeForDesktop(
       const display::Display& display);
 };
 
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index 687698d..2b80b75f 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -853,8 +853,7 @@
       if (!host->MayReuseHost() ||
           !RenderProcessHostImpl::IsSuitableHost(
               host, site_instance->GetIsolationContext(),
-              site_instance->GetSiteInfo().site_url(),
-              site_instance->lock_url(), site_instance->IsGuest())) {
+              site_instance->GetSiteInfo(), site_instance->IsGuest())) {
         continue;
       }
 
@@ -1058,14 +1057,13 @@
       return nullptr;
 
     // It's possible that |host| is currently unsuitable for hosting
-    // |site_url|, for example if it was used for a ServiceWorker for a
+    // |site_instance|, for example if it was used for a ServiceWorker for a
     // nonexistent extension URL.  See https://crbug.com/782349 and
     // https://crbug.com/780661.
-    GURL site_url(site_instance->GetSiteInfo().site_url());
     if (!host->MayReuseHost() ||
         !RenderProcessHostImpl::IsSuitableHost(
-            host, site_instance->GetIsolationContext(), site_url,
-            site_instance->lock_url(), site_instance->IsGuest()))
+            host, site_instance->GetIsolationContext(),
+            site_instance->GetSiteInfo(), site_instance->IsGuest()))
       return nullptr;
 
     site_process_set_.erase(site_process_pair);
@@ -3142,13 +3140,13 @@
          pending_views_ == 0;
 }
 
-void RenderProcessHostImpl::LockToOrigin(
+void RenderProcessHostImpl::SetProcessLock(
     const IsolationContext& isolation_context,
-    const GURL& lock_url) {
-  ChildProcessSecurityPolicyImpl::GetInstance()->LockToOrigin(
-      isolation_context, GetID(), lock_url);
+    const ProcessLock& process_lock) {
+  ChildProcessSecurityPolicyImpl::GetInstance()->LockProcess(
+      isolation_context, GetID(), process_lock);
 
-  // Note that LockToOrigin is only called once per RenderProcessHostImpl
+  // Note that SetProcessLock is only called once per RenderProcessHostImpl
   // (when committing a navigation into an empty renderer).  Therefore, the
   // call to NotifyRendererIfLockedToSite below is insufficient for setting up
   // renderers respawned after crashing - this is handled by another call to
@@ -3156,19 +3154,19 @@
   NotifyRendererIfLockedToSite();
 }
 
-bool RenderProcessHostImpl::IsLockedToOriginForTesting() {
-  GURL lock_url =
-      ChildProcessSecurityPolicyImpl::GetInstance()->GetOriginLock(GetID());
-  return !lock_url.is_empty();
+bool RenderProcessHostImpl::IsProcessLockedForTesting() {
+  ProcessLock lock =
+      ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock(GetID());
+  return !lock.is_empty();
 }
 
 void RenderProcessHostImpl::NotifyRendererIfLockedToSite() {
-  GURL lock_url =
-      ChildProcessSecurityPolicyImpl::GetInstance()->GetOriginLock(GetID());
-  if (!lock_url.is_valid())
+  ProcessLock process_lock =
+      ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock(GetID());
+  if (!process_lock.lock_url().is_valid())
     return;
 
-  if (!SiteInstanceImpl::IsOriginLockASite(lock_url))
+  if (!process_lock.IsASiteOrOrigin())
     return;
 
   GetRendererInterface()->SetIsLockedToSite();
@@ -4059,8 +4057,7 @@
 bool RenderProcessHostImpl::IsSuitableHost(
     RenderProcessHost* host,
     const IsolationContext& isolation_context,
-    const GURL& site_url,
-    const GURL& lock_url,
+    const SiteInfo& site_info,
     const bool is_guest) {
   BrowserContext* browser_context =
       isolation_context.browser_or_resource_context().ToBrowserContext();
@@ -4084,8 +4081,8 @@
   // Check whether the given host and the intended site_url will be using the
   // same StoragePartition, since a RenderProcessHost can only support a
   // single StoragePartition.  This is relevant for packaged apps.
-  StoragePartition* dest_partition =
-      BrowserContext::GetStoragePartitionForSite(browser_context, site_url);
+  StoragePartition* dest_partition = BrowserContext::GetStoragePartitionForSite(
+      browser_context, site_info.site_url());
   if (!host->InSameStoragePartition(dest_partition))
     return false;
 
@@ -4093,7 +4090,7 @@
   // from |site_url| if an effective URL is used.
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
   bool host_has_web_ui_bindings = policy->HasWebUIBindings(host->GetID());
-  GURL process_lock = policy->GetOriginLock(host->GetID());
+  ProcessLock process_lock = policy->GetProcessLock(host->GetID());
   if (host->HostHasNotBeenUsed()) {
     // If the host hasn't been used, it won't have the expected WebUI bindings
     // or origin locks just *yet* - skip the checks in this case.  One example
@@ -4105,7 +4102,7 @@
     // WebUI checks.
     bool url_requires_web_ui_bindings =
         WebUIControllerFactoryRegistry::GetInstance()->UseWebUIBindingsForURL(
-            browser_context, site_url);
+            browser_context, site_info.site_url());
     if (host_has_web_ui_bindings != url_requires_web_ui_bindings)
       return false;
 
@@ -4114,17 +4111,18 @@
       // destination that doesn't require a dedicated process, even for the
       // same site. This can happen with dynamic isolated origins (see
       // https://crbug.com/950453).
-      if (!SiteInstanceImpl::ShouldLockToOrigin(isolation_context, site_url,
-                                                is_guest))
+      if (!SiteInstanceImpl::ShouldLockProcess(isolation_context,
+                                               site_info.site_url(), is_guest))
         return false;
 
       // If the destination requires a different process lock, this process
       // cannot be used.
-      if (process_lock != lock_url)
+      if (process_lock.lock_url() != site_info.process_lock_url())
         return false;
     } else {
-      if (!host->IsUnused() && SiteInstanceImpl::ShouldLockToOrigin(
-                                   isolation_context, site_url, is_guest)) {
+      if (!host->IsUnused() &&
+          SiteInstanceImpl::ShouldLockProcess(isolation_context,
+                                              site_info.site_url(), is_guest)) {
         // If this process has been used to host any other content, it cannot
         // be reused if the destination site requires a dedicated process and
         // should use a process locked to just that site.
@@ -4133,8 +4131,10 @@
     }
   }
 
-  if (!GetContentClient()->browser()->IsSuitableHost(host, site_url))
+  if (!GetContentClient()->browser()->IsSuitableHost(host,
+                                                     site_info.site_url())) {
     return false;
+  }
 
   // If this site_url is going to require a dedicated process, then check
   // whether this process has a pending navigation to a URL for which
@@ -4143,9 +4143,9 @@
   // URLs, since in that case the latter navigation could lock this process
   // before the commit for the siteless URL arrives, resulting in a renderer
   // kill. See https://crbug.com/970046.
-  if (SiteInstanceImpl::ShouldAssignSiteForURL(site_url) &&
+  if (SiteInstanceImpl::ShouldAssignSiteForURL(site_info.site_url()) &&
       SiteInstanceImpl::DoesSiteRequireDedicatedProcess(isolation_context,
-                                                        site_url)) {
+                                                        site_info.site_url())) {
     SiteProcessCountTracker* pending_tracker =
         static_cast<SiteProcessCountTracker*>(
             browser_context->GetUserData(kPendingSiteProcessCountTrackerKey));
@@ -4231,8 +4231,7 @@
     if (iter.GetCurrentValue()->MayReuseHost() &&
         RenderProcessHostImpl::IsSuitableHost(
             iter.GetCurrentValue(), site_instance->GetIsolationContext(),
-            site_instance->GetSiteInfo().site_url(), site_instance->lock_url(),
-            site_instance->IsGuest())) {
+            site_instance->GetSiteInfo(), site_instance->IsGuest())) {
       // The spare is always considered before process reuse.
       DCHECK_NE(iter.GetCurrentValue(),
                 SpareRenderProcessHostManager::GetInstance()
@@ -4278,8 +4277,7 @@
         iter.GetCurrentValue()->IsUnused() &&
         RenderProcessHostImpl::IsSuitableHost(
             iter.GetCurrentValue(), site_instance->GetIsolationContext(),
-            site_instance->GetSiteInfo().site_url(), site_instance->lock_url(),
-            site_instance->IsGuest())) {
+            site_instance->GetSiteInfo(), site_instance->IsGuest())) {
       return host;
     }
     iter.Advance();
@@ -4317,9 +4315,7 @@
     const GURL& url) {
   SiteInfo site_info =
       SiteInstanceImpl::ComputeSiteInfo(isolation_context, url);
-  GURL lock_url =
-      SiteInstanceImpl::DetermineProcessLockURL(isolation_context, url);
-  return GetSoleProcessHostForSite(isolation_context, site_info, lock_url,
+  return GetSoleProcessHostForSite(isolation_context, site_info,
                                    /* is_guest */ false);
 }
 
@@ -4327,7 +4323,6 @@
 RenderProcessHost* RenderProcessHostImpl::GetSoleProcessHostForSite(
     const IsolationContext& isolation_context,
     const SiteInfo& site_info,
-    const GURL& lock_url,
     const bool is_guest) {
   // Look up the map of site to process for the given browser_context.
   SiteProcessMap* map = GetSiteProcessMapForBrowserContext(
@@ -4339,8 +4334,7 @@
   const GURL& site_url = site_info.site_url();
   RenderProcessHost* host = map->FindProcess(site_url.possibly_invalid_spec());
   if (host && (!host->MayReuseHost() ||
-               !IsSuitableHost(host, isolation_context, site_url, lock_url,
-                               is_guest))) {
+               !IsSuitableHost(host, isolation_context, site_info, is_guest))) {
     // The registered process does not have an appropriate set of bindings for
     // the url.  Remove it from the map so we can register a better one.
     RecordAction(
@@ -4386,9 +4380,9 @@
   // First, attempt to reuse an existing RenderProcessHost if necessary.
   switch (process_reuse_policy) {
     case SiteInstanceImpl::ProcessReusePolicy::PROCESS_PER_SITE:
-      render_process_host = GetSoleProcessHostForSite(
-          site_instance->GetIsolationContext(), site_info,
-          site_instance->lock_url(), site_instance->IsGuest());
+      render_process_host =
+          GetSoleProcessHostForSite(site_instance->GetIsolationContext(),
+                                    site_info, site_instance->IsGuest());
       break;
     case SiteInstanceImpl::ProcessReusePolicy::REUSE_PENDING_OR_COMMITTED_SITE:
       render_process_host =
@@ -4457,8 +4451,7 @@
   // site.
   if (render_process_host &&
       !RenderProcessHostImpl::IsSuitableHost(
-          render_process_host, site_instance->GetIsolationContext(),
-          site_info.site_url(), site_instance->lock_url(),
+          render_process_host, site_instance->GetIsolationContext(), site_info,
           site_instance->IsGuest())) {
     base::debug::SetCrashKeyString(bad_message::GetRequestedSiteURLKey(),
                                    site_info.GetDebugString());
diff --git a/content/browser/renderer_host/render_process_host_impl.h b/content/browser/renderer_host/render_process_host_impl.h
index b7517779..40533d7 100644
--- a/content/browser/renderer_host/render_process_host_impl.h
+++ b/content/browser/renderer_host/render_process_host_impl.h
@@ -117,6 +117,7 @@
 class PermissionServiceContext;
 class PeerConnectionTrackerHost;
 class PluginRegistryImpl;
+class ProcessLock;
 class PushMessagingManager;
 class RenderFrameMessageFilter;
 class RenderProcessHostCreationObserver;
@@ -259,9 +260,9 @@
   void SetIsUsed() override;
 
   bool HostHasNotBeenUsed() override;
-  void LockToOrigin(const IsolationContext& isolation_context,
-                    const GURL& lock_url) override;
-  bool IsLockedToOriginForTesting() override;
+  void SetProcessLock(const IsolationContext& isolation_context,
+                      const ProcessLock& process_lock) override;
+  bool IsProcessLockedForTesting() override;
   void BindCacheStorage(
       const network::CrossOriginEmbedderPolicy& cross_origin_embedder_policy,
       mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter>
@@ -332,19 +333,16 @@
   static void FilterURL(RenderProcessHost* rph, bool empty_allowed, GURL* url);
 
   // Returns true if |host| is suitable for rendering a page in the given
-  // |isolation_context|, where the page would utilize |site_url| as its
-  // SiteInstance site URL, and its process would be locked to |lock_url|.
-  // |site_url| and |lock_url| may differ in cases where an effective URL is
-  // not the actual site that the process is locked to, which happens for
-  // hosted apps. |is_guest| should be set to true if the call is being made
-  // for a <webview> guest SiteInstance.
-  // TODO(wjmaclean): Rethink |how site_url|/|lock_url| parameters are passed.
-  // |site_url| will probably become a SiteInfo, but whether we want to combine
-  // |lock_url| into that or keep it separate needs to be decided.
+  // |isolation_context|, where the page would utilize |site_info.site_url()| as
+  // its SiteInstance site URL, and its process would be locked to
+  // |site_info.lock_url()|. Site and lock urls may differ in cases where
+  // an effective URL is not the actual site that the process is locked to,
+  // which happens for hosted apps. |is_guest| should be set to true if the call
+  // is being made for a <webview> guest SiteInstance.
+  // TODO(wjmaclean): move is_guest into SiteInfo at some point.
   static bool IsSuitableHost(RenderProcessHost* host,
                              const IsolationContext& isolation_context,
-                             const GURL& site_url,
-                             const GURL& lock_url,
+                             const SiteInfo& site_info,
                              bool is_guest);
 
   // Returns an existing RenderProcessHost for |url| in |isolation_context|, if
@@ -358,13 +356,12 @@
       const IsolationContext& isolation_context,
       const GURL& url);
 
-  // Variant of the above that takes in a SiteInstance site URL and the
-  // process's origin lock URL, when they are known. |is_guest| should be set
-  // to true if the call is being made for a <webview> guest SiteInstance.
+  // Variant of the above that takes in a SiteInfo. |is_guest| should be set to
+  // true if the call is being made for a <webview> guest SiteInstance.
+  // TODO(wjmaclean): Move is_guest into SiteInfo at some point.
   static RenderProcessHost* GetSoleProcessHostForSite(
       const IsolationContext& isolation_context,
       const SiteInfo& site_info,
-      const GURL& lock_url,
       const bool is_guest);
 
   // Registers the given |process| to be used for all sites identified by
diff --git a/content/browser/renderer_host/render_process_host_unittest.cc b/content/browser/renderer_host/render_process_host_unittest.cc
index dbf083b..3a8cb95 100644
--- a/content/browser/renderer_host/render_process_host_unittest.cc
+++ b/content/browser/renderer_host/render_process_host_unittest.cc
@@ -48,12 +48,10 @@
       SiteInstanceImpl::CreateForURL(browser_context(), test_url);
   EXPECT_FALSE(RenderProcessHostImpl::IsSuitableHost(
       &guest_host, site_instance->GetIsolationContext(),
-      site_instance->GetSiteURL(), site_instance->lock_url(),
-      site_instance->IsGuest()));
+      site_instance->GetSiteInfo(), site_instance->IsGuest()));
   EXPECT_TRUE(RenderProcessHostImpl::IsSuitableHost(
       process(), site_instance->GetIsolationContext(),
-      site_instance->GetSiteURL(), site_instance->lock_url(),
-      site_instance->IsGuest()));
+      site_instance->GetSiteInfo(), site_instance->IsGuest()));
   EXPECT_EQ(process(),
             RenderProcessHostImpl::GetExistingProcessHost(site_instance.get()));
 }
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index 0dd89a1..4c7812b 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -3268,7 +3268,7 @@
 
 void RenderWidgetHostImpl::SetScreenOrientationForTesting(
     uint16_t angle,
-    ScreenOrientationValues type) {
+    blink::mojom::ScreenOrientation type) {
   screen_orientation_angle_for_testing_ = angle;
   screen_orientation_type_for_testing_ = type;
   SynchronizeVisualProperties();
diff --git a/content/browser/renderer_host/render_widget_host_impl.h b/content/browser/renderer_host/render_widget_host_impl.h
index a65560df4..a5d2634 100644
--- a/content/browser/renderer_host/render_widget_host_impl.h
+++ b/content/browser/renderer_host/render_widget_host_impl.h
@@ -742,7 +742,7 @@
   void ForceFirstFrameAfterNavigationTimeout();
 
   void SetScreenOrientationForTesting(uint16_t angle,
-                                      ScreenOrientationValues type);
+                                      blink::mojom::ScreenOrientation type);
 
   // Requests Keyboard lock.  Note: the lock may not take effect until later.
   // If |codes| has no value then all keys will be locked, otherwise only the
@@ -1282,7 +1282,8 @@
   mojo::Remote<viz::mojom::InputTargetClient> input_target_client_;
 
   base::Optional<uint16_t> screen_orientation_angle_for_testing_;
-  base::Optional<ScreenOrientationValues> screen_orientation_type_for_testing_;
+  base::Optional<blink::mojom::ScreenOrientation>
+      screen_orientation_type_for_testing_;
 
   bool force_enable_zoom_ = false;
 
diff --git a/content/browser/renderer_host/render_widget_host_unittest.cc b/content/browser/renderer_host/render_widget_host_unittest.cc
index b4191b0..816401b 100644
--- a/content/browser/renderer_host/render_widget_host_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_unittest.cc
@@ -956,7 +956,8 @@
   screen_info.rect = blink::WebRect(0, 0, 800, 600);
   screen_info.available_rect = blink::WebRect(0, 0, 800, 600);
   screen_info.orientation_angle = 0;
-  screen_info.orientation_type = SCREEN_ORIENTATION_VALUES_PORTRAIT_PRIMARY;
+  screen_info.orientation_type =
+      blink::mojom::ScreenOrientation::kPortraitPrimary;
 
   sink_->ClearMessages();
   view_->SetScreenInfo(screen_info);
@@ -969,7 +970,8 @@
   EXPECT_FALSE(host_->visual_properties_ack_pending_);
 
   screen_info.orientation_angle = 180;
-  screen_info.orientation_type = SCREEN_ORIENTATION_VALUES_LANDSCAPE_PRIMARY;
+  screen_info.orientation_type =
+      blink::mojom::ScreenOrientation::kLandscapePrimary;
 
   sink_->ClearMessages();
   view_->SetScreenInfo(screen_info);
@@ -1012,7 +1014,8 @@
   screen_info.rect = kScreenBounds;
   screen_info.available_rect = kScreenBounds;
   screen_info.orientation_angle = 0;
-  screen_info.orientation_type = SCREEN_ORIENTATION_VALUES_PORTRAIT_PRIMARY;
+  screen_info.orientation_type =
+      blink::mojom::ScreenOrientation::kPortraitPrimary;
   view_->SetScreenInfo(screen_info);
 
   sink_->ClearMessages();
@@ -1060,7 +1063,8 @@
   screen_info.rect = screen_rect;
   screen_info.available_rect = screen_rect;
   screen_info.orientation_angle = 0;
-  screen_info.orientation_type = SCREEN_ORIENTATION_VALUES_PORTRAIT_PRIMARY;
+  screen_info.orientation_type =
+      blink::mojom::ScreenOrientation::kPortraitPrimary;
   view_->SetScreenInfo(screen_info);
 
   // Set a vertical display feature which must result in two window segments,
diff --git a/content/browser/renderer_host/render_widget_host_view_base.h b/content/browser/renderer_host/render_widget_host_view_base.h
index 8188ba4..6a17e52 100644
--- a/content/browser/renderer_host/render_widget_host_view_base.h
+++ b/content/browser/renderer_host/render_widget_host_view_base.h
@@ -32,7 +32,6 @@
 #include "content/public/common/screen_info.h"
 #include "content/public/common/widget_type.h"
 #include "services/viz/public/mojom/hit_test/hit_test_region_list.mojom.h"
-#include "third_party/blink/public/common/screen_orientation/web_screen_orientation_type.h"
 #include "third_party/blink/public/mojom/frame/intrinsic_sizing_info.mojom-forward.h"
 #include "third_party/blink/public/mojom/input/input_event_result.mojom-shared.h"
 #include "third_party/skia/include/core/SkImageInfo.h"
diff --git a/content/browser/renderer_host/render_widget_host_view_base_unittest.cc b/content/browser/renderer_host/render_widget_host_view_base_unittest.cc
index c2aeaf39..1e5c745 100644
--- a/content/browser/renderer_host/render_widget_host_view_base_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_base_unittest.cc
@@ -6,7 +6,6 @@
 
 #include "content/browser/renderer_host/display_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/public/common/screen_orientation/web_screen_orientation_type.h"
 #include "ui/display/display.h"
 
 namespace content {
@@ -30,57 +29,57 @@
   {
     display::Display display =
         CreateDisplay(100, 100, display::Display::ROTATE_0);
-    EXPECT_EQ(SCREEN_ORIENTATION_VALUES_PORTRAIT_PRIMARY,
+    EXPECT_EQ(blink::mojom::ScreenOrientation::kPortraitPrimary,
               DisplayUtil::GetOrientationTypeForMobile(display));
 
     display = CreateDisplay(200, 200, display::Display::ROTATE_90);
-    EXPECT_EQ(SCREEN_ORIENTATION_VALUES_LANDSCAPE_PRIMARY,
+    EXPECT_EQ(blink::mojom::ScreenOrientation::kLandscapePrimary,
               DisplayUtil::GetOrientationTypeForMobile(display));
 
     display = CreateDisplay(0, 0, display::Display::ROTATE_180);
-    EXPECT_EQ(SCREEN_ORIENTATION_VALUES_PORTRAIT_SECONDARY,
+    EXPECT_EQ(blink::mojom::ScreenOrientation::kPortraitSecondary,
               DisplayUtil::GetOrientationTypeForMobile(display));
 
     display = CreateDisplay(10000, 10000, display::Display::ROTATE_270);
-    EXPECT_EQ(SCREEN_ORIENTATION_VALUES_LANDSCAPE_SECONDARY,
+    EXPECT_EQ(blink::mojom::ScreenOrientation::kLandscapeSecondary,
               DisplayUtil::GetOrientationTypeForMobile(display));
   }
 
   // natural width > natural height.
   {
     display::Display display = CreateDisplay(1, 0, display::Display::ROTATE_0);
-    EXPECT_EQ(SCREEN_ORIENTATION_VALUES_LANDSCAPE_PRIMARY,
+    EXPECT_EQ(blink::mojom::ScreenOrientation::kLandscapePrimary,
               DisplayUtil::GetOrientationTypeForMobile(display));
 
     display = CreateDisplay(19999, 20000, display::Display::ROTATE_90);
-    EXPECT_EQ(SCREEN_ORIENTATION_VALUES_PORTRAIT_SECONDARY,
+    EXPECT_EQ(blink::mojom::ScreenOrientation::kPortraitSecondary,
               DisplayUtil::GetOrientationTypeForMobile(display));
 
     display = CreateDisplay(200, 100, display::Display::ROTATE_180);
-    EXPECT_EQ(SCREEN_ORIENTATION_VALUES_LANDSCAPE_SECONDARY,
+    EXPECT_EQ(blink::mojom::ScreenOrientation::kLandscapeSecondary,
               DisplayUtil::GetOrientationTypeForMobile(display));
 
     display = CreateDisplay(1, 10000, display::Display::ROTATE_270);
-    EXPECT_EQ(SCREEN_ORIENTATION_VALUES_PORTRAIT_PRIMARY,
+    EXPECT_EQ(blink::mojom::ScreenOrientation::kPortraitPrimary,
               DisplayUtil::GetOrientationTypeForMobile(display));
   }
 
   // natural width < natural height.
   {
     display::Display display = CreateDisplay(0, 1, display::Display::ROTATE_0);
-    EXPECT_EQ(SCREEN_ORIENTATION_VALUES_PORTRAIT_PRIMARY,
+    EXPECT_EQ(blink::mojom::ScreenOrientation::kPortraitPrimary,
               DisplayUtil::GetOrientationTypeForMobile(display));
 
     display = CreateDisplay(20000, 19999, display::Display::ROTATE_90);
-    EXPECT_EQ(SCREEN_ORIENTATION_VALUES_LANDSCAPE_PRIMARY,
+    EXPECT_EQ(blink::mojom::ScreenOrientation::kLandscapePrimary,
               DisplayUtil::GetOrientationTypeForMobile(display));
 
     display = CreateDisplay(100, 200, display::Display::ROTATE_180);
-    EXPECT_EQ(SCREEN_ORIENTATION_VALUES_PORTRAIT_SECONDARY,
+    EXPECT_EQ(blink::mojom::ScreenOrientation::kPortraitSecondary,
               DisplayUtil::GetOrientationTypeForMobile(display));
 
     display = CreateDisplay(10000, 1, display::Display::ROTATE_270);
-    EXPECT_EQ(SCREEN_ORIENTATION_VALUES_LANDSCAPE_SECONDARY,
+    EXPECT_EQ(blink::mojom::ScreenOrientation::kLandscapeSecondary,
               DisplayUtil::GetOrientationTypeForMobile(display));
   }
 }
@@ -95,30 +94,34 @@
   // natural width > natural height.
   {
     display::Display display = CreateDisplay(1, 0, display::Display::ROTATE_0);
-    ScreenOrientationValues landscape_1 =
+    blink::mojom::ScreenOrientation landscape_1 =
         DisplayUtil::GetOrientationTypeForDesktop(display);
-    EXPECT_TRUE(landscape_1 == SCREEN_ORIENTATION_VALUES_LANDSCAPE_PRIMARY ||
-                landscape_1 == SCREEN_ORIENTATION_VALUES_LANDSCAPE_SECONDARY);
+    EXPECT_TRUE(
+        landscape_1 == blink::mojom::ScreenOrientation::kLandscapePrimary ||
+        landscape_1 == blink::mojom::ScreenOrientation::kLandscapeSecondary);
 
     display = CreateDisplay(200, 100, display::Display::ROTATE_180);
-    ScreenOrientationValues landscape_2 =
+    blink::mojom::ScreenOrientation landscape_2 =
         DisplayUtil::GetOrientationTypeForDesktop(display);
-    EXPECT_TRUE(landscape_2 == SCREEN_ORIENTATION_VALUES_LANDSCAPE_PRIMARY ||
-                landscape_2 == SCREEN_ORIENTATION_VALUES_LANDSCAPE_SECONDARY);
+    EXPECT_TRUE(
+        landscape_2 == blink::mojom::ScreenOrientation::kLandscapePrimary ||
+        landscape_2 == blink::mojom::ScreenOrientation::kLandscapeSecondary);
 
     EXPECT_NE(landscape_1, landscape_2);
 
     display = CreateDisplay(19999, 20000, display::Display::ROTATE_90);
-    ScreenOrientationValues portrait_1 =
+    blink::mojom::ScreenOrientation portrait_1 =
         DisplayUtil::GetOrientationTypeForDesktop(display);
-    EXPECT_TRUE(portrait_1 == SCREEN_ORIENTATION_VALUES_PORTRAIT_PRIMARY ||
-                portrait_1 == SCREEN_ORIENTATION_VALUES_PORTRAIT_SECONDARY);
+    EXPECT_TRUE(
+        portrait_1 == blink::mojom::ScreenOrientation::kPortraitPrimary ||
+        portrait_1 == blink::mojom::ScreenOrientation::kPortraitSecondary);
 
     display = CreateDisplay(1, 10000, display::Display::ROTATE_270);
-    ScreenOrientationValues portrait_2 =
+    blink::mojom::ScreenOrientation portrait_2 =
         DisplayUtil::GetOrientationTypeForDesktop(display);
-    EXPECT_TRUE(portrait_2 == SCREEN_ORIENTATION_VALUES_PORTRAIT_PRIMARY ||
-                portrait_2 == SCREEN_ORIENTATION_VALUES_PORTRAIT_SECONDARY);
+    EXPECT_TRUE(
+        portrait_2 == blink::mojom::ScreenOrientation::kPortraitPrimary ||
+        portrait_2 == blink::mojom::ScreenOrientation::kPortraitSecondary);
 
     EXPECT_NE(portrait_1, portrait_2);
 
diff --git a/content/browser/resources/conversions/conversion_internals.js b/content/browser/resources/conversions/conversion_internals.js
index cd89543..679ed861 100644
--- a/content/browser/resources/conversions/conversion_internals.js
+++ b/content/browser/resources/conversions/conversion_internals.js
@@ -43,7 +43,8 @@
 
   let result = origin.scheme + '://' + origin.host;
 
-  if (origin.scheme == 'https' && origin.port != '443') {
+  if ((origin.scheme == 'https' && origin.port != '443') ||
+      (origin.scheme == 'http' && origin.port != '80')) {
     result += ':' + origin.port;
   }
   return result;
diff --git a/content/browser/screen_orientation/screen_orientation_browsertest.cc b/content/browser/screen_orientation/screen_orientation_browsertest.cc
index a1ea0bda..21b8eec 100644
--- a/content/browser/screen_orientation/screen_orientation_browsertest.cc
+++ b/content/browser/screen_orientation/screen_orientation_browsertest.cc
@@ -46,17 +46,18 @@
     RenderWidgetHostImpl* main_frame_rwh = static_cast<RenderWidgetHostImpl*>(
         web_contents()->GetMainFrame()->GetRenderWidgetHost());
 
-    ScreenOrientationValues type = SCREEN_ORIENTATION_VALUES_DEFAULT;
+    blink::mojom::ScreenOrientation type =
+        blink::mojom::ScreenOrientation::kUndefined;
     if (str_type == "portrait-primary") {
-      type = SCREEN_ORIENTATION_VALUES_PORTRAIT_PRIMARY;
+      type = blink::mojom::ScreenOrientation::kPortraitPrimary;
     } else if (str_type == "portrait-secondary") {
-      type = SCREEN_ORIENTATION_VALUES_PORTRAIT_SECONDARY;
+      type = blink::mojom::ScreenOrientation::kPortraitSecondary;
     } else if (str_type == "landscape-primary") {
-      type = SCREEN_ORIENTATION_VALUES_LANDSCAPE_PRIMARY;
+      type = blink::mojom::ScreenOrientation::kLandscapePrimary;
     } else if (str_type == "landscape-secondary") {
-      type = SCREEN_ORIENTATION_VALUES_LANDSCAPE_SECONDARY;
+      type = blink::mojom::ScreenOrientation::kLandscapeSecondary;
     }
-    ASSERT_NE(SCREEN_ORIENTATION_VALUES_DEFAULT, type);
+    ASSERT_NE(blink::mojom::ScreenOrientation::kUndefined, type);
 
     ScreenInfo screen_info;
     main_frame_rwh->GetScreenInfo(&screen_info);
diff --git a/content/browser/screen_orientation/screen_orientation_provider.cc b/content/browser/screen_orientation/screen_orientation_provider.cc
index 6372042..e170c51d 100644
--- a/content/browser/screen_orientation/screen_orientation_provider.cc
+++ b/content/browser/screen_orientation/screen_orientation_provider.cc
@@ -157,15 +157,15 @@
   rwh->GetScreenInfo(&screen_info);
 
   switch (screen_info.orientation_type) {
-    case SCREEN_ORIENTATION_VALUES_PORTRAIT_PRIMARY:
-    case SCREEN_ORIENTATION_VALUES_PORTRAIT_SECONDARY:
+    case blink::mojom::ScreenOrientation::kPortraitPrimary:
+    case blink::mojom::ScreenOrientation::kPortraitSecondary:
       if (screen_info.orientation_angle == 0 ||
           screen_info.orientation_angle == 180) {
         return blink::kWebScreenOrientationLockPortraitPrimary;
       }
       return blink::kWebScreenOrientationLockLandscapePrimary;
-    case SCREEN_ORIENTATION_VALUES_LANDSCAPE_PRIMARY:
-    case SCREEN_ORIENTATION_VALUES_LANDSCAPE_SECONDARY:
+    case blink::mojom::ScreenOrientation::kLandscapePrimary:
+    case blink::mojom::ScreenOrientation::kLandscapeSecondary:
       if (screen_info.orientation_angle == 0 ||
           screen_info.orientation_angle == 180) {
         return blink::kWebScreenOrientationLockLandscapePrimary;
@@ -191,26 +191,26 @@
   switch (lock) {
     case blink::kWebScreenOrientationLockPortraitPrimary:
       return screen_info.orientation_type ==
-             SCREEN_ORIENTATION_VALUES_PORTRAIT_PRIMARY;
+             blink::mojom::ScreenOrientation::kPortraitPrimary;
     case blink::kWebScreenOrientationLockPortraitSecondary:
       return screen_info.orientation_type ==
-             SCREEN_ORIENTATION_VALUES_PORTRAIT_SECONDARY;
+             blink::mojom::ScreenOrientation::kPortraitSecondary;
     case blink::kWebScreenOrientationLockLandscapePrimary:
       return screen_info.orientation_type ==
-             SCREEN_ORIENTATION_VALUES_LANDSCAPE_PRIMARY;
+             blink::mojom::ScreenOrientation::kLandscapePrimary;
     case blink::kWebScreenOrientationLockLandscapeSecondary:
       return screen_info.orientation_type ==
-             SCREEN_ORIENTATION_VALUES_LANDSCAPE_SECONDARY;
+             blink::mojom::ScreenOrientation::kLandscapeSecondary;
     case blink::kWebScreenOrientationLockLandscape:
       return screen_info.orientation_type ==
-                 SCREEN_ORIENTATION_VALUES_LANDSCAPE_PRIMARY ||
+                 blink::mojom::ScreenOrientation::kLandscapePrimary ||
              screen_info.orientation_type ==
-                 SCREEN_ORIENTATION_VALUES_LANDSCAPE_SECONDARY;
+                 blink::mojom::ScreenOrientation::kLandscapeSecondary;
     case blink::kWebScreenOrientationLockPortrait:
       return screen_info.orientation_type ==
-                 SCREEN_ORIENTATION_VALUES_PORTRAIT_PRIMARY ||
+                 blink::mojom::ScreenOrientation::kPortraitPrimary ||
              screen_info.orientation_type ==
-                 SCREEN_ORIENTATION_VALUES_PORTRAIT_SECONDARY;
+                 blink::mojom::ScreenOrientation::kPortraitSecondary;
     case blink::kWebScreenOrientationLockAny:
       return true;
     case blink::kWebScreenOrientationLockNatural:
diff --git a/content/browser/service_worker/fake_embedded_worker_instance_client.cc b/content/browser/service_worker/fake_embedded_worker_instance_client.cc
index 783c75e..7fcb3ce3 100644
--- a/content/browser/service_worker/fake_embedded_worker_instance_client.cc
+++ b/content/browser/service_worker/fake_embedded_worker_instance_client.cc
@@ -13,6 +13,38 @@
 
 namespace content {
 
+class FakeServiceWorkerInstalledScriptsManager
+    : public blink::mojom::ServiceWorkerInstalledScriptsManager {
+ public:
+  explicit FakeServiceWorkerInstalledScriptsManager(
+      mojo::PendingReceiver<blink::mojom::ServiceWorkerInstalledScriptsManager>
+          receiver)
+      : receiver_(this, std::move(receiver)) {}
+
+  blink::mojom::ServiceWorkerScriptInfoPtr WaitForTransferInstalledScript() {
+    if (!script_info_) {
+      base::RunLoop loop;
+      quit_closure_ = loop.QuitClosure();
+      loop.Run();
+      DCHECK(script_info_);
+    }
+    return std::move(script_info_);
+  }
+
+ private:
+  void TransferInstalledScript(
+      blink::mojom::ServiceWorkerScriptInfoPtr script_info) override {
+    script_info_ = std::move(script_info);
+    if (quit_closure_)
+      std::move(quit_closure_).Run();
+  }
+
+  base::OnceClosure quit_closure_;
+  blink::mojom::ServiceWorkerScriptInfoPtr script_info_;
+
+  mojo::Receiver<blink::mojom::ServiceWorkerInstalledScriptsManager> receiver_;
+};
+
 FakeEmbeddedWorkerInstanceClient::FakeEmbeddedWorkerInstanceClient(
     EmbeddedWorkerTestHelper* helper)
     : helper_(helper) {}
@@ -44,6 +76,12 @@
   loop.Run();
 }
 
+blink::mojom::ServiceWorkerScriptInfoPtr
+FakeEmbeddedWorkerInstanceClient::WaitForTransferInstalledScript() {
+  DCHECK(installed_scripts_manager_);
+  return installed_scripts_manager_->WaitForTransferInstalledScript();
+}
+
 void FakeEmbeddedWorkerInstanceClient::Disconnect() {
   receiver_.reset();
   CallOnConnectionError();
@@ -71,6 +109,9 @@
       devtools_agent_host_remote.BindNewPipeAndPassReceiver());
 
   if (start_params_->is_installed) {
+    installed_scripts_manager_ =
+        std::make_unique<FakeServiceWorkerInstalledScriptsManager>(
+            std::move(start_params_->installed_scripts_info->manager_receiver));
     EvaluateScript();
     return;
   }
diff --git a/content/browser/service_worker/fake_embedded_worker_instance_client.h b/content/browser/service_worker/fake_embedded_worker_instance_client.h
index 8db7376..778dd370 100644
--- a/content/browser/service_worker/fake_embedded_worker_instance_client.h
+++ b/content/browser/service_worker/fake_embedded_worker_instance_client.h
@@ -12,10 +12,12 @@
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "third_party/blink/public/mojom/service_worker/embedded_worker.mojom.h"
+#include "third_party/blink/public/mojom/service_worker/service_worker_installed_scripts_manager.mojom.h"
 
 namespace content {
 
 class EmbeddedWorkerTestHelper;
+class FakeServiceWorkerInstalledScriptsManager;
 
 // The default fake for blink::mojom::EmbeddedWorkerInstanceClient. It responds
 // to Start/Stop/etc messages without starting an actual service worker thread.
@@ -40,6 +42,8 @@
                 receiver);
   void RunUntilBound();
 
+  blink::mojom::ServiceWorkerScriptInfoPtr WaitForTransferInstalledScript();
+
   // Closes the binding and deletes |this|.
   void Disconnect();
 
@@ -70,6 +74,9 @@
   mojo::Receiver<blink::mojom::EmbeddedWorkerInstanceClient> receiver_{this};
   base::OnceClosure quit_closure_for_bind_;
 
+  std::unique_ptr<FakeServiceWorkerInstalledScriptsManager>
+      installed_scripts_manager_;
+
   base::WeakPtrFactory<FakeEmbeddedWorkerInstanceClient> weak_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(FakeEmbeddedWorkerInstanceClient);
diff --git a/content/browser/service_worker/service_worker_container_host.cc b/content/browser/service_worker/service_worker_container_host.cc
index 69ea2e43..c727f822 100644
--- a/content/browser/service_worker/service_worker_container_host.cc
+++ b/content/browser/service_worker/service_worker_container_host.cc
@@ -282,7 +282,7 @@
                            "ServiceWorkerContainerHost::GetRegistrations",
                            trace_id);
   context_->registry()->GetRegistrationsForOrigin(
-      url_.GetOrigin(),
+      url::Origin::Create(url_),
       base::AdaptCallbackForRepeating(base::BindOnce(
           &ServiceWorkerContainerHost::GetRegistrationsComplete,
           weak_factory_.GetWeakPtr(), std::move(callback), trace_id)));
diff --git a/content/browser/service_worker/service_worker_context_core.cc b/content/browser/service_worker/service_worker_context_core.cc
index 7818fc7..8ece6cd 100644
--- a/content/browser/service_worker/service_worker_context_core.cc
+++ b/content/browser/service_worker/service_worker_context_core.cc
@@ -538,7 +538,7 @@
                      AsWeakPtr(), scope, std::move(callback)));
 }
 
-void ServiceWorkerContextCore::DeleteForOrigin(const GURL& origin,
+void ServiceWorkerContextCore::DeleteForOrigin(const url::Origin& origin,
                                                StatusCallback callback) {
   DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
   registry()->GetRegistrationsForOrigin(
@@ -555,7 +555,7 @@
 }
 
 void ServiceWorkerContextCore::DidGetRegistrationsForDeleteForOrigin(
-    const GURL& origin,
+    const url::Origin& origin,
     base::OnceCallback<void(blink::ServiceWorkerStatusCode)> callback,
     blink::ServiceWorkerStatusCode status,
     const std::vector<scoped_refptr<ServiceWorkerRegistration>>&
@@ -1033,6 +1033,11 @@
   return registry_->storage();
 }
 
+mojo::Remote<storage::mojom::ServiceWorkerStorageControl>&
+ServiceWorkerContextCore::GetStorageControl() {
+  return registry_->GetRemoteStorageControl();
+}
+
 ServiceWorkerProcessManager* ServiceWorkerContextCore::process_manager() {
   return wrapper_->process_manager();
 }
diff --git a/content/browser/service_worker/service_worker_context_core.h b/content/browser/service_worker/service_worker_context_core.h
index bec8b4d8..76a2658 100644
--- a/content/browser/service_worker/service_worker_context_core.h
+++ b/content/browser/service_worker/service_worker_context_core.h
@@ -18,6 +18,7 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list_threadsafe.h"
+#include "components/services/storage/public/mojom/service_worker_storage_control.mojom.h"
 #include "content/browser/service_worker/service_worker_info.h"
 #include "content/browser/service_worker/service_worker_process_manager.h"
 #include "content/browser/service_worker/service_worker_registration_status.h"
@@ -176,6 +177,8 @@
   // TODO(crbug.com/1016064): Remove this accessor once some parts of
   // ServiceWorkerStorage are moved to the Storage Service.
   ServiceWorkerStorage* storage() const;
+  mojo::Remote<storage::mojom::ServiceWorkerStorageControl>&
+  GetStorageControl();
   ServiceWorkerProcessManager* process_manager();
   ServiceWorkerJobCoordinator* job_coordinator() {
     return job_coordinator_.get();
@@ -254,7 +257,7 @@
   // Callback is called after all deletions occurred. The status code is
   // blink::ServiceWorkerStatusCode::kOk if all succeed, or
   // SERVICE_WORKER_FAILED if any did not succeed.
-  void DeleteForOrigin(const GURL& origin, StatusCallback callback);
+  void DeleteForOrigin(const url::Origin& origin, StatusCallback callback);
 
   // Performs internal storage cleanup. Operations to the storage in the past
   // (e.g. deletion) are usually recorded in disk for a certain period until
@@ -393,7 +396,7 @@
                               std::string* out_error) const;
 
   void DidGetRegistrationsForDeleteForOrigin(
-      const GURL& origin,
+      const url::Origin& origin,
       base::OnceCallback<void(blink::ServiceWorkerStatusCode)> callback,
       blink::ServiceWorkerStatusCode status,
       const std::vector<scoped_refptr<ServiceWorkerRegistration>>&
diff --git a/content/browser/service_worker/service_worker_context_core_unittest.cc b/content/browser/service_worker/service_worker_context_core_unittest.cc
index 21f7bbc..8b976ad 100644
--- a/content/browser/service_worker/service_worker_context_core_unittest.cc
+++ b/content/browser/service_worker/service_worker_context_core_unittest.cc
@@ -115,7 +115,7 @@
   }
 
   // Wrapper for ServiceWorkerContextCore::DeleteForOrigin.
-  blink::ServiceWorkerStatusCode DeleteForOrigin(const GURL& origin) {
+  blink::ServiceWorkerStatusCode DeleteForOrigin(const url::Origin& origin) {
     blink::ServiceWorkerStatusCode status;
     base::RunLoop loop;
     context()->DeleteForOrigin(
@@ -191,7 +191,7 @@
 TEST_F(ServiceWorkerContextCoreTest, DeleteForOrigin) {
   const GURL script("https://www.example.com/a/sw.js");
   const GURL scope("https://www.example.com/a");
-  const GURL origin("https://www.example.com");
+  const url::Origin origin = url::Origin::Create(scope);
 
   // Register a service worker.
   blink::mojom::ServiceWorkerRegistrationOptions options;
@@ -210,7 +210,7 @@
 TEST_F(ServiceWorkerContextCoreTest, DeleteForOriginAbortsQueuedJobs) {
   const GURL script("https://www.example.com/a/sw.js");
   const GURL scope("https://www.example.com/a");
-  const GURL origin("https://www.example.com");
+  const url::Origin origin = url::Origin::Create(scope);
 
   // Register a service worker.
   blink::mojom::ServiceWorkerRegistrationOptions options;
@@ -242,7 +242,7 @@
        DeleteUninstallingForOriginAbortsQueuedJobs) {
   const GURL script("https://www.example.com/a/sw.js");
   const GURL scope("https://www.example.com/a");
-  const GURL origin("https://www.example.com");
+  const url::Origin origin = url::Origin::Create(scope);
 
   // Register a service worker.
   blink::mojom::ServiceWorkerRegistrationOptions options;
@@ -253,7 +253,7 @@
   // Add a controlled client.
   ServiceWorkerContainerHost* container_host = CreateControllee();
   container_host->UpdateUrls(scope, net::SiteForCookies::FromUrl(scope),
-                             url::Origin::Create(scope));
+                             origin);
   container_host->SetControllerRegistration(registration,
                                             /*notify_controllerchange=*/false);
 
@@ -289,7 +289,7 @@
 TEST_F(ServiceWorkerContextCoreTest, DeleteForOrigin_UnregisterFail) {
   const GURL script("https://www.example.com/a/sw.js");
   const GURL scope("https://www.example.com/a");
-  const GURL origin("https://www.example.com");
+  const url::Origin origin = url::Origin::Create(scope);
 
   // Register a service worker.
   blink::mojom::ServiceWorkerRegistrationOptions options;
diff --git a/content/browser/service_worker/service_worker_context_unittest.cc b/content/browser/service_worker/service_worker_context_unittest.cc
index 163d627..7bca4c1e 100644
--- a/content/browser/service_worker/service_worker_context_unittest.cc
+++ b/content/browser/service_worker/service_worker_context_unittest.cc
@@ -857,7 +857,7 @@
             registration_id4);
 
   bool called = false;
-  context()->DeleteForOrigin(origin1_s1.GetOrigin(),
+  context()->DeleteForOrigin(url::Origin::Create(origin1_s1),
                              MakeUnregisteredCallback(&called));
 
   ASSERT_FALSE(called);
diff --git a/content/browser/service_worker/service_worker_context_wrapper.cc b/content/browser/service_worker/service_worker_context_wrapper.cc
index 8af9a8d..8715fb5 100644
--- a/content/browser/service_worker/service_worker_context_wrapper.cc
+++ b/content/browser/service_worker/service_worker_context_wrapper.cc
@@ -632,7 +632,7 @@
       std::move(callback_runner)));
 }
 
-void ServiceWorkerContextWrapper::DeleteForOrigin(const GURL& origin,
+void ServiceWorkerContextWrapper::DeleteForOrigin(const url::Origin& origin,
                                                   ResultCallback callback) {
   RunOrPostTaskOnCoreThread(
       FROM_HERE,
@@ -642,7 +642,7 @@
 }
 
 void ServiceWorkerContextWrapper::DeleteForOriginOnCoreThread(
-    const GURL& origin,
+    const url::Origin& origin,
     ResultCallback callback,
     scoped_refptr<base::TaskRunner> callback_runner) {
   DCHECK_CURRENTLY_ON(GetCoreThreadId());
@@ -652,7 +652,7 @@
     return;
   }
   context()->DeleteForOrigin(
-      origin.GetOrigin(),
+      origin,
       base::BindOnce(
           [](ResultCallback callback,
              scoped_refptr<base::TaskRunner> callback_runner,
@@ -1187,7 +1187,7 @@
             std::vector<scoped_refptr<ServiceWorkerRegistration>>()));
     return;
   }
-  context_core_->registry()->GetRegistrationsForOrigin(origin.GetURL(),
+  context_core_->registry()->GetRegistrationsForOrigin(origin,
                                                        std::move(callback));
 }
 
diff --git a/content/browser/service_worker/service_worker_context_wrapper.h b/content/browser/service_worker/service_worker_context_wrapper.h
index 8f851ec5..f302e6e 100644
--- a/content/browser/service_worker/service_worker_context_wrapper.h
+++ b/content/browser/service_worker/service_worker_context_wrapper.h
@@ -172,7 +172,8 @@
       base::Optional<std::string> host_filter,
       GetInstalledRegistrationOriginsCallback callback) override;
   void GetAllOriginsInfo(GetUsageInfoCallback callback) override;
-  void DeleteForOrigin(const GURL& origin, ResultCallback callback) override;
+  void DeleteForOrigin(const url::Origin& origin,
+                       ResultCallback callback) override;
   void PerformStorageCleanup(base::OnceClosure callback) override;
   void CheckHasServiceWorker(const GURL& url,
                              CheckHasServiceWorkerCallback callback) override;
@@ -518,7 +519,7 @@
       FindRegistrationCallback callback,
       scoped_refptr<base::TaskRunner> callback_runner);
   void DeleteForOriginOnCoreThread(
-      const GURL& origin,
+      const url::Origin& origin,
       ResultCallback callback,
       scoped_refptr<base::TaskRunner> callback_runner);
   void FindRegistrationForScopeOnCoreThread(
diff --git a/content/browser/service_worker/service_worker_database.cc b/content/browser/service_worker/service_worker_database.cc
index 19f0432..38f3457 100644
--- a/content/browser/service_worker/service_worker_database.cc
+++ b/content/browser/service_worker/service_worker_database.cc
@@ -141,13 +141,14 @@
   return true;
 }
 
-std::string CreateRegistrationKeyPrefix(const GURL& origin) {
+std::string CreateRegistrationKeyPrefix(const url::Origin& origin) {
   return base::StringPrintf("%s%s%c", service_worker_internals::kRegKeyPrefix,
-                            origin.GetOrigin().spec().c_str(),
+                            origin.GetURL().spec().c_str(),
                             service_worker_internals::kKeySeparator);
 }
 
-std::string CreateRegistrationKey(int64_t registration_id, const GURL& origin) {
+std::string CreateRegistrationKey(int64_t registration_id,
+                                  const url::Origin& origin) {
   return CreateRegistrationKeyPrefix(origin).append(
       base::NumberToString(registration_id));
 }
@@ -377,7 +378,7 @@
 }
 
 ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetRegistrationsForOrigin(
-    const GURL& origin,
+    const url::Origin& origin,
     std::vector<storage::mojom::ServiceWorkerRegistrationDataPtr>*
         registrations,
     std::vector<std::vector<storage::mojom::ServiceWorkerResourceRecordPtr>>*
@@ -462,7 +463,7 @@
   if (status != Status::kOk)
     return status;
 
-  std::string prefix = CreateRegistrationKeyPrefix(origin.GetURL());
+  std::string prefix = CreateRegistrationKeyPrefix(origin);
 
   // Read all registrations.
   {
@@ -537,8 +538,7 @@
       registrations->push_back(std::move(registration));
     }
   }
-  UMA_HISTOGRAM_COUNTS_10000("ServiceWorker.RegistrationCount",
-                             registrations->size());
+
   HandleReadResult(FROM_HERE, status);
   return status;
 }
@@ -558,7 +558,8 @@
   if (status != Status::kOk)
     return status;
 
-  status = ReadRegistrationData(registration_id, origin, registration);
+  status = ReadRegistrationData(registration_id, url::Origin::Create(origin),
+                                registration);
   if (status != Status::kOk)
     return status;
 
@@ -660,9 +661,9 @@
 
   // Retrieve a previous version to sweep purgeable resources.
   storage::mojom::ServiceWorkerRegistrationDataPtr old_registration;
-  status =
-      ReadRegistrationData(registration.registration_id,
-                           registration.scope.GetOrigin(), &old_registration);
+  status = ReadRegistrationData(registration.registration_id,
+                                url::Origin::Create(registration.scope),
+                                &old_registration);
   if (status != Status::kOk && status != Status::kErrorNotFound)
     return status;
   if (status == Status::kOk) {
@@ -704,7 +705,8 @@
     return Status::kErrorFailed;
 
   storage::mojom::ServiceWorkerRegistrationDataPtr registration;
-  status = ReadRegistrationData(registration_id, origin, &registration);
+  status = ReadRegistrationData(registration_id, url::Origin::Create(origin),
+                                &registration);
   if (status != Status::kOk)
     return status;
 
@@ -729,7 +731,8 @@
     return Status::kErrorFailed;
 
   storage::mojom::ServiceWorkerRegistrationDataPtr registration;
-  status = ReadRegistrationData(registration_id, origin, &registration);
+  status = ReadRegistrationData(registration_id, url::Origin::Create(origin),
+                                &registration);
   if (status != Status::kOk)
     return status;
 
@@ -754,7 +757,8 @@
     return Status::kErrorFailed;
 
   storage::mojom::ServiceWorkerRegistrationDataPtr registration;
-  status = ReadRegistrationData(registration_id, origin, &registration);
+  status = ReadRegistrationData(registration_id, url::Origin::Create(origin),
+                                &registration);
   if (status != Status::kOk)
     return status;
 
@@ -779,7 +783,8 @@
     return Status::kErrorFailed;
 
   storage::mojom::ServiceWorkerRegistrationDataPtr registration;
-  status = ReadRegistrationData(registration_id, origin, &registration);
+  status = ReadRegistrationData(registration_id, url::Origin::Create(origin),
+                                &registration);
   if (status != Status::kOk)
     return status;
 
@@ -811,7 +816,8 @@
   // |registration_id| is the only one for |origin|.
   // TODO(nhiroki): Check the uniqueness by more efficient way.
   std::vector<storage::mojom::ServiceWorkerRegistrationDataPtr> registrations;
-  status = GetRegistrationsForOrigin(origin, &registrations, nullptr);
+  status = GetRegistrationsForOrigin(url::Origin::Create(origin),
+                                     &registrations, nullptr);
   if (status != Status::kOk)
     return status;
 
@@ -821,7 +827,8 @@
   }
 
   // Delete a registration specified by |registration_id|.
-  batch.Delete(CreateRegistrationKey(registration_id, origin));
+  batch.Delete(
+      CreateRegistrationKey(registration_id, url::Origin::Create(origin)));
   batch.Delete(CreateRegistrationIdToOriginKey(registration_id));
 
   // Delete resource records and user data associated with the registration.
@@ -990,7 +997,8 @@
 
   // There should be the registration specified by |registration_id|.
   storage::mojom::ServiceWorkerRegistrationDataPtr registration;
-  status = ReadRegistrationData(registration_id, origin, &registration);
+  status = ReadRegistrationData(registration_id, url::Origin::Create(origin),
+                                &registration);
   if (status != Status::kOk)
     return status;
 
@@ -1354,13 +1362,15 @@
     batch.Delete(CreateUniqueOriginKey(origin));
 
     std::vector<storage::mojom::ServiceWorkerRegistrationDataPtr> registrations;
-    status = GetRegistrationsForOrigin(origin, &registrations, nullptr);
+    status = GetRegistrationsForOrigin(url::Origin::Create(origin),
+                                       &registrations, nullptr);
     if (status != Status::kOk)
       return status;
 
     // Delete registrations, resource records and user data.
     for (const auto& data : registrations) {
-      batch.Delete(CreateRegistrationKey(data->registration_id, origin));
+      batch.Delete(CreateRegistrationKey(data->registration_id,
+                                         url::Origin::Create(origin)));
       batch.Delete(CreateRegistrationIdToOriginKey(data->registration_id));
 
       status = DeleteResourceRecords(data->version_id,
@@ -1494,7 +1504,7 @@
 
 ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadRegistrationData(
     int64_t registration_id,
-    const GURL& origin,
+    const url::Origin& origin,
     storage::mojom::ServiceWorkerRegistrationDataPtr* registration) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(registration);
@@ -1717,7 +1727,7 @@
   std::string value;
   bool success = data.SerializeToString(&value);
   DCHECK(success);
-  GURL origin = registration.scope.GetOrigin();
+  url::Origin origin = url::Origin::Create(registration.scope);
   batch->Put(CreateRegistrationKey(data.registration_id(), origin), value);
 }
 
diff --git a/content/browser/service_worker/service_worker_database.h b/content/browser/service_worker/service_worker_database.h
index dd3058f..3ee09fdd 100644
--- a/content/browser/service_worker/service_worker_database.h
+++ b/content/browser/service_worker/service_worker_database.h
@@ -90,7 +90,7 @@
   // Reads registrations for |origin| from the database. Returns OK if they are
   // successfully read or not found. Otherwise, returns an error.
   Status GetRegistrationsForOrigin(
-      const GURL& origin,
+      const url::Origin& origin,
       std::vector<storage::mojom::ServiceWorkerRegistrationDataPtr>*
           registrations,
       std::vector<std::vector<storage::mojom::ServiceWorkerResourceRecordPtr>>*
@@ -295,7 +295,7 @@
   // if successfully reads. Otherwise, returns an error.
   Status ReadRegistrationData(
       int64_t registration_id,
-      const GURL& origin,
+      const url::Origin& origin,
       storage::mojom::ServiceWorkerRegistrationDataPtr* registration);
 
   // Parses |serialized| as a RegistrationData object and pushes it into |out|.
diff --git a/content/browser/service_worker/service_worker_database_unittest.cc b/content/browser/service_worker/service_worker_database_unittest.cc
index 7f5c03f..c5caaa2 100644
--- a/content/browser/service_worker/service_worker_database_unittest.cc
+++ b/content/browser/service_worker/service_worker_database_unittest.cc
@@ -453,8 +453,8 @@
   std::vector<storage::mojom::ServiceWorkerRegistrationDataPtr> registrations;
   std::vector<std::vector<ResourceRecordPtr>> resources_list;
   EXPECT_EQ(ServiceWorkerDatabase::Status::kOk,
-            database->GetRegistrationsForOrigin(origin1, &registrations,
-                                                &resources_list));
+            database->GetRegistrationsForOrigin(
+                url::Origin::Create(origin1), &registrations, &resources_list));
   EXPECT_TRUE(registrations.empty());
   EXPECT_TRUE(resources_list.empty());
 
@@ -476,8 +476,8 @@
   registrations.clear();
   resources_list.clear();
   EXPECT_EQ(ServiceWorkerDatabase::Status::kOk,
-            database->GetRegistrationsForOrigin(origin1, &registrations,
-                                                &resources_list));
+            database->GetRegistrationsForOrigin(
+                url::Origin::Create(origin1), &registrations, &resources_list));
   EXPECT_EQ(1U, registrations.size());
   VerifyRegistrationData(data1, *registrations[0]);
   EXPECT_EQ(1U, resources_list.size());
@@ -499,8 +499,8 @@
   registrations.clear();
   resources_list.clear();
   EXPECT_EQ(ServiceWorkerDatabase::Status::kOk,
-            database->GetRegistrationsForOrigin(origin2, &registrations,
-                                                &resources_list));
+            database->GetRegistrationsForOrigin(
+                url::Origin::Create(origin2), &registrations, &resources_list));
   EXPECT_EQ(1U, registrations.size());
   VerifyRegistrationData(data2, *registrations[0]);
   EXPECT_EQ(1U, resources_list.size());
@@ -536,8 +536,8 @@
   registrations.clear();
   resources_list.clear();
   EXPECT_EQ(ServiceWorkerDatabase::Status::kOk,
-            database->GetRegistrationsForOrigin(origin3, &registrations,
-                                                &resources_list));
+            database->GetRegistrationsForOrigin(
+                url::Origin::Create(origin3), &registrations, &resources_list));
   EXPECT_EQ(2U, registrations.size());
   VerifyRegistrationData(data3, *registrations[0]);
   VerifyRegistrationData(data4, *registrations[1]);
@@ -548,9 +548,9 @@
   // The third parameter |opt_resources_list| to GetRegistrationsForOrigin()
   // is optional. So, nullptr should be acceptable.
   registrations.clear();
-  EXPECT_EQ(
-      ServiceWorkerDatabase::Status::kOk,
-      database->GetRegistrationsForOrigin(origin1, &registrations, nullptr));
+  EXPECT_EQ(ServiceWorkerDatabase::Status::kOk,
+            database->GetRegistrationsForOrigin(url::Origin::Create(origin1),
+                                                &registrations, nullptr));
   EXPECT_EQ(1U, registrations.size());
   VerifyRegistrationData(data1, *registrations[0]);
 }
@@ -1987,9 +1987,9 @@
 
   // The registrations for |origin1| should be removed.
   std::vector<storage::mojom::ServiceWorkerRegistrationDataPtr> registrations;
-  EXPECT_EQ(
-      ServiceWorkerDatabase::Status::kOk,
-      database->GetRegistrationsForOrigin(origin1, &registrations, nullptr));
+  EXPECT_EQ(ServiceWorkerDatabase::Status::kOk,
+            database->GetRegistrationsForOrigin(url::Origin::Create(origin1),
+                                                &registrations, nullptr));
   EXPECT_TRUE(registrations.empty());
   GURL origin_out;
   EXPECT_EQ(
@@ -2130,8 +2130,8 @@
   std::vector<storage::mojom::ServiceWorkerRegistrationDataPtr> registrations;
   std::vector<std::vector<ResourceRecordPtr>> resources_list;
   EXPECT_EQ(ServiceWorkerDatabase::Status::kErrorCorrupted,
-            database->GetRegistrationsForOrigin(origin, &registrations,
-                                                &resources_list));
+            database->GetRegistrationsForOrigin(
+                url::Origin::Create(origin), &registrations, &resources_list));
   EXPECT_TRUE(registrations.empty());
   EXPECT_TRUE(resources_list.empty());
 
@@ -2218,9 +2218,10 @@
     // Restore.
     std::vector<storage::mojom::ServiceWorkerRegistrationDataPtr> registrations;
     std::vector<std::vector<ResourceRecordPtr>> resources_list;
-    EXPECT_EQ(ServiceWorkerDatabase::Status::kOk,
-              database->GetRegistrationsForOrigin(origin, &registrations,
-                                                  &resources_list));
+    EXPECT_EQ(
+        ServiceWorkerDatabase::Status::kOk,
+        database->GetRegistrationsForOrigin(url::Origin::Create(origin),
+                                            &registrations, &resources_list));
 
     // The data must not have been altered.
     VerifyRegistrationData(data, *registrations[0]);
diff --git a/content/browser/service_worker/service_worker_main_resource_loader_unittest.cc b/content/browser/service_worker/service_worker_main_resource_loader_unittest.cc
index 7c54afa..16f100a 100644
--- a/content/browser/service_worker/service_worker_main_resource_loader_unittest.cc
+++ b/content/browser/service_worker/service_worker_main_resource_loader_unittest.cc
@@ -212,6 +212,10 @@
 
   void SetResponseTime(base::Time time) { response_time_ = time; }
 
+  void WaitForTransferInstalledScript() {
+    embedded_worker_instance_client_->WaitForTransferInstalledScript();
+  }
+
  protected:
   void DispatchFetchEventForMainResource(
       blink::mojom::DispatchFetchEventParamsPtr params,
@@ -431,6 +435,21 @@
     service_worker_ =
         helper_->AddNewPendingServiceWorker<FetchEventServiceWorker>(
             helper_.get(), client);
+
+    // Wait for main script response is set to |version| because
+    // ServiceWorkerMainResourceLoader needs the main script response to
+    // create a response. The main script response is set when the first
+    // TransferInstalledScript().
+    {
+      base::Optional<blink::ServiceWorkerStatusCode> status;
+      base::RunLoop loop;
+      version_->StartWorker(
+          ServiceWorkerMetrics::EventType::UNKNOWN,
+          ReceiveServiceWorkerStatus(&status, loop.QuitClosure()));
+      loop.Run();
+      ASSERT_EQ(blink::ServiceWorkerStatusCode::kOk, status.value());
+      service_worker_->WaitForTransferInstalledScript();
+    }
   }
 
   ServiceWorkerRegistry* registry() { return helper_->context()->registry(); }
@@ -504,7 +523,7 @@
     EXPECT_FALSE(info.load_timing.service_worker_fetch_start.is_null());
     EXPECT_FALSE(
         info.load_timing.service_worker_respond_with_settled.is_null());
-    EXPECT_LT(info.load_timing.service_worker_start_time,
+    EXPECT_LE(info.load_timing.service_worker_start_time,
               info.load_timing.service_worker_ready_time);
     EXPECT_LE(info.load_timing.service_worker_ready_time,
               info.load_timing.service_worker_fetch_start);
diff --git a/content/browser/service_worker/service_worker_process_manager_unittest.cc b/content/browser/service_worker/service_worker_process_manager_unittest.cc
index d089d7a1..3174f516 100644
--- a/content/browser/service_worker/service_worker_process_manager_unittest.cc
+++ b/content/browser/service_worker/service_worker_process_manager_unittest.cc
@@ -104,12 +104,14 @@
        AllocateWorkerProcess_WithProcessReuse) {
   const int kEmbeddedWorkerId = 100;
   const GURL kSiteUrl = GURL("http://example.com");
+  SiteInfo site_info = SiteInstanceImpl::ComputeSiteInfo(
+      IsolationContext(browser_context_.get()), kSiteUrl);
 
   // Create a process that is hosting a frame with kSiteUrl.
   std::unique_ptr<MockRenderProcessHost> host(CreateRenderProcessHost());
   host->Init();
   RenderProcessHostImpl::AddFrameWithSite(browser_context_.get(), host.get(),
-                                          SiteInfo(kSiteUrl));
+                                          site_info);
 
   const std::map<int, scoped_refptr<SiteInstance>>& processes =
       worker_process_map();
@@ -139,18 +141,20 @@
   EXPECT_TRUE(processes.empty());
 
   RenderProcessHostImpl::RemoveFrameWithSite(browser_context_.get(), host.get(),
-                                             SiteInfo(kSiteUrl));
+                                             site_info);
 }
 
 TEST_F(ServiceWorkerProcessManagerTest,
        AllocateWorkerProcess_WithoutProcessReuse) {
   const int kEmbeddedWorkerId = 100;
   const GURL kSiteUrl = GURL("http://example.com");
+  SiteInfo site_info = SiteInstanceImpl::ComputeSiteInfo(
+      IsolationContext(browser_context_.get()), kSiteUrl);
 
   // Create a process that is hosting a frame with kSiteUrl.
   std::unique_ptr<MockRenderProcessHost> host(CreateRenderProcessHost());
   RenderProcessHostImpl::AddFrameWithSite(browser_context_.get(), host.get(),
-                                          SiteInfo(kSiteUrl));
+                                          site_info);
 
   const std::map<int, scoped_refptr<SiteInstance>>& processes =
       worker_process_map();
@@ -179,7 +183,7 @@
   EXPECT_TRUE(processes.empty());
 
   RenderProcessHostImpl::RemoveFrameWithSite(browser_context_.get(), host.get(),
-                                             SiteInfo(kSiteUrl));
+                                             site_info);
 }
 
 TEST_F(ServiceWorkerProcessManagerTest, AllocateWorkerProcess_InShutdown) {
diff --git a/content/browser/service_worker/service_worker_quota_client.cc b/content/browser/service_worker/service_worker_quota_client.cc
index a3831d1..2b39dcb 100644
--- a/content/browser/service_worker/service_worker_quota_client.cc
+++ b/content/browser/service_worker/service_worker_quota_client.cc
@@ -66,8 +66,7 @@
                                                 DeletionCallback callback) {
   DCHECK_EQ(type, StorageType::kTemporary);
   context_->DeleteForOrigin(
-      origin.GetURL(),
-      base::BindOnce(&ReportToQuotaStatus, std::move(callback)));
+      origin, base::BindOnce(&ReportToQuotaStatus, std::move(callback)));
 }
 
 void ServiceWorkerQuotaClient::PerformStorageCleanup(
diff --git a/content/browser/service_worker/service_worker_registration.cc b/content/browser/service_worker/service_worker_registration.cc
index 4673d32..5375e02 100644
--- a/content/browser/service_worker/service_worker_registration.cc
+++ b/content/browser/service_worker/service_worker_registration.cc
@@ -8,7 +8,6 @@
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
-#include "base/metrics/histogram_macros.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "content/browser/service_worker/embedded_worker_status.h"
 #include "content/browser/service_worker/service_worker_container_host.h"
@@ -139,9 +138,6 @@
 
 ServiceWorkerRegistrationInfo ServiceWorkerRegistration::GetInfo() {
   DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
-  UMA_HISTOGRAM_COUNTS_10000("ServiceWorker.RegistrationInfo.ScopeLength",
-                             scope().spec().length());
-
   return ServiceWorkerRegistrationInfo(
       scope(), update_via_cache(), registration_id_,
       is_deleted() ? ServiceWorkerRegistrationInfo::IS_DELETED
diff --git a/content/browser/service_worker/service_worker_registry.cc b/content/browser/service_worker/service_worker_registry.cc
index 189bdce..3957b4d 100644
--- a/content/browser/service_worker/service_worker_registry.cc
+++ b/content/browser/service_worker/service_worker_registry.cc
@@ -277,7 +277,7 @@
 }
 
 void ServiceWorkerRegistry::GetRegistrationsForOrigin(
-    const GURL& origin,
+    const url::Origin& origin,
     GetRegistrationsCallback callback) {
   DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
   storage()->GetRegistrationsForOrigin(
@@ -326,11 +326,11 @@
 
 std::vector<scoped_refptr<ServiceWorkerRegistration>>
 ServiceWorkerRegistry::GetUninstallingRegistrationsForOrigin(
-    const GURL& origin) {
+    const url::Origin& origin) {
   DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
   std::vector<scoped_refptr<ServiceWorkerRegistration>> results;
   for (const auto& registration : uninstalling_registrations_) {
-    if (registration.second->scope().GetOrigin() == origin) {
+    if (url::Origin::Create(registration.second->scope()) == origin) {
       results.push_back(registration.second);
     }
   }
@@ -1002,12 +1002,11 @@
 
 void ServiceWorkerRegistry::DidGetRegistrationsForOrigin(
     GetRegistrationsCallback callback,
-    const GURL& origin_filter,
+    const url::Origin& origin_filter,
     storage::mojom::ServiceWorkerDatabaseStatus database_status,
     std::unique_ptr<RegistrationList> registration_data_list,
     std::unique_ptr<std::vector<ResourceList>> resources_list) {
   DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
-  DCHECK(origin_filter.is_valid());
 
   blink::ServiceWorkerStatusCode status =
       DatabaseStatusToStatusCode(database_status);
@@ -1035,7 +1034,7 @@
 
   // Add unstored registrations that are being installed.
   for (const auto& registration : installing_registrations_) {
-    if (registration.second->scope().GetOrigin() != origin_filter)
+    if (url::Origin::Create(registration.second->scope()) != origin_filter)
       continue;
     if (registration_ids.insert(registration.first).second)
       registrations.push_back(registration.second);
diff --git a/content/browser/service_worker/service_worker_registry.h b/content/browser/service_worker/service_worker_registry.h
index 0a096d2..ccc651c0 100644
--- a/content/browser/service_worker/service_worker_registry.h
+++ b/content/browser/service_worker/service_worker_registry.h
@@ -134,7 +134,7 @@
                                  FindRegistrationCallback callback);
 
   // Returns all stored and installing registrations for a given origin.
-  void GetRegistrationsForOrigin(const GURL& origin,
+  void GetRegistrationsForOrigin(const url::Origin& origin,
                                  GetRegistrationsCallback callback);
 
   // Reads the total resource size stored in the storage for a given origin.
@@ -149,7 +149,7 @@
   ServiceWorkerRegistration* GetUninstallingRegistration(const GURL& scope);
 
   std::vector<scoped_refptr<ServiceWorkerRegistration>>
-  GetUninstallingRegistrationsForOrigin(const GURL& origin);
+  GetUninstallingRegistrationsForOrigin(const url::Origin& origin);
 
   // Commits |registration| with the installed but not activated |version|
   // to storage, overwriting any pre-existing registration data for the scope.
@@ -289,7 +289,7 @@
 
   void DidGetRegistrationsForOrigin(
       GetRegistrationsCallback callback,
-      const GURL& origin_filter,
+      const url::Origin& origin_filter,
       storage::mojom::ServiceWorkerDatabaseStatus database_status,
       std::unique_ptr<RegistrationList> registration_data_list,
       std::unique_ptr<std::vector<ResourceList>> resources_list);
diff --git a/content/browser/service_worker/service_worker_script_loader_factory.cc b/content/browser/service_worker/service_worker_script_loader_factory.cc
index 24fecfd..5d4ec511 100644
--- a/content/browser/service_worker/service_worker_script_loader_factory.cc
+++ b/content/browser/service_worker/service_worker_script_loader_factory.cc
@@ -108,7 +108,7 @@
       switch (it->second.result) {
         case ServiceWorkerSingleScriptUpdateChecker::Result::kIdentical:
           // Case D.1:
-          context_->storage()->GetNewResourceId(base::BindOnce(
+          context_->GetStorageControl()->GetNewResourceId(base::BindOnce(
               &ServiceWorkerScriptLoaderFactory::CopyScript,
               weak_factory_.GetWeakPtr(), it->first, it->second.old_resource_id,
               base::BindOnce(
@@ -136,7 +136,7 @@
 
   // Case D.3:
   // Assign a new resource ID for the script from network.
-  context_->storage()->GetNewResourceId(base::BindOnce(
+  context_->GetStorageControl()->GetNewResourceId(base::BindOnce(
       &ServiceWorkerScriptLoaderFactory::OnResourceIdAssignedForNewScriptLoader,
       weak_factory_.GetWeakPtr(), std::move(receiver), routing_id, request_id,
       options, resource_request, std::move(client), traffic_annotation));
diff --git a/content/browser/service_worker/service_worker_storage.cc b/content/browser/service_worker/service_worker_storage.cc
index 709af399..06af36d 100644
--- a/content/browser/service_worker/service_worker_storage.cc
+++ b/content/browser/service_worker/service_worker_storage.cc
@@ -260,7 +260,7 @@
 }
 
 void ServiceWorkerStorage::GetRegistrationsForOrigin(
-    const GURL& origin,
+    const url::Origin& origin,
     GetRegistrationsDataCallback callback) {
   switch (state_) {
     case STORAGE_STATE_DISABLED:
@@ -1359,7 +1359,8 @@
   // TODO(nhiroki): Add convenient method to ServiceWorkerDatabase to check the
   // unique origin list.
   RegistrationList registrations;
-  status = database->GetRegistrationsForOrigin(origin, &registrations, nullptr);
+  status = database->GetRegistrationsForOrigin(url::Origin::Create(origin),
+                                               &registrations, nullptr);
   if (status != ServiceWorkerDatabase::Status::kOk) {
     original_task_runner->PostTask(
         FROM_HERE, base::BindOnce(std::move(callback), OriginState::kKeep,
@@ -1399,7 +1400,7 @@
   GURL origin = client_url.GetOrigin();
   RegistrationList registration_data_list;
   ServiceWorkerDatabase::Status status = database->GetRegistrationsForOrigin(
-      origin, &registration_data_list, nullptr);
+      url::Origin::Create(origin), &registration_data_list, nullptr);
   if (status != ServiceWorkerDatabase::Status::kOk) {
     original_task_runner->PostTask(
         FROM_HERE, base::BindOnce(std::move(callback),
@@ -1435,7 +1436,7 @@
   GURL origin = scope.GetOrigin();
   RegistrationList registration_data_list;
   ServiceWorkerDatabase::Status status = database->GetRegistrationsForOrigin(
-      origin, &registration_data_list, nullptr);
+      url::Origin::Create(origin), &registration_data_list, nullptr);
   if (status != ServiceWorkerDatabase::Status::kOk) {
     original_task_runner->PostTask(
         FROM_HERE, base::BindOnce(std::move(callback),
diff --git a/content/browser/service_worker/service_worker_storage.h b/content/browser/service_worker/service_worker_storage.h
index c58f08e9..421e6fd 100644
--- a/content/browser/service_worker/service_worker_storage.h
+++ b/content/browser/service_worker/service_worker_storage.h
@@ -140,7 +140,7 @@
                                  FindRegistrationDataCallback callback);
 
   // Returns all stored registrations for a given origin.
-  void GetRegistrationsForOrigin(const GURL& origin,
+  void GetRegistrationsForOrigin(const url::Origin& origin,
                                  GetRegistrationsDataCallback callback);
 
   // Reads the total resource size stored in the storage for a given origin.
diff --git a/content/browser/service_worker/service_worker_storage_control_impl.cc b/content/browser/service_worker/service_worker_storage_control_impl.cc
index b1a524c..74cd4c1 100644
--- a/content/browser/service_worker/service_worker_storage_control_impl.cc
+++ b/content/browser/service_worker/service_worker_storage_control_impl.cc
@@ -159,7 +159,7 @@
     const GURL& origin,
     GetRegistrationsForOriginCallback callback) {
   storage_->GetRegistrationsForOrigin(
-      origin,
+      url::Origin::Create(origin),
       base::BindOnce(&DidGetRegistrationsForOrigin, std::move(callback)));
 }
 
diff --git a/content/browser/service_worker/service_worker_storage_unittest.cc b/content/browser/service_worker/service_worker_storage_unittest.cc
index 308631f4..402945f 100644
--- a/content/browser/service_worker/service_worker_storage_unittest.cc
+++ b/content/browser/service_worker/service_worker_storage_unittest.cc
@@ -399,7 +399,7 @@
   }
 
   blink::ServiceWorkerStatusCode GetRegistrationsForOrigin(
-      const GURL& origin,
+      const url::Origin& origin,
       std::vector<scoped_refptr<ServiceWorkerRegistration>>* registrations) {
     base::Optional<blink::ServiceWorkerStatusCode> result;
     base::RunLoop loop;
@@ -675,9 +675,9 @@
   EXPECT_FALSE(registry()->GetUninstallingRegistration(kScope.GetOrigin()));
 
   std::vector<scoped_refptr<ServiceWorkerRegistration>> found_registrations;
-  EXPECT_EQ(
-      blink::ServiceWorkerStatusCode::kErrorAbort,
-      GetRegistrationsForOrigin(kScope.GetOrigin(), &found_registrations));
+  EXPECT_EQ(blink::ServiceWorkerStatusCode::kErrorAbort,
+            GetRegistrationsForOrigin(url::Origin::Create(kScope),
+                                      &found_registrations));
 
   std::vector<ServiceWorkerRegistrationInfo> all_registrations;
   EXPECT_EQ(blink::ServiceWorkerStatusCode::kErrorAbort,
@@ -860,15 +860,16 @@
   // Finding by origin should provide the same result if origin is kScope.
   std::vector<scoped_refptr<ServiceWorkerRegistration>>
       registrations_for_origin;
-  EXPECT_EQ(
-      blink::ServiceWorkerStatusCode::kOk,
-      GetRegistrationsForOrigin(kScope.GetOrigin(), &registrations_for_origin));
+  EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk,
+            GetRegistrationsForOrigin(url::Origin::Create(kScope),
+                                      &registrations_for_origin));
   EXPECT_EQ(1u, registrations_for_origin.size());
   registrations_for_origin.clear();
 
   EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk,
-            GetRegistrationsForOrigin(GURL("http://example.com/").GetOrigin(),
-                                      &registrations_for_origin));
+            GetRegistrationsForOrigin(
+                url::Origin::Create(GURL("http://example.com/")),
+                &registrations_for_origin));
   EXPECT_TRUE(registrations_for_origin.empty());
 
   found_registration = nullptr;
@@ -971,14 +972,15 @@
 
   std::vector<scoped_refptr<ServiceWorkerRegistration>>
       registrations_for_origin;
-  EXPECT_EQ(
-      blink::ServiceWorkerStatusCode::kOk,
-      GetRegistrationsForOrigin(kScope.GetOrigin(), &registrations_for_origin));
+  EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk,
+            GetRegistrationsForOrigin(url::Origin::Create(kScope),
+                                      &registrations_for_origin));
   EXPECT_TRUE(registrations_for_origin.empty());
 
   EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk,
-            GetRegistrationsForOrigin(GURL("http://example.com/").GetOrigin(),
-                                      &registrations_for_origin));
+            GetRegistrationsForOrigin(
+                url::Origin::Create(GURL("http://example.com/")),
+                &registrations_for_origin));
   EXPECT_TRUE(registrations_for_origin.empty());
 
   // Notify storage of it being installed.
@@ -1012,15 +1014,16 @@
   all_registrations.clear();
 
   // Finding by origin should provide the same result if origin is kScope.
-  EXPECT_EQ(
-      blink::ServiceWorkerStatusCode::kOk,
-      GetRegistrationsForOrigin(kScope.GetOrigin(), &registrations_for_origin));
+  EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk,
+            GetRegistrationsForOrigin(url::Origin::Create(kScope),
+                                      &registrations_for_origin));
   EXPECT_EQ(1u, registrations_for_origin.size());
   registrations_for_origin.clear();
 
   EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk,
-            GetRegistrationsForOrigin(GURL("http://example.com/").GetOrigin(),
-                                      &registrations_for_origin));
+            GetRegistrationsForOrigin(
+                url::Origin::Create(GURL("http://example.com/")),
+                &registrations_for_origin));
   EXPECT_TRUE(registrations_for_origin.empty());
 
   // Notify storage of installation no longer happening.
@@ -1049,14 +1052,15 @@
             GetAllRegistrationsInfos(&all_registrations));
   EXPECT_TRUE(all_registrations.empty());
 
-  EXPECT_EQ(
-      blink::ServiceWorkerStatusCode::kOk,
-      GetRegistrationsForOrigin(kScope.GetOrigin(), &registrations_for_origin));
+  EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk,
+            GetRegistrationsForOrigin(url::Origin::Create(kScope),
+                                      &registrations_for_origin));
   EXPECT_TRUE(registrations_for_origin.empty());
 
   EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk,
-            GetRegistrationsForOrigin(GURL("http://example.com/").GetOrigin(),
-                                      &registrations_for_origin));
+            GetRegistrationsForOrigin(
+                url::Origin::Create(GURL("http://example.com/")),
+                &registrations_for_origin));
   EXPECT_TRUE(registrations_for_origin.empty());
 }
 
diff --git a/content/browser/service_worker/service_worker_update_checker.cc b/content/browser/service_worker/service_worker_update_checker.cc
index 7241823..7394d00 100644
--- a/content/browser/service_worker/service_worker_update_checker.cc
+++ b/content/browser/service_worker/service_worker_update_checker.cc
@@ -250,9 +250,10 @@
   DCHECK_NE(blink::mojom::kInvalidServiceWorkerResourceId, resource_id)
       << "All the target scripts should be stored in the storage.";
 
-  version_to_update_->context()->storage()->GetNewResourceId(base::BindOnce(
-      &ServiceWorkerUpdateChecker::OnResourceIdAssignedForOneScriptCheck,
-      weak_factory_.GetWeakPtr(), url, resource_id));
+  version_to_update_->context()->GetStorageControl()->GetNewResourceId(
+      base::BindOnce(
+          &ServiceWorkerUpdateChecker::OnResourceIdAssignedForOneScriptCheck,
+          weak_factory_.GetWeakPtr(), url, resource_id));
 }
 
 void ServiceWorkerUpdateChecker::OnResourceIdAssignedForOneScriptCheck(
diff --git a/content/browser/service_worker/service_worker_version.cc b/content/browser/service_worker/service_worker_version.cc
index bd382bf..ca5ae8ca 100644
--- a/content/browser/service_worker/service_worker_version.cc
+++ b/content/browser/service_worker/service_worker_version.cc
@@ -389,19 +389,12 @@
       script_origin(), registration_id(), version_id(),
       embedded_worker()->process_id(), embedded_worker()->thread_id(),
       embedded_worker()->worker_devtools_agent_route_id());
-
-  UMA_HISTOGRAM_COUNTS_10000("ServiceWorker.VersionInfo.ScriptURLLength",
-                             info.script_url.spec().length());
-
   for (const auto& controllee : controllee_map_) {
     ServiceWorkerContainerHost* container_host = controllee.second;
     info.clients.emplace(container_host->client_uuid(),
                          container_host->GetServiceWorkerClientInfo());
   }
 
-  UMA_HISTOGRAM_COUNTS_10000("ServiceWorker.VersionInfo.ClientCount",
-                             info.clients.size());
-
   info.script_response_time = script_response_time_for_devtools_;
   if (!main_script_response_)
     return info;
diff --git a/content/browser/site_instance_impl.cc b/content/browser/site_instance_impl.cc
index 618f093..532fc43 100644
--- a/content/browser/site_instance_impl.cc
+++ b/content/browser/site_instance_impl.cc
@@ -56,17 +56,20 @@
 
 // static
 SiteInfo SiteInfo::CreateForErrorPage() {
-  return SiteInfo(GURL(content::kUnreachableWebDataURL));
+  return SiteInfo(GURL(content::kUnreachableWebDataURL),
+                  GURL(content::kUnreachableWebDataURL));
 }
 
-SiteInfo::SiteInfo(const GURL& site_url) : site_url_(site_url) {}
+SiteInfo::SiteInfo(const GURL& site_url, const GURL& process_lock_url)
+    : site_url_(site_url), process_lock_url_(process_lock_url) {}
 
 bool SiteInfo::operator==(const SiteInfo& other) const {
-  return site_url_ == other.site_url_;
+  return site_url_ == other.site_url_ &&
+         process_lock_url_ == other.process_lock_url_;
 }
 
 bool SiteInfo::operator!=(const SiteInfo& other) const {
-  return site_url_ != other.site_url_;
+  return !(*this == other);
 }
 
 std::string SiteInfo::GetDebugString() const {
@@ -181,7 +184,7 @@
   // Setting site and lock directly without the site URL conversions we
   // do for user provided URLs. Callers expect GetSiteURL() to return the
   // value they provide in |guest_site_url|.
-  site_instance->SetSiteAndLockInternal(guest_site_url, guest_site_url);
+  site_instance->SetSiteInfoInternal(SiteInfo(guest_site_url, guest_site_url));
 
   return site_instance;
 }
@@ -215,11 +218,6 @@
   return GetContentClient()->browser()->ShouldAssignSiteForURL(url);
 }
 
-// static
-bool SiteInstanceImpl::IsOriginLockASite(const GURL& lock_url) {
-  return lock_url.has_scheme() && lock_url.has_host();
-}
-
 int32_t SiteInstanceImpl::GetId() {
   return id_;
 }
@@ -285,9 +283,10 @@
   // existing process that we would use if GetProcess() were called.
   BrowserContext* browser_context = browsing_instance_->GetBrowserContext();
   if (has_site_ &&
-      RenderProcessHostImpl::ShouldUseProcessPerSite(browser_context, site_) &&
-      RenderProcessHostImpl::GetSoleProcessHostForSite(
-          GetIsolationContext(), site_, lock_url_, IsGuest())) {
+      RenderProcessHostImpl::ShouldUseProcessPerSite(browser_context,
+                                                     site_info_) &&
+      RenderProcessHostImpl::GetSoleProcessHostForSite(GetIsolationContext(),
+                                                       site_info_, IsGuest())) {
     return true;
   }
 
@@ -308,8 +307,8 @@
 
     // Check if the ProcessReusePolicy should be updated.
     bool should_use_process_per_site =
-        has_site_ &&
-        RenderProcessHostImpl::ShouldUseProcessPerSite(browser_context, site_);
+        has_site_ && RenderProcessHostImpl::ShouldUseProcessPerSite(
+                         browser_context, site_info_);
     if (should_use_process_per_site) {
       process_reuse_policy_ = ProcessReusePolicy::PROCESS_PER_SITE;
     } else if (process_reuse_policy_ == ProcessReusePolicy::PROCESS_PER_SITE) {
@@ -337,7 +336,7 @@
   // process is used for a process-per-site site, it is ok to reuse this for the
   // new page (regardless of the site).
   if (HasSite() && RenderProcessHostImpl::ShouldUseProcessPerSite(
-                       browsing_instance_->GetBrowserContext(), site_)) {
+                       browsing_instance_->GetBrowserContext(), site_info_)) {
     return;
   }
 
@@ -346,8 +345,7 @@
   // different from this SiteInstance's site.
   if (!current_process->MayReuseHost() ||
       !RenderProcessHostImpl::IsSuitableHost(
-          current_process, GetIsolationContext(), site_.site_url(), lock_url(),
-          IsGuest())) {
+          current_process, GetIsolationContext(), site_info_, IsGuest())) {
     return;
   }
   SetProcessInternal(current_process);
@@ -378,7 +376,7 @@
   GetContentClient()->browser()->SiteInstanceGotProcess(this);
 
   if (has_site_)
-    LockToOriginIfNeeded();
+    LockProcessIfNeeded();
 }
 
 bool SiteInstanceImpl::CanAssociateWithSpareProcess() {
@@ -402,36 +400,29 @@
   DCHECK(!has_site_);
 
   original_url_ = url;
-
-  // Convert |url| into appropriate site and lock URLs that can be passed to
-  // SetSiteAndLockInternal(). We must do this transformation for any arbitrary
+  // Convert |url| into an appropriate SiteInfo that can be passed to
+  // SetSiteInfoInternal(). We must do this transformation for any arbitrary
   // URL we get from a user, a navigation, or script.
-  GURL site_url;
-  GURL lock_url;
-  browsing_instance_->GetSiteAndLockForURL(
-      url, /* allow_default_instance */ false, &site_url, &lock_url);
-
-  SetSiteAndLockInternal(site_url, lock_url);
+  SetSiteInfoInternal(browsing_instance_->GetSiteInfoForURL(
+      url, /* allow_default_instance */ false));
 }
 
-void SiteInstanceImpl::SetSiteAndLockInternal(const GURL& site_url,
-                                              const GURL& lock_url) {
+void SiteInstanceImpl::SetSiteInfoInternal(const SiteInfo& site_info) {
   // TODO(acolwell): Add logic to validate |site_url| and |lock_url| are valid.
   DCHECK(!has_site_);
 
   // Remember that this SiteInstance has been used to load a URL, even if the
   // URL is invalid.
   has_site_ = true;
-  site_ = SiteInfo(site_url);
-  lock_url_ = lock_url;
+  site_info_ = site_info;
 
-  // Check if |site_url| corresponds to an opt-in isolated origin, and if so,
+  // Check if |site_info| corresponds to an opt-in isolated origin, and if so,
   // track this origin in the current BrowsingInstance.  This is needed to
   // consistently isolate future navigations to this origin in this
   // BrowsingInstance, even if its opt-in status changes later.
   ChildProcessSecurityPolicyImpl* policy =
       ChildProcessSecurityPolicyImpl::GetInstance();
-  url::Origin site_origin(url::Origin::Create(site_url));
+  url::Origin site_origin(url::Origin::Create(site_info_.site_url()));
   // At this point, this should be a simple lookup on the master list, since
   // this SiteInstance is new to the BrowsingInstance.
   bool isolated = policy->ShouldOriginGetOptInIsolation(
@@ -450,13 +441,14 @@
   // Update the process reuse policy based on the site.
   BrowserContext* browser_context = browsing_instance_->GetBrowserContext();
   bool should_use_process_per_site =
-      RenderProcessHostImpl::ShouldUseProcessPerSite(browser_context, site_);
+      RenderProcessHostImpl::ShouldUseProcessPerSite(browser_context,
+                                                     site_info_);
   if (should_use_process_per_site) {
     process_reuse_policy_ = ProcessReusePolicy::PROCESS_PER_SITE;
   }
 
   if (process_) {
-    LockToOriginIfNeeded();
+    LockProcessIfNeeded();
 
     // Ensure the process is registered for this site if necessary.
     if (should_use_process_per_site)
@@ -476,11 +468,15 @@
 }
 
 const GURL& SiteInstanceImpl::GetSiteURL() {
-  return site_.site_url();
+  return site_info_.site_url();
 }
 
 const SiteInfo& SiteInstanceImpl::GetSiteInfo() {
-  return site_;
+  return site_info_;
+}
+
+const ProcessLock SiteInstanceImpl::GetProcessLock() const {
+  return ProcessLock(site_info_);
 }
 
 bool SiteInstanceImpl::HasSite() const {
@@ -517,24 +513,21 @@
   // pages, which should not commit successful navigations.  This check avoids a
   // process transfer for browser-initiated navigations to about:blank in a
   // dedicated process; without it, IsSuitableHost would consider this process
-  // unsuitable for about:blank when it compares origin locks.
+  // unsuitable for about:blank when it compares process locks.
   // Renderer-initiated navigations will handle about:blank navigations
   // elsewhere and leave them in the source SiteInstance, along with
   // about:srcdoc and data:.
-  if (url.IsAboutBlank() && site_ != SiteInfo::CreateForErrorPage())
+  if (url.IsAboutBlank() && site_info_ != SiteInfo::CreateForErrorPage())
     return true;
 
   // If the site URL is an extension (e.g., for hosted apps or WebUI) but the
   // process is not (or vice versa), make sure we notice and fix it.
-  GURL site_url;
-  GURL origin_lock;
 
   // Note: This call must return information that is identical to what
   // would be reported in the SiteInstance returned by
   // GetRelatedSiteInstance(url).
-  browsing_instance_->GetSiteAndLockForURL(
-      url, /* allow_default_instance */ true, &site_url, &origin_lock);
-  SiteInfo site_info(site_url);
+  SiteInfo site_info = browsing_instance_->GetSiteInfoForURL(
+      url, /* allow_default_instance */ true);
 
   // If this is a default SiteInstance and the BrowsingInstance gives us a
   // non-default site URL even when we explicitly allow the default SiteInstance
@@ -547,7 +540,7 @@
   // frame is a site that should be in the default SiteInstance and the
   // SiteInstance associated with that frame is initially a SiteInstance with
   // no site URL set.
-  if (IsDefaultSiteInstance() && site_info != site_)
+  if (IsDefaultSiteInstance() && site_info != site_info_)
     return false;
 
   // Note that HasProcess() may return true if process_ is null, in
@@ -563,14 +556,15 @@
     // If there is no process but there is a site, then the process must have
     // been discarded after we navigated away.  If the site URLs match, then it
     // is safe to use this SiteInstance.
-    if (site_ == site_info)
+    if (site_info_ == site_info)
       return true;
 
     // If the site URLs do not match, but neither this SiteInstance nor the
     // destination site_url require dedicated processes, then it is safe to use
     // this SiteInstance.
     if (!RequiresDedicatedProcess() &&
-        !DoesSiteURLRequireDedicatedProcess(GetIsolationContext(), site_url)) {
+        !DoesSiteURLRequireDedicatedProcess(GetIsolationContext(),
+                                            site_info.site_url())) {
       return true;
     }
 
@@ -581,7 +575,7 @@
   }
 
   return RenderProcessHostImpl::IsSuitableHost(
-      GetProcess(), GetIsolationContext(), site_url, origin_lock, IsGuest());
+      GetProcess(), GetIsolationContext(), site_info, IsGuest());
 }
 
 bool SiteInstanceImpl::RequiresDedicatedProcess() {
@@ -590,7 +584,7 @@
     return false;
 
   return DoesSiteURLRequireDedicatedProcess(GetIsolationContext(),
-                                            site_.site_url());
+                                            site_info_.site_url());
 }
 
 void SiteInstanceImpl::IncrementActiveFrameCount() {
@@ -665,16 +659,16 @@
     // TODO(acolwell): Remove HasSiteInstance() call once we have a way to
     // prevent SiteInstances with no site URL from being used for URLs
     // that should be routed to the default SiteInstance.
-    DCHECK_EQ(site_.site_url(), GetDefaultSiteURL());
-    return site_.site_url() ==
+    DCHECK_EQ(site_info_.site_url(), GetDefaultSiteURL());
+    return site_info_.site_url() ==
                GetSiteForURLInternal(GetIsolationContext(), url,
                                      true /* should_use_effective_urls */,
                                      true /* allow_default_site_url */) &&
            !browsing_instance_->HasSiteInstance(url);
   }
 
-  return SiteInstanceImpl::IsSameSite(GetIsolationContext(), site_.site_url(),
-                                      url,
+  return SiteInstanceImpl::IsSameSite(GetIsolationContext(),
+                                      site_info_.site_url(), url,
                                       true /* should_compare_effective_urls */);
 }
 
@@ -780,7 +774,7 @@
 bool SiteInstanceImpl::DoesSiteForURLMatch(const GURL& url) {
   // Note: The |allow_default_site_url| value used here MUST match the value
   // used in CreateForURL().
-  return site_.site_url() ==
+  return site_info_.site_url() ==
          GetSiteForURLInternal(GetIsolationContext(), url,
                                true /* should_use_effective_urls */,
                                true /* allow_default_site_url */);
@@ -809,17 +803,20 @@
   // where needed.  Eventually, GetSiteForURL should always require an
   // IsolationContext to be passed in, and this implementation should just
   // become SiteInstanceImpl::GetSiteForURL.
-  return SiteInstanceImpl::ComputeSiteInfo(IsolationContext(browser_context),
-                                           url)
-      .site_url();
+  return SiteInstanceImpl::GetSiteForURL(IsolationContext(browser_context),
+                                         url);
 }
 
 SiteInfo SiteInstanceImpl::ComputeSiteInfo(
     const IsolationContext& isolation_context,
     const GURL& url) {
+  // The call to GetSiteForURL() below is only allowed on the UI thread, due to
+  // its possible use of effective urls.
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   // This function will expand as more information, such as site-/origin-keying,
   // are included in SiteInfo.
-  return SiteInfo(GetSiteForURL(isolation_context, url));
+  return SiteInfo(GetSiteForURL(isolation_context, url),
+                  DetermineProcessLockURL(isolation_context, url));
 }
 
 // static
@@ -1087,7 +1084,7 @@
 }
 
 // static
-bool SiteInstanceImpl::ShouldLockToOrigin(
+bool SiteInstanceImpl::ShouldLockProcess(
     const IsolationContext& isolation_context,
     const GURL& site_url,
     const bool is_guest) {
@@ -1125,8 +1122,8 @@
   // call sites of ChildProcessSecurityPolicy::CanAccessDataForOrigin, we
   // must give the embedder a chance to exempt some sites to avoid process
   // kills.
-  if (!GetContentClient()->browser()->ShouldLockToOrigin(browser_context,
-                                                         site_url)) {
+  if (!GetContentClient()->browser()->ShouldLockProcess(browser_context,
+                                                        site_url)) {
     return false;
   }
 
@@ -1161,14 +1158,14 @@
     observer.RenderProcessGone(this, info);
 }
 
-void SiteInstanceImpl::LockToOriginIfNeeded() {
+void SiteInstanceImpl::LockProcessIfNeeded() {
   DCHECK(HasSite());
 
   // From now on, this process should be considered "tainted" for future
   // process reuse decisions:
-  // (1) If |site_| required a dedicated process, this SiteInstance's process
-  //     can only host URLs for the same site.
-  // (2) Even if |site_| does not require a dedicated process, this
+  // (1) If |site_info_| required a dedicated process, this SiteInstance's
+  //     process can only host URLs for the same site.
+  // (2) Even if |site_info_| does not require a dedicated process, this
   //     SiteInstance's process still cannot be reused to host other sites
   //     requiring dedicated sites in the future.
   // We can get here either when we commit a URL into a SiteInstance that does
@@ -1178,31 +1175,34 @@
 
   ChildProcessSecurityPolicyImpl* policy =
       ChildProcessSecurityPolicyImpl::GetInstance();
-  GURL process_lock = policy->GetOriginLock(process_->GetID());
-  if (ShouldLockToOrigin(GetIsolationContext(), site_.site_url(), IsGuest())) {
+  ProcessLock process_lock = policy->GetProcessLock(process_->GetID());
+  if (ShouldLockProcess(GetIsolationContext(), site_info_.site_url(),
+                        IsGuest())) {
     // Sanity check that this won't try to assign an origin lock to a <webview>
     // process, which can't be locked.
     CHECK(!process_->IsForGuestsOnly());
 
+    ProcessLock lock_to_set = GetProcessLock();
     if (process_lock.is_empty()) {
       // TODO(nick): When all sites are isolated, this operation provides
       // strong protection. If only some sites are isolated, we need
       // additional logic to prevent the non-isolated sites from requesting
       // resources for isolated sites. https://crbug.com/509125
-      TRACE_EVENT2("navigation", "SiteInstanceImpl::LockToOrigin", "site id",
-                   id_, "lock", lock_url().possibly_invalid_spec());
-      process_->LockToOrigin(GetIsolationContext(), lock_url());
-    } else if (process_lock != lock_url()) {
+      TRACE_EVENT2("navigation", "RenderProcessHost::SetProcessLock", "site id",
+                   id_, "lock", lock_to_set.ToString());
+      process_->SetProcessLock(GetIsolationContext(), lock_to_set);
+    } else if (process_lock != lock_to_set) {
       // We should never attempt to reassign a different origin lock to a
       // process.
       base::debug::SetCrashKeyString(bad_message::GetRequestedSiteURLKey(),
-                                     site_.GetDebugString());
+                                     site_info_.GetDebugString());
       policy->LogKilledProcessOriginLock(process_->GetID());
-      CHECK(false) << "Trying to lock a process to " << lock_url()
-                   << " but the process is already locked to " << process_lock;
+      CHECK(false) << "Trying to lock a process to " << lock_to_set.ToString()
+                   << " but the process is already locked to "
+                   << process_lock.ToString();
     } else {
       // Process already has the right origin lock assigned.  This case will
-      // happen for commits to |site_| after the first one.
+      // happen for commits to |site_info_| after the first one.
     }
   } else {
     // If the site that we've just committed doesn't require a dedicated
@@ -1210,10 +1210,10 @@
     // does.
     if (!process_lock.is_empty()) {
       base::debug::SetCrashKeyString(bad_message::GetRequestedSiteURLKey(),
-                                     site_.GetDebugString());
+                                     site_info_.GetDebugString());
       policy->LogKilledProcessOriginLock(process_->GetID());
-      CHECK(false) << "Trying to commit non-isolated site " << site_
-                   << " in process locked to " << process_lock;
+      CHECK(false) << "Trying to commit non-isolated site " << site_info_
+                   << " in process locked to " << process_lock.lock_url();
     }
   }
 
diff --git a/content/browser/site_instance_impl.h b/content/browser/site_instance_impl.h
index 571e638..48f4ee37 100644
--- a/content/browser/site_instance_impl.h
+++ b/content/browser/site_instance_impl.h
@@ -20,6 +20,7 @@
 
 namespace content {
 class BrowsingInstance;
+class ProcessLock;
 class RenderProcessHostFactory;
 
 // SiteInfo represents the principal of a SiteInstance. All documents and
@@ -50,7 +51,7 @@
  public:
   static SiteInfo CreateForErrorPage();
 
-  explicit SiteInfo(const GURL& site_url);
+  SiteInfo(const GURL& site_url, const GURL& process_lock_url);
   SiteInfo() = default;
 
   // Returns the site URL associated with all of the documents and workers in
@@ -71,6 +72,21 @@
   //   the same site_url() but that differ in other properties.
   const GURL& site_url() const { return site_url_; }
 
+  // Returns the URL which should be used in a SetProcessLock call for this
+  // SiteInfo's process.  This is the same as |site_url_| except for cases
+  // involving effective URLs, such as hosted apps.  In those cases, this URL is
+  // a site URL that is computed without the use of effective URLs.
+  //
+  // NOTE: This URL is currently set even in cases where this SiteInstance's
+  //       process is *not* going to be locked to it.  Callers should be careful
+  //       to consider this case when comparing lock URLs; ShouldLockProcess()
+  //       may be used to determine whether the process lock will actually be
+  //       used.
+  //
+  // TODO(alexmos): See if we can clean this up and not set |process_lock_url_|
+  //                if the SiteInstance's process isn't going to be locked.
+  const GURL& process_lock_url() const { return process_lock_url_; }
+
   bool operator==(const SiteInfo& other) const;
   bool operator!=(const SiteInfo& other) const;
 
@@ -79,6 +95,11 @@
 
  private:
   GURL site_url_;
+  // The URL to use when locking a process to this SiteInstance's site via
+  // SetProcessLock(). This is the same as |site_url_| except for cases
+  // involving effective URLs, such as hosted apps.  In those cases, this URL is
+  // a site URL that is computed without the use of effective URLs.
+  GURL process_lock_url_;
   // TODO(crbug.com/1067389): Add site vs origin granularity.
 };
 
@@ -132,12 +153,6 @@
 
   static bool ShouldAssignSiteForURL(const GURL& url);
 
-  // Returns whether |lock_url| is at least at the granularity of a site (i.e.,
-  // a scheme plus eTLD+1, like https://google.com).  Also returns true if the
-  // lock is to a more specific origin (e.g., https://accounts.google.com), but
-  // not if the lock is empty or applies to an entire scheme (e.g., file://).
-  static bool IsOriginLockASite(const GURL& lock_url);
-
   // Return whether both URLs are part of the same web site, for the purpose of
   // assigning them to processes accordingly.  The decision is currently based
   // on the registered domain of the URLs (google.com, bbc.co.uk), as well as
@@ -213,8 +228,9 @@
   // another SiteInstance from the same site.
   bool is_for_service_worker() const { return is_for_service_worker_; }
 
-  // Returns the URL which was used to set the |site_| for this SiteInstance.
-  // May be empty if this SiteInstance does not have a |site_|.
+  // Returns the URL which was used to set the |site_info_| for this
+  // SiteInstance. May be empty if this SiteInstance does not have a
+  // |site_info_|.
   const GURL& original_url() {
     DCHECK(!IsDefaultSiteInstance());
     return original_url_;
@@ -226,20 +242,6 @@
   bool IsOriginalUrlSameSite(const GURL& dest_url,
                              bool should_compare_effective_urls);
 
-  // Returns the URL which should be used in a LockToOrigin call for this
-  // SiteInstance's process.  This is the same as |site_| except for cases
-  // involving effective URLs, such as hosted apps.  In those cases, this URL
-  // is a site URL that is computed without the use of effective URLs.
-  //
-  // NOTE: This URL is currently set even in cases where this SiteInstance's
-  // process is *not* going to be locked to it.  Callers should be careful to
-  // consider this case when comparing lock URLs; ShouldLockToOrigin() may be
-  // used to determine whether the process lock will actually be used.
-  //
-  // TODO(alexmos): See if we can clean this up and not set |lock_url_| if the
-  // SiteInstance's process isn't going to be locked.
-  const GURL& lock_url() { return lock_url_; }
-
   // True if |url| resolves to an effective URL that is different from |url|.
   // See GetEffectiveURL().  This will be true for hosted apps as well as NTP
   // URLs.
@@ -253,6 +255,13 @@
   // GetSiteURL().
   const SiteInfo& GetSiteInfo();
 
+  // Returns a ProcessLock that can be used with SetProcessLock to lock a
+  // process to this SiteInstance's SiteInfo. The ProcessLock relies heavily on
+  // the SiteInfo's process_lock_url() for security decisions.
+  const ProcessLock GetProcessLock() const;
+
+  // This function returns a SiteInfo with the appropriate site_url and
+  // process_lock_url computed.
   // Note: eventually this function will replace GetSiteForURL().
   static SiteInfo ComputeSiteInfo(const IsolationContext& isolation_context,
                                   const GURL& url);
@@ -379,13 +388,15 @@
   //
   // Note that this function currently requires passing in a site URL (which
   // may use effective URLs), and not a lock URL to which the process may
-  // eventually be locked via LockToOrigin().  See comments on lock_url() for
-  // more info. |is_guest| should be set to true if the call is being made for
-  // a <webview> guest SiteInstance(i.e. SiteInstance::IsGuest() returns true).
+  // eventually be locked via SetProcessLock().  See comments on SiteInfo's
+  // process_lock_url() for more info. |is_guest| should be set to true if the
+  // call is being made for a <webview> guest SiteInstance(i.e.
+  // SiteInstance::IsGuest() returns true).
   // TODO(alexmos):  See if this can take a lock URL instead.
-  static bool ShouldLockToOrigin(const IsolationContext& isolation_context,
-                                 const GURL& site_url,
-                                 const bool is_guest);
+  // TODO(wjmaclean): Or see if this can take a SiteInfo or ProcessLock instead.
+  static bool ShouldLockProcess(const IsolationContext& isolation_context,
+                                const GURL& site_url,
+                                const bool is_guest);
 
   // Converts |lock_url| into an origin that can be used as
   // |URLLoaderFactoryParams::request_initiator_site_lock|.
@@ -447,7 +458,7 @@
                            const ChildProcessTerminationInfo& info) override;
 
   // Used to restrict a process' origin access rights.
-  void LockToOriginIfNeeded();
+  void LockProcessIfNeeded();
 
   // If kProcessSharingWithStrictSiteInstances is enabled, this will check
   // whether both a site and a process have been assigned to this SiteInstance,
@@ -456,12 +467,12 @@
   // a dedicated process.
   void MaybeSetBrowsingInstanceDefaultProcess();
 
-  // Sets |site_| and |lock_| with |site_url| and |lock_url| respectively
-  // and registers this object with |browsing_instance_|. SetSite() calls
-  // this method to set the site and lock for a user provided URL. This
-  // method should only be called by code that need to set the site and
-  // lock directly without any "url to site URL" transformation.
-  void SetSiteAndLockInternal(const GURL& site_url, const GURL& lock_url);
+  // Sets |site_info_| with |site_info| and registers this object with
+  // |browsing_instance_|. SetSite() calls this method to set the site and lock
+  // for a user provided URL. This method should only be called by code that
+  // need to set the site and lock directly without any "url to site URL"
+  // transformation.
+  void SetSiteInfoInternal(const SiteInfo& site_info);
 
   // Helper method to set the process of this SiteInstance, only in cases
   // where it is safe. It is not generally safe to change the process of a
@@ -531,21 +542,15 @@
   // |process_|.
   bool can_associate_with_spare_process_;
 
-  // The web site that this SiteInstance is rendering pages for.
-  SiteInfo site_;
+  // The SiteInfo that this SiteInstance is rendering pages for.
+  SiteInfo site_info_;
 
   // Whether SetSite has been called.
   bool has_site_;
 
-  // The URL which was used to set the |site_| for this SiteInstance.
+  // The URL which was used to set the |site_info_| for this SiteInstance.
   GURL original_url_;
 
-  // The URL to use when locking a process to this SiteInstance's site via
-  // LockToOrigin().  This is the same as |site_| except for cases involving
-  // effective URLs, such as hosted apps.  In those cases, this URL is a site
-  // URL that is computed without the use of effective URLs.
-  GURL lock_url_;
-
   // The ProcessReusePolicy to use when creating a RenderProcessHost for this
   // SiteInstance.
   ProcessReusePolicy process_reuse_policy_;
diff --git a/content/browser/site_instance_impl_unittest.cc b/content/browser/site_instance_impl_unittest.cc
index cf4fa77..09f43b1 100644
--- a/content/browser/site_instance_impl_unittest.cc
+++ b/content/browser/site_instance_impl_unittest.cc
@@ -480,7 +480,7 @@
     scoped_refptr<SiteInstanceImpl> site_instance =
         SiteInstanceImpl::CreateForURL(browser_context.get(), test_url);
     EXPECT_EQ(expected_app_site_url, site_instance->GetSiteURL());
-    EXPECT_EQ(nonapp_site_url, site_instance->lock_url());
+    EXPECT_EQ(nonapp_site_url, site_instance->GetSiteInfo().process_lock_url());
   }
 
   // New related SiteInstance from an existing SiteInstance with a
@@ -494,7 +494,8 @@
     auto* site_instance_impl =
         static_cast<SiteInstanceImpl*>(site_instance.get());
     EXPECT_EQ(expected_app_site_url, site_instance->GetSiteURL());
-    EXPECT_EQ(nonapp_site_url, site_instance_impl->lock_url());
+    EXPECT_EQ(nonapp_site_url,
+              site_instance_impl->GetSiteInfo().process_lock_url());
   }
 
   // New SiteInstance with a lazily assigned site URL.
@@ -504,7 +505,7 @@
     EXPECT_FALSE(site_instance->HasSite());
     site_instance->SetSite(test_url);
     EXPECT_EQ(expected_app_site_url, site_instance->GetSiteURL());
-    EXPECT_EQ(nonapp_site_url, site_instance->lock_url());
+    EXPECT_EQ(nonapp_site_url, site_instance->GetSiteInfo().process_lock_url());
   }
 
   SetBrowserClientForTesting(regular_client);
@@ -876,7 +877,7 @@
   EXPECT_FALSE(RenderProcessHostImpl::GetSoleProcessHostForURL(
       instance->GetIsolationContext(), GURL()));
   EXPECT_FALSE(RenderProcessHostImpl::GetSoleProcessHostForSite(
-      instance->GetIsolationContext(), SiteInfo(), GURL(), false));
+      instance->GetIsolationContext(), SiteInfo(), false));
 
   DrainMessageLoop();
 }
@@ -1336,17 +1337,26 @@
   SetBrowserClientForTesting(regular_client);
 }
 
-TEST_F(SiteInstanceTest, IsOriginLockASite) {
-  EXPECT_FALSE(SiteInstanceImpl::IsOriginLockASite(GURL("http://")));
-  EXPECT_FALSE(SiteInstanceImpl::IsOriginLockASite(GURL("")));
-  EXPECT_FALSE(SiteInstanceImpl::IsOriginLockASite(GURL("google.com")));
-  EXPECT_FALSE(SiteInstanceImpl::IsOriginLockASite(GURL("http:")));
-  EXPECT_FALSE(SiteInstanceImpl::IsOriginLockASite(GURL("chrome:")));
+namespace {
 
-  EXPECT_TRUE(SiteInstanceImpl::IsOriginLockASite(GURL("http://foo.com")));
-  EXPECT_TRUE(SiteInstanceImpl::IsOriginLockASite(GURL("http://bar.foo.com")));
-  EXPECT_TRUE(SiteInstanceImpl::IsOriginLockASite(
-      GURL("http://user:pass@google.com:99/foo;bar?q=a#ref")));
+ProcessLock ProcessLockFromString(const std::string& url) {
+  return ProcessLock(SiteInfo(GURL(url), GURL(url)));
+}
+
+}  // namespace
+
+TEST_F(SiteInstanceTest, IsProcessLockASite) {
+  EXPECT_FALSE(ProcessLockFromString("http://").IsASiteOrOrigin());
+  EXPECT_FALSE(ProcessLockFromString("").IsASiteOrOrigin());
+  EXPECT_FALSE(ProcessLockFromString("google.com").IsASiteOrOrigin());
+  EXPECT_FALSE(ProcessLockFromString("http:").IsASiteOrOrigin());
+  EXPECT_FALSE(ProcessLockFromString("chrome:").IsASiteOrOrigin());
+
+  EXPECT_TRUE(ProcessLockFromString("http://foo.com").IsASiteOrOrigin());
+  EXPECT_TRUE(ProcessLockFromString("http://bar.foo.com").IsASiteOrOrigin());
+  EXPECT_TRUE(
+      ProcessLockFromString("http://user:pass@google.com:99/foo;bar?q=a#ref")
+          .IsASiteOrOrigin());
 }
 
 TEST_F(SiteInstanceTest, StartIsolatingSite) {
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index 2bcc25a..0f0b4a5 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -14672,8 +14672,8 @@
       SiteInstanceImpl::DetermineProcessLockURL(isolation_context, start_url);
   auto another_url_lock =
       SiteInstanceImpl::DetermineProcessLockURL(isolation_context, another_url);
-  EXPECT_EQ(start_url_lock, policy->GetOriginLock(process_id));
-  EXPECT_NE(another_url_lock, policy->GetOriginLock(process_id));
+  EXPECT_EQ(start_url_lock, policy->GetProcessLock(process_id).lock_url());
+  EXPECT_NE(another_url_lock, policy->GetProcessLock(process_id).lock_url());
 
   // Transfer the NavigationRequest ownership to the RenderFrameHost. The test
   // for NavigationRequest match happens before the check of origin lock and
diff --git a/content/browser/url_loader_factory_params_helper.cc b/content/browser/url_loader_factory_params_helper.cc
index c26cc6d..c596b14 100644
--- a/content/browser/url_loader_factory_params_helper.cc
+++ b/content/browser/url_loader_factory_params_helper.cc
@@ -207,10 +207,10 @@
   // Attempt to use the process lock as |request_initiator_site_lock|.
   base::Optional<url::Origin> request_initiator_site_lock;
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
-  GURL process_lock = policy->GetOriginLock(process->GetID());
-  if (process_lock.is_valid()) {
+  ProcessLock process_lock = policy->GetProcessLock(process->GetID());
+  if (process_lock.lock_url().is_valid()) {
     request_initiator_site_lock =
-        SiteInstanceImpl::GetRequestInitiatorSiteLock(process_lock);
+        SiteInstanceImpl::GetRequestInitiatorSiteLock(process_lock.lock_url());
   }
 
   // Since this function is about to get deprecated (crbug.com/1098938), it
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 3378de15..355b270 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -4895,8 +4895,7 @@
 }
 #endif
 
-void WebContentsImpl::OnDidDisplayContentWithCertificateErrors(
-    RenderFrameHostImpl* source) {
+void WebContentsImpl::OnDidDisplayContentWithCertificateErrors() {
   controller_.ssl_manager()->DidDisplayContentWithCertErrors();
 }
 
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index bb70f5e6..0e2d824 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -1200,7 +1200,7 @@
   void OnDidRunInsecureContent(RenderFrameHostImpl* source,
                                const GURL& security_origin,
                                const GURL& target_url);
-  void OnDidDisplayContentWithCertificateErrors(RenderFrameHostImpl* source);
+  void OnDidDisplayContentWithCertificateErrors();
   void OnDidRunContentWithCertificateErrors(RenderFrameHostImpl* source);
 
   JavaScriptDialogNavigationDeferrer* GetJavaScriptDialogNavigationDeferrer() {
@@ -2085,7 +2085,7 @@
  private:
   friend class TestNavigationObserver;
   friend class WebContentsAddedObserver;
-  friend class ContentBrowserSequenceChecker;
+  friend class ContentBrowserConsistencyChecker;
 
   FriendWrapper();  // Not instantiable.
 
diff --git a/content/browser/webrtc/webrtc_audio_debug_recordings_browsertest.cc b/content/browser/webrtc/webrtc_audio_debug_recordings_browsertest.cc
index 5921b24..8b0c52d 100644
--- a/content/browser/webrtc/webrtc_audio_debug_recordings_browsertest.cc
+++ b/content/browser/webrtc/webrtc_audio_debug_recordings_browsertest.cc
@@ -175,7 +175,7 @@
 
   // Verify that no other files exist and remove temp dir.
   EXPECT_TRUE(base::IsDirectoryEmpty(temp_dir_path));
-  EXPECT_TRUE(base::DeleteFile(temp_dir_path, false));
+  EXPECT_TRUE(base::DeleteFile(temp_dir_path));
 
   base::ThreadRestrictions::SetIOAllowed(prev_io_allowed);
 }
@@ -228,7 +228,7 @@
 
   // Verify that no files exist and remove temp dir.
   EXPECT_TRUE(base::IsDirectoryEmpty(temp_dir_path));
-  EXPECT_TRUE(base::DeleteFile(temp_dir_path, false));
+  EXPECT_TRUE(base::DeleteFile(temp_dir_path));
 
   base::ThreadRestrictions::SetIOAllowed(prev_io_allowed);
 }
@@ -333,7 +333,7 @@
 
   // Verify that no other files exist and remove temp dir.
   EXPECT_TRUE(base::IsDirectoryEmpty(temp_dir_path));
-  EXPECT_TRUE(base::DeleteFile(temp_dir_path, false));
+  EXPECT_TRUE(base::DeleteFile(temp_dir_path));
 
   base::ThreadRestrictions::SetIOAllowed(prev_io_allowed);
 }
diff --git a/content/browser/webui/web_ui_navigation_browsertest.cc b/content/browser/webui/web_ui_navigation_browsertest.cc
index c77fb54..a0979a1 100644
--- a/content/browser/webui/web_ui_navigation_browsertest.cc
+++ b/content/browser/webui/web_ui_navigation_browsertest.cc
@@ -656,10 +656,10 @@
   EXPECT_EQ(main_frame_url, webui_rfh->GetLastCommittedURL());
   EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
       webui_rfh->GetProcess()->GetID()));
-  EXPECT_FALSE(webui_site_instance->lock_url().is_empty());
-  EXPECT_EQ(ChildProcessSecurityPolicyImpl::GetInstance()->GetOriginLock(
+  EXPECT_TRUE(webui_site_instance->GetSiteInfo().process_lock_url().is_valid());
+  EXPECT_EQ(ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock(
                 root->current_frame_host()->GetProcess()->GetID()),
-            webui_site_instance->lock_url());
+            webui_site_instance->GetProcessLock());
 
   TestUntrustedDataSourceCSP csp;
   std::vector<std::string> frame_ancestors({"chrome://web-ui"});
@@ -851,9 +851,9 @@
   EXPECT_EQ(chrome_url, webui_rfh->GetLastCommittedURL());
   EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
       webui_rfh->GetProcess()->GetID()));
-  EXPECT_EQ(ChildProcessSecurityPolicyImpl::GetInstance()->GetOriginLock(
+  EXPECT_EQ(ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock(
                 root->current_frame_host()->GetProcess()->GetID()),
-            webui_site_instance->lock_url());
+            webui_site_instance->GetProcessLock());
 
   GURL web_url(embedded_test_server()->GetURL("/title2.html"));
   std::string script =
@@ -870,9 +870,9 @@
       root->current_frame_host()->GetSiteInstance()));
   EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
       root->current_frame_host()->GetProcess()->GetID()));
-  EXPECT_NE(ChildProcessSecurityPolicyImpl::GetInstance()->GetOriginLock(
+  EXPECT_NE(ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock(
                 root->current_frame_host()->GetProcess()->GetID()),
-            webui_site_instance->lock_url());
+            webui_site_instance->GetProcessLock());
 }
 
 #if !defined(OS_ANDROID)
diff --git a/content/common/common_param_traits_macros.h b/content/common/common_param_traits_macros.h
index 1209aecf..3c0edcb 100644
--- a/content/common/common_param_traits_macros.h
+++ b/content/common/common_param_traits_macros.h
@@ -11,6 +11,7 @@
 #include "cc/trees/browser_controls_params.h"
 #include "content/common/frame_messages.h"
 #include "content/common/visual_properties.h"
+#include "content/public/common/screen_orientation_values.h"
 #include "ipc/ipc_message_macros.h"
 #include "third_party/blink/public/web/web_device_emulation_params.h"
 
@@ -24,9 +25,8 @@
 IPC_ENUM_TRAITS_MAX_VALUE(content::ScreenOrientationValues,
                           content::SCREEN_ORIENTATION_VALUES_LAST)
 
-IPC_ENUM_TRAITS_MIN_MAX_VALUE(blink::WebScreenOrientationType,
-                              blink::kWebScreenOrientationUndefined,
-                              blink::WebScreenOrientationTypeLast)
+IPC_ENUM_TRAITS_MAX_VALUE(blink::mojom::ScreenOrientation,
+                          blink::mojom::ScreenOrientation::kMaxValue)
 
 IPC_ENUM_TRAITS_MAX_VALUE(blink::mojom::DisplayMode,
                           blink::mojom::DisplayMode::kMaxValue)
diff --git a/content/common/content_navigation_policy.cc b/content/common/content_navigation_policy.cc
index 0095333..c6d1aef9 100644
--- a/content/common/content_navigation_policy.cc
+++ b/content/common/content_navigation_policy.cc
@@ -91,8 +91,18 @@
 }
 
 bool IsProactivelySwapBrowsingInstanceOnSameSiteNavigationEnabled() {
-  return GetProactivelySwapBrowsingInstanceLevel() >=
-         ProactivelySwapBrowsingInstanceLevel::kSameSite;
+  // Same-site proactive BrowsingInstance swap is enabled if we set the level of
+  // the ProactivelySwapBrowsingInstance feature to >= kSameSite, or if we
+  // enable back-forward cache on same-site navigations.
+  if (GetProactivelySwapBrowsingInstanceLevel() >=
+      ProactivelySwapBrowsingInstanceLevel::kSameSite) {
+    return true;
+  }
+  if (!IsBackForwardCacheEnabled())
+    return false;
+  static constexpr base::FeatureParam<bool> enable_same_site_back_forward_cache(
+      &features::kBackForwardCache, "enable_same_site", false);
+  return enable_same_site_back_forward_cache.Get();
 }
 
 const char kRenderDocumentLevelParameterName[] = "level";
diff --git a/content/common/frame_messages.h b/content/common/frame_messages.h
index 6944dff..391b67f5 100644
--- a/content/common/frame_messages.h
+++ b/content/common/frame_messages.h
@@ -645,16 +645,6 @@
 // The message is delivered using RenderWidget::QueueMessage.
 IPC_MESSAGE_ROUTED1(FrameHostMsg_VisualStateResponse, uint64_t /* id */)
 
-// Ask the frame host to print a cross-process subframe.
-// The printed content of this subframe belongs to the document specified by
-// its document cookie. Document cookie is a unique id for a printed document
-// associated with a print job.
-// The content will be rendered in the specified rectangular area in its parent
-// frame.
-IPC_MESSAGE_ROUTED2(FrameHostMsg_PrintCrossProcessSubframe,
-                    gfx::Rect /* rect area of the frame content */,
-                    int /* rendered document cookie */)
-
 // Adding a new message? Stick to the sort order above: first platform
 // independent FrameMsg, then ifdefs for platform specific FrameMsg, then
 // platform independent FrameHostMsg, then ifdefs for platform specific
diff --git a/content/common/widget_messages.h b/content/common/widget_messages.h
index 472a935..fa5f9cd8 100644
--- a/content/common/widget_messages.h
+++ b/content/common/widget_messages.h
@@ -16,7 +16,6 @@
 #include "content/common/visual_properties.h"
 #include "content/public/common/common_param_traits.h"
 #include "ipc/ipc_message_macros.h"
-#include "third_party/blink/public/common/screen_orientation/web_screen_orientation_type.h"
 #include "third_party/blink/public/platform/viewport_intersection_state.h"
 #include "third_party/blink/public/platform/web_float_rect.h"
 #include "ui/base/ime/text_input_action.h"
diff --git a/content/public/android/java/src/org/chromium/content/browser/accessibility/PieWebContentsAccessibility.java b/content/public/android/java/src/org/chromium/content/browser/accessibility/PieWebContentsAccessibility.java
index 849b38f3..3e1a34b 100644
--- a/content/public/android/java/src/org/chromium/content/browser/accessibility/PieWebContentsAccessibility.java
+++ b/content/public/android/java/src/org/chromium/content/browser/accessibility/PieWebContentsAccessibility.java
@@ -7,6 +7,7 @@
 import android.annotation.TargetApi;
 import android.os.Build;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.autofill.AutofillManager;
 
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.content_public.browser.WebContents;
@@ -19,6 +20,15 @@
 public class PieWebContentsAccessibility extends OWebContentsAccessibility {
     PieWebContentsAccessibility(WebContents webContents) {
         super(webContents);
+        AutofillManager autofillManager = mContext.getSystemService(AutofillManager.class);
+        if (autofillManager != null && autofillManager.isEnabled()) {
+            // Native accessibility is usually initialized when getAccessibilityNodeProvider is
+            // called, but the Autofill compatibility bridge only calls that method after it has
+            // received the first accessibility events. To solve the chicken-and-egg problem,
+            // always initialize the native parts when the user has an Autofill service enabled.
+            refreshState();
+            getAccessibilityNodeProvider();
+        }
     }
 
     @Override
diff --git a/content/public/browser/BUILD.gn b/content/public/browser/BUILD.gn
index 1699a26..1a17694 100644
--- a/content/public/browser/BUILD.gn
+++ b/content/public/browser/BUILD.gn
@@ -192,6 +192,7 @@
     "idle_manager.h",
     "installability_error.cc",
     "installability_error.h",
+    "installed_payment_apps_finder.h",
     "invalidate_type.h",
     "javascript_dialog_manager.cc",
     "javascript_dialog_manager.h",
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 3fe77cc..f1acef5 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -145,8 +145,8 @@
   return false;
 }
 
-bool ContentBrowserClient::ShouldLockToOrigin(BrowserContext* browser_context,
-                                              const GURL& effective_url) {
+bool ContentBrowserClient::ShouldLockProcess(BrowserContext* browser_context,
+                                             const GURL& effective_url) {
   DCHECK(browser_context);
   return true;
 }
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index a781173..f800969 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -366,9 +366,9 @@
   // isolation is enabled for this URL, and is a bug workaround.
   //
   // TODO(nick): Remove this function once https://crbug.com/160576 is fixed,
-  // and origin lock can be applied to all URLs.
-  virtual bool ShouldLockToOrigin(BrowserContext* browser_context,
-                                  const GURL& effective_url);
+  // and ProcessLock can be applied to all URLs.
+  virtual bool ShouldLockProcess(BrowserContext* browser_context,
+                                 const GURL& effective_url);
 
   // Returns a boolean indicating whether the WebUI |scheme| requires its
   // process to be locked to the WebUI origin.
diff --git a/content/public/browser/installed_payment_apps_finder.h b/content/public/browser/installed_payment_apps_finder.h
new file mode 100644
index 0000000..2f2d7d4
--- /dev/null
+++ b/content/public/browser/installed_payment_apps_finder.h
@@ -0,0 +1,51 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_PUBLIC_BROWSER_INSTALLED_PAYMENT_APPS_FINDER_H_
+#define CONTENT_PUBLIC_BROWSER_INSTALLED_PAYMENT_APPS_FINDER_H_
+
+#include "base/callback_forward.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/stored_payment_app.h"
+#include "third_party/blink/public/mojom/payments/payment_app.mojom.h"
+
+namespace content {
+
+class BrowserContext;
+
+// This is helper class for retrieving installed payment apps.
+// The instance of this class can be retrieved using the static
+// GetInstance() method. All methods must be called on the UI thread.
+
+// Example:
+//    base::WeakPtr<InstalledPaymentAppsFinder> finder =
+//      content::InstalledPaymentAppsFinder::GetInstance(context);
+
+class CONTENT_EXPORT InstalledPaymentAppsFinder {
+ public:
+  // All functions are actually implemented in
+  // InstalledPaymentAppsFinderImpl.cc. Please see:
+  // content/browser/payments/installed_payment_apps_finder.cc
+
+  // This class is owned BrowserContext, and will be reuse the existing one
+  // using GetInstance() if an instance already exists with BrowserContext.
+  static base::WeakPtr<InstalledPaymentAppsFinder> GetInstance(
+      BrowserContext* context);
+
+  using PaymentApps = std::map<int64_t, std::unique_ptr<StoredPaymentApp>>;
+  using GetAllPaymentAppsCallback = base::OnceCallback<void(PaymentApps)>;
+
+  // This method is used to query all registered payment apps with payment
+  // instruments in browser side. When merchant site requests payment to browser
+  // via PaymentRequest API, then the UA will display the all proper stored
+  // payment instruments by this method.
+  virtual void GetAllPaymentApps(GetAllPaymentAppsCallback callback) = 0;
+
+ protected:
+  virtual ~InstalledPaymentAppsFinder() = default;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_PUBLIC_BROWSER_INSTALLED_PAYMENT_APPS_FINDER_H_
diff --git a/content/public/browser/navigation_handle.h b/content/public/browser/navigation_handle.h
index 15ca24c3..ec1ab7f4c 100644
--- a/content/public/browser/navigation_handle.h
+++ b/content/public/browser/navigation_handle.h
@@ -136,9 +136,7 @@
   virtual const GURL& GetSearchableFormURL() = 0;
   virtual const std::string& GetSearchableFormEncoding() = 0;
 
-  // Returns the reload type for this navigation. Note that renderer-initiated
-  // reloads (via location.reload()) won't count as a reload and do return
-  // ReloadType::NONE.
+  // Returns the reload type for this navigation.
   virtual ReloadType GetReloadType() = 0;
 
   // Returns the restore type for this navigation. RestoreType::NONE is returned
diff --git a/content/public/browser/payment_app_provider.h b/content/public/browser/payment_app_provider.h
index a317257..e8c4cd0a 100644
--- a/content/public/browser/payment_app_provider.h
+++ b/content/public/browser/payment_app_provider.h
@@ -5,14 +5,8 @@
 #ifndef CONTENT_PUBLIC_BROWSER_PAYMENT_APP_PROVIDER_H_
 #define CONTENT_PUBLIC_BROWSER_PAYMENT_APP_PROVIDER_H_
 
-#include <stdint.h>
-#include <memory>
-#include <utility>
-#include <vector>
-
 #include "base/callback_forward.h"
 #include "content/common/content_export.h"
-#include "content/public/browser/stored_payment_app.h"
 #include "services/metrics/public/cpp/ukm_recorder.h"
 #include "third_party/blink/public/mojom/payments/payment_app.mojom.h"
 
@@ -26,6 +20,7 @@
 
 class BrowserContext;
 class WebContents;
+struct SupportedDelegations;
 
 // This is providing the service worker based payment app related APIs to
 // Chrome layer. This class is a singleton, the instance of which can be
@@ -40,8 +35,6 @@
   // Please see: content/browser/payments/payment_app_provider_impl.cc
   static PaymentAppProvider* GetInstance();
 
-  using PaymentApps = std::map<int64_t, std::unique_ptr<StoredPaymentApp>>;
-  using GetAllPaymentAppsCallback = base::OnceCallback<void(PaymentApps)>;
   using RegistrationIdCallback =
       base::OnceCallback<void(int64_t registration_id)>;
   using InvokePaymentAppCallback =
@@ -53,8 +46,6 @@
       base::OnceCallback<void(payments::mojom::PaymentHandlerStatus status)>;
 
   // Should be accessed only on the UI thread.
-  virtual void GetAllPaymentApps(BrowserContext* browser_context,
-                                 GetAllPaymentAppsCallback callback) = 0;
   virtual void InvokePaymentApp(
       WebContents* web_contents,
       int64_t registration_id,
diff --git a/content/public/browser/render_frame_host.h b/content/public/browser/render_frame_host.h
index 366e0360..95ea1bb 100644
--- a/content/public/browser/render_frame_host.h
+++ b/content/public/browser/render_frame_host.h
@@ -104,13 +104,14 @@
   // frame that is currently rendered in a different process than |process_id|.
   static int GetFrameTreeNodeIdForRoutingId(int process_id, int routing_id);
 
-  // Returns the RenderFrameHost corresponding to the |placeholder_routing_id|
-  // in the given |render_process_id|. The returned RenderFrameHost will always
-  // be in a different process.  It may be null if the placeholder is not found
-  // in the given process, which may happen if the frame was recently deleted
-  // or swapped to |render_process_id| itself.
-  static RenderFrameHost* FromPlaceholderId(int render_process_id,
-                                            int placeholder_routing_id);
+  // Returns the RenderFrameHost corresponding to the
+  // |placeholder_frame_token| in the given |render_process_id|. The returned
+  // RenderFrameHost will always be in a different process.  It may be null if
+  // the placeholder is not found in the given process, which may happen if the
+  // frame was recently deleted or swapped to |render_process_id| itself.
+  static RenderFrameHost* FromPlaceholderToken(
+      int render_process_id,
+      const base::UnguessableToken& placeholder_frame_token);
 
 #if defined(OS_ANDROID)
   // Returns the RenderFrameHost object associated with a Java native pointer.
diff --git a/content/public/browser/render_process_host.h b/content/public/browser/render_process_host.h
index 99cd8e9d..5b4ecff 100644
--- a/content/public/browser/render_process_host.h
+++ b/content/public/browser/render_process_host.h
@@ -70,6 +70,7 @@
 class BrowserContext;
 class BrowserMessageFilter;
 class IsolationContext;
+class ProcessLock;
 class RenderProcessHostObserver;
 class StoragePartition;
 #if defined(OS_ANDROID)
@@ -460,17 +461,16 @@
   // crbug.com/738634.
   virtual bool HostHasNotBeenUsed() = 0;
 
-  // Locks this RenderProcessHost to the 'origin' |lock_url|. This method is
-  // public so that it can be called from SiteInstanceImpl, and used by
-  // MockRenderProcessHost. It isn't meant to be called outside of content.
-  // TODO(creis): Rename LockToOrigin to LockToPrincipal. See
-  // https://crbug.com/846155.
-  virtual void LockToOrigin(const IsolationContext& isolation_context,
-                            const GURL& lock_url) = 0;
+  // Locks this RenderProcessHost to documents compatible with |process_lock|.
+  // This method is public so that it can be called from SiteInstanceImpl, and
+  // used by MockRenderProcessHost. It isn't meant to be called outside of
+  // content.
+  virtual void SetProcessLock(const IsolationContext& isolation_context,
+                              const ProcessLock& process_lock) = 0;
 
-  // Returns true if this process is locked to a particular 'origin'.  See the
-  // LockToOrigin() call above.
-  virtual bool IsLockedToOriginForTesting() = 0;
+  // Returns true if this process is locked to a particular ProcessLock.  See
+  // the SetProcessLock() call above.
+  virtual bool IsProcessLockedForTesting() = 0;
 
   // The following several methods are for internal use only, and are only
   // exposed here to support MockRenderProcessHost usage in tests.
diff --git a/content/public/browser/service_worker_context.h b/content/public/browser/service_worker_context.h
index 522bd6a..a377246c 100644
--- a/content/public/browser/service_worker_context.h
+++ b/content/public/browser/service_worker_context.h
@@ -186,7 +186,7 @@
   // service workers belonging to the registrations. All clients controlled by
   // those service workers will lose their controllers immediately after this
   // operation.
-  virtual void DeleteForOrigin(const GURL& origin_url,
+  virtual void DeleteForOrigin(const url::Origin& origin_url,
                                ResultCallback callback) = 0;
 
   // Performs internal storage cleanup. Operations to the storage in the past
diff --git a/content/public/common/screen_info.h b/content/public/common/screen_info.h
index c7dfd7a..706ee42 100644
--- a/content/public/common/screen_info.h
+++ b/content/public/common/screen_info.h
@@ -7,7 +7,7 @@
 
 #include "build/build_config.h"
 #include "content/common/content_export.h"
-#include "content/public/common/screen_orientation_values.h"
+#include "third_party/blink/public/mojom/widget/screen_orientation.mojom-shared.h"
 #include "ui/gfx/color_space.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/icc_profile.h"
@@ -50,8 +50,8 @@
     gfx::Rect available_rect;
 
     // The monitor's orientation.
-    ScreenOrientationValues orientation_type =
-        SCREEN_ORIENTATION_VALUES_DEFAULT;
+    blink::mojom::ScreenOrientation orientation_type =
+        blink::mojom::ScreenOrientation::kUndefined;
 
     // This is the orientation angle of the displayed content in degrees.
     // It is the opposite of the physical rotation.
diff --git a/content/public/test/browser_test_base.cc b/content/public/test/browser_test_base.cc
index ae723a9..79ce28b 100644
--- a/content/public/test/browser_test_base.cc
+++ b/content/public/test/browser_test_base.cc
@@ -56,7 +56,7 @@
 #include "content/public/test/no_renderer_crashes_assertion.h"
 #include "content/public/test/test_launcher.h"
 #include "content/public/test/test_utils.h"
-#include "content/test/content_browser_sequence_checker.h"
+#include "content/test/content_browser_consistency_checker.h"
 #include "gpu/command_buffer/service/gpu_switches.h"
 #include "gpu/config/gpu_switches.h"
 #include "media/base/media_switches.h"
@@ -335,7 +335,7 @@
   if (!allow_network_access_to_host_resolutions_)
     test_host_resolver_ = std::make_unique<TestHostResolver>();
 
-  ContentBrowserSequenceChecker scoped_enable_sequence_checks;
+  ContentBrowserConsistencyChecker scoped_enable_consistency_checks;
 
   SetUpInProcessBrowserTestFixture();
 
diff --git a/content/public/test/fake_service_worker_context.cc b/content/public/test/fake_service_worker_context.cc
index 8b45710..cb412e3 100644
--- a/content/public/test/fake_service_worker_context.cc
+++ b/content/public/test/fake_service_worker_context.cc
@@ -68,7 +68,7 @@
     GetUsageInfoCallback callback) {
   NOTREACHED();
 }
-void FakeServiceWorkerContext::DeleteForOrigin(const GURL& origin,
+void FakeServiceWorkerContext::DeleteForOrigin(const url::Origin& origin,
                                                ResultCallback callback) {
   NOTREACHED();
 }
diff --git a/content/public/test/fake_service_worker_context.h b/content/public/test/fake_service_worker_context.h
index 8c2663e..dae099f89 100644
--- a/content/public/test/fake_service_worker_context.h
+++ b/content/public/test/fake_service_worker_context.h
@@ -53,7 +53,8 @@
       base::Optional<std::string> host_filter,
       GetInstalledRegistrationOriginsCallback callback) override;
   void GetAllOriginsInfo(GetUsageInfoCallback callback) override;
-  void DeleteForOrigin(const GURL& origin, ResultCallback callback) override;
+  void DeleteForOrigin(const url::Origin& origin,
+                       ResultCallback callback) override;
   void PerformStorageCleanup(base::OnceClosure callback) override;
   void CheckHasServiceWorker(const GURL& url,
                              CheckHasServiceWorkerCallback callback) override;
diff --git a/content/public/test/mock_render_process_host.cc b/content/public/test/mock_render_process_host.cc
index 7362c59..2c4a1cb 100644
--- a/content/public/test/mock_render_process_host.cc
+++ b/content/public/test/mock_render_process_host.cc
@@ -441,19 +441,19 @@
   return IsUnused() && listeners_.IsEmpty() && GetKeepAliveRefCount() == 0;
 }
 
-void MockRenderProcessHost::LockToOrigin(
+void MockRenderProcessHost::SetProcessLock(
     const IsolationContext& isolation_context,
-    const GURL& lock_url) {
-  ChildProcessSecurityPolicyImpl::GetInstance()->LockToOrigin(
-      isolation_context, GetID(), lock_url);
-  if (SiteInstanceImpl::IsOriginLockASite(lock_url))
+    const ProcessLock& process_lock) {
+  ChildProcessSecurityPolicyImpl::GetInstance()->LockProcess(
+      isolation_context, GetID(), process_lock);
+  if (process_lock.IsASiteOrOrigin())
     is_renderer_locked_to_site_ = true;
 }
 
-bool MockRenderProcessHost::IsLockedToOriginForTesting() {
-  GURL lock_url =
-      ChildProcessSecurityPolicyImpl::GetInstance()->GetOriginLock(GetID());
-  return !lock_url.is_empty();
+bool MockRenderProcessHost::IsProcessLockedForTesting() {
+  ProcessLock lock =
+      ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock(GetID());
+  return !lock.is_empty();
 }
 
 void MockRenderProcessHost::BindCacheStorage(
diff --git a/content/public/test/mock_render_process_host.h b/content/public/test/mock_render_process_host.h
index df06f3a..12410c2 100644
--- a/content/public/test/mock_render_process_host.h
+++ b/content/public/test/mock_render_process_host.h
@@ -162,9 +162,9 @@
   void SetIsUsed() override;
 
   bool HostHasNotBeenUsed() override;
-  void LockToOrigin(const IsolationContext& isolation_context,
-                    const GURL& lock_url) override;
-  bool IsLockedToOriginForTesting() override;
+  void SetProcessLock(const IsolationContext& isolation_context,
+                      const ProcessLock& process_lock) override;
+  bool IsProcessLockedForTesting() override;
   void BindCacheStorage(
       const network::CrossOriginEmbedderPolicy&,
       mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter>,
diff --git a/content/public/test/navigation_handle_observer.cc b/content/public/test/navigation_handle_observer.cc
index 05215a4..adf4af4 100644
--- a/content/public/test/navigation_handle_observer.cc
+++ b/content/public/test/navigation_handle_observer.cc
@@ -36,6 +36,7 @@
   frame_tree_node_id_ = navigation_handle->GetFrameTreeNodeId();
   navigation_id_ = navigation_handle->GetNavigationId();
   navigation_start_ = navigation_handle->NavigationStart();
+  reload_type_ = navigation_handle->GetReloadType();
 }
 
 void NavigationHandleObserver::DidFinishNavigation(
diff --git a/content/public/test/navigation_handle_observer.h b/content/public/test/navigation_handle_observer.h
index 087dc3b..8c99192b 100644
--- a/content/public/test/navigation_handle_observer.h
+++ b/content/public/test/navigation_handle_observer.h
@@ -7,6 +7,7 @@
 
 #include "base/macros.h"
 #include "content/public/browser/navigation_handle_timing.h"
+#include "content/public/browser/reload_type.h"
 #include "content/public/browser/web_contents_observer.h"
 
 namespace content {
@@ -42,10 +43,10 @@
     return resolve_error_info_;
   }
   base::TimeTicks navigation_start() { return navigation_start_; }
-
   const NavigationHandleTiming& navigation_handle_timing() {
     return navigation_handle_timing_;
   }
+  ReloadType reload_type() { return reload_type_; }
 
  private:
   // A reference to the NavigationHandle so this class will track only
@@ -70,6 +71,7 @@
   net::ResolveErrorInfo resolve_error_info_;
   base::TimeTicks navigation_start_;
   NavigationHandleTiming navigation_handle_timing_;
+  ReloadType reload_type_ = ReloadType::NONE;
 
   DISALLOW_COPY_AND_ASSIGN(NavigationHandleObserver);
 };
diff --git a/content/public/test/network_service_test_helper.cc b/content/public/test/network_service_test_helper.cc
index c2b4481..2a1b9c7 100644
--- a/content/public/test/network_service_test_helper.cc
+++ b/content/public/test/network_service_test_helper.cc
@@ -20,14 +20,18 @@
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/test/test_host_resolver.h"
+#include "crypto/sha2.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
+#include "net/base/hash_value.h"
 #include "net/base/ip_address.h"
+#include "net/cert/ev_root_ca_metadata.h"
 #include "net/cert/mock_cert_verifier.h"
 #include "net/cert/test_root_certs.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/http/transport_security_state.h"
 #include "net/http/transport_security_state_test_util.h"
 #include "net/nqe/network_quality_estimator.h"
+#include "net/test/cert_test_util.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/spawned_test_server/spawned_test_server.h"
 #include "net/test/test_data_directory.h"
@@ -247,6 +251,19 @@
     base::FieldTrialList::FindFullName(field_trial_name);
   }
 
+  void SetEVPolicy(const std::vector<uint8_t>& fingerprint_sha256,
+                   const std::string& policy_oid,
+                   SetEVPolicyCallback callback) override {
+    CHECK_EQ(fingerprint_sha256.size(), crypto::kSHA256Length);
+    net::SHA256HashValue fingerprint_sha256_hash;
+    memcpy(&fingerprint_sha256_hash.data, fingerprint_sha256.data(),
+           crypto::kSHA256Length);
+    ev_test_policy_ = std::make_unique<net::ScopedTestEVPolicy>(
+        net::EVRootCAMetadata::GetInstance(), fingerprint_sha256_hash,
+        policy_oid.data());
+    std::move(callback).Run();
+  }
+
   void BindReceiver(
       mojo::PendingReceiver<network::mojom::NetworkServiceTest> receiver) {
     receivers_.Add(this, std::move(receiver));
@@ -278,6 +295,7 @@
   base::MemoryPressureListener::MemoryPressureLevel
       latest_memory_pressure_level_ =
           base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
+  std::unique_ptr<net::ScopedTestEVPolicy> ev_test_policy_;
 
   DISALLOW_COPY_AND_ASSIGN(NetworkServiceTestImpl);
 };
diff --git a/content/public/test/render_view_test.cc b/content/public/test/render_view_test.cc
index b2a9325..d71372a 100644
--- a/content/public/test/render_view_test.cc
+++ b/content/public/test/render_view_test.cc
@@ -264,7 +264,8 @@
   blink_platform_impl_->Shutdown();
 }
 
-RenderViewTest::RenderViewTest(bool hook_render_frame_creation) {
+RenderViewTest::RenderViewTest(bool hook_render_frame_creation)
+    : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
   // Overrides creation of RenderFrameImpl. Subclasses may wish to do this
   // themselves and it can only be done once.
   if (hook_render_frame_creation)
diff --git a/content/public/test/test_download_http_response.cc b/content/public/test/test_download_http_response.cc
index 805fdcd..8be3b371 100644
--- a/content/public/test/test_download_http_response.cc
+++ b/content/public/test/test_download_http_response.cc
@@ -107,11 +107,13 @@
     int64_t min_offset,
     int64_t max_offset,
     const std::string& response,
-    bool is_transient)
+    bool is_transient,
+    bool delay_response)
     : min_offset(min_offset),
       max_offset(max_offset),
       response(response),
-      is_transient(is_transient) {}
+      is_transient(is_transient),
+      delay_response(delay_response) {}
 
 // static
 TestDownloadHttpResponse::Parameters
@@ -151,9 +153,10 @@
     int64_t min_offset,
     int64_t max_offset,
     const std::string& response,
-    bool is_transient) {
-  range_request_responses.emplace_back(
-      HttpResponseData(min_offset, max_offset, response, is_transient));
+    bool is_transient,
+    bool delay_response) {
+  range_request_responses.emplace_back(HttpResponseData(
+      min_offset, max_offset, response, is_transient, delay_response));
 }
 
 TestDownloadHttpResponse::CompletedRequest::CompletedRequest(
@@ -294,8 +297,15 @@
   // Send static |range_request_responses| in |parameters_| and close
   // connection.
   std::string response;
-  if (GetResponseForRangeRequest(&response)) {
-    bytes_sender_.Run(response, GenerateResultClosure());
+  bool delay_response = false;
+  if (GetResponseForRangeRequest(&response, &delay_response)) {
+    if (delay_response) {
+      delayed_response_callback_ =
+          base::BindOnce(bytes_sender_, response, GenerateResultClosure()),
+      bytes_sender_.Run(GetDefaultResponseHeaders(), base::DoNothing());
+    } else {
+      bytes_sender_.Run(response, GenerateResultClosure());
+    }
     return;
   }
 
@@ -352,7 +362,9 @@
   return headers;
 }
 
-bool TestDownloadHttpResponse::GetResponseForRangeRequest(std::string* output) {
+bool TestDownloadHttpResponse::GetResponseForRangeRequest(
+    std::string* output,
+    bool* delay_response) {
   if (!range_.IsValid())
     return false;
 
@@ -369,6 +381,7 @@
 
     if (it->max_offset == -1 || requset_offset <= it->max_offset) {
       *output = it->response;
+      *delay_response = it->delay_response;
       // Update the global parameter for transient response, so the
       // next response will be different.
       if (it->is_transient) {
@@ -583,6 +596,11 @@
                         base::Unretained(this));
 }
 
+void TestDownloadHttpResponse::SendDelayedResponse() {
+  if (delayed_response_callback_)
+    std::move(delayed_response_callback_).Run();
+}
+
 void TestDownloadHttpResponse::GenerateResult() {
   auto completed_request = std::make_unique<CompletedRequest>(request_);
   // Transferred bytes in [range_.first_byte_position(), response_sent_offset_).
@@ -669,4 +687,14 @@
   run_loop_->Run();
 }
 
+void TestDownloadResponseHandler::DispatchDelayedResponses() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  for (auto& response : responses_) {
+    server_task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(&TestDownloadHttpResponse::SendDelayedResponse,
+                       base::Unretained(response.get())));
+  }
+}
+
 }  // namespace content
diff --git a/content/public/test/test_download_http_response.h b/content/public/test/test_download_http_response.h
index e020c0b..0293355 100644
--- a/content/public/test/test_download_http_response.h
+++ b/content/public/test/test_download_http_response.h
@@ -45,7 +45,8 @@
     HttpResponseData(int64_t min_offset,
                      int64_t max_offset,
                      const std::string& response,
-                     bool is_transient);
+                     bool is_transient,
+                     bool delay_response);
     HttpResponseData(const HttpResponseData& other) = default;
 
     // The range for first byte position in range header to use this response.
@@ -57,6 +58,8 @@
     // Whether the response data is transient and will be invalidated after
     // sending once.
     bool is_transient = false;
+
+    bool delay_response = false;
   };
 
   struct Parameters {
@@ -86,10 +89,13 @@
     // later responses will be calculated from the parameters. If
     // |is_transient| is false, the |response| will be applied to the
     // given range request forever.
+    // if |delay_response| is true, the response body will be sent when
+    // SendDelayedResponse() is called.
     void SetResponseForRangeRequest(int64_t min_offset,
                                     int64_t max_offset,
                                     const std::string& response,
-                                    bool is_transient = false);
+                                    bool is_transient = false,
+                                    bool delay_response = false);
 
     // Contents of the ETag header field of the response.  No Etag header is
     // sent if this field is empty.
@@ -256,6 +262,9 @@
   void SendResponse(const net::test_server::SendBytesCallback& send,
                     net::test_server::SendCompleteCallback done);
 
+  // Runs |delayed_response_callback_| to send the delayed response.
+  void SendDelayedResponse();
+
  private:
   // Parses the request headers.
   void ParseRequestHeader();
@@ -275,9 +284,9 @@
   std::string GetDefaultResponseHeaders();
 
   // Gets response header and body for range request starts from
-  // |first_byte_position|.
+  // |first_byte_position|, and whether to delay sending the response.
   // Returns false if no specific responses are found.
-  bool GetResponseForRangeRequest(std::string* output);
+  bool GetResponseForRangeRequest(std::string* output, bool* delay_response);
 
   // Gets response body chunk based on random seed in |parameters_|.
   std::string GetResponseChunk(const net::HttpByteRange& buffer_range);
@@ -334,6 +343,11 @@
   // Callback to run when the response is sent.
   OnResponseSentCallback on_response_sent_callback_;
 
+  // If |delay_response| is true in SetResponseForRangeRequest(), this
+  // callback will be used to send the delayed response when
+  // SendDelayedResponse() is called.
+  base::OnceClosure delayed_response_callback_;
+
   base::WeakPtrFactory<TestDownloadHttpResponse> weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(TestDownloadHttpResponse);
@@ -370,6 +384,9 @@
   // Wait for a certain number of requests to complete.
   void WaitUntilCompletion(size_t request_count);
 
+  // Called to dispatch all delayed responses if there are any.
+  void DispatchDelayedResponses();
+
   using CompletedRequests =
       std::vector<std::unique_ptr<TestDownloadHttpResponse::CompletedRequest>>;
   CompletedRequests const& completed_requests() { return completed_requests_; }
diff --git a/content/public/test/test_file_error_injector.cc b/content/public/test/test_file_error_injector.cc
index 5394ad7..7b1ed01 100644
--- a/content/public/test/test_file_error_injector.cc
+++ b/content/public/test/test_file_error_injector.cc
@@ -182,10 +182,15 @@
                                                   const char* data,
                                                   size_t bytes_to_validate,
                                                   size_t bytes_to_write) {
-  return ShouldReturnError(
-      TestFileErrorInjector::FILE_OPERATION_WRITE,
+  download::DownloadInterruptReason origin_error =
       download::DownloadFileImpl::ValidateAndWriteDataToFile(
-          offset, data, bytes_to_validate, bytes_to_write));
+          offset, data, bytes_to_validate, bytes_to_write);
+  if (error_info_.data_write_offset == -1 ||
+      offset == error_info_.data_write_offset) {
+    return ShouldReturnError(TestFileErrorInjector::FILE_OPERATION_WRITE,
+                             origin_error);
+  }
+  return origin_error;
 }
 
 download::DownloadInterruptReason
diff --git a/content/public/test/test_file_error_injector.h b/content/public/test/test_file_error_injector.h
index f810cee..d3a8b3f 100644
--- a/content/public/test/test_file_error_injector.h
+++ b/content/public/test/test_file_error_injector.h
@@ -67,6 +67,9 @@
     download::DownloadInterruptReason error;  // Error to inject.
     int64_t stream_offset = -1;     // Offset of the error stream.
     int64_t stream_bytes_written = -1;  // Bytes written to the error stream.
+    // If > 0, only write calls to this offset will generate errors.
+    // Otherwise, all file writes will generate errors.
+    int64_t data_write_offset = -1;
   };
 
   // Creates an instance.  May only be called once.
diff --git a/content/public/test/test_renderer_host.cc b/content/public/test/test_renderer_host.cc
index ebc67bb..8ddf6d8 100644
--- a/content/public/test/test_renderer_host.cc
+++ b/content/public/test/test_renderer_host.cc
@@ -27,7 +27,7 @@
 #include "content/public/test/mock_render_process_host.h"
 #include "content/public/test/navigation_simulator.h"
 #include "content/public/test/test_browser_context.h"
-#include "content/test/content_browser_sequence_checker.h"
+#include "content/test/content_browser_consistency_checker.h"
 #include "content/test/test_navigation_url_loader_factory.h"
 #include "content/test/test_render_frame_host.h"
 #include "content/test/test_render_frame_host_factory.h"
@@ -244,7 +244,7 @@
   aura_test_helper_->SetUp();
 #endif
 
-  sequence_checker_ = std::make_unique<ContentBrowserSequenceChecker>();
+  consistency_checker_ = std::make_unique<ContentBrowserConsistencyChecker>();
 
 #if !defined(OS_ANDROID)
   network_change_notifier_ = net::test::MockNetworkChangeNotifier::Create();
diff --git a/content/public/test/test_renderer_host.h b/content/public/test/test_renderer_host.h
index 0ed1154..66fc9fa 100644
--- a/content/public/test/test_renderer_host.h
+++ b/content/public/test/test_renderer_host.h
@@ -49,7 +49,7 @@
 namespace content {
 
 class BrowserContext;
-class ContentBrowserSequenceChecker;
+class ContentBrowserConsistencyChecker;
 class MockRenderProcessHost;
 class MockRenderProcessHostFactory;
 class NavigationController;
@@ -279,7 +279,7 @@
 
   std::unique_ptr<BrowserTaskEnvironment> task_environment_;
 
-  std::unique_ptr<ContentBrowserSequenceChecker> sequence_checker_;
+  std::unique_ptr<ContentBrowserConsistencyChecker> consistency_checker_;
 
   // TODO(crbug.com/1011275): This is a temporary work around to fix flakiness
   // on tests. The default behavior of the network stack is to allocate a
diff --git a/content/public/test/test_utils.h b/content/public/test/test_utils.h
index 6224158..f92c22d 100644
--- a/content/public/test/test_utils.h
+++ b/content/public/test/test_utils.h
@@ -101,13 +101,13 @@
 void IsolateAllSitesForTesting(base::CommandLine* command_line);
 
 // Whether same-site navigations might result in a change of RenderFrameHosts -
-// this will happen when ProactivelySwapBrowsingInstance or RenderDocument
-// is enabled on same-site main frame navigations.
+// this will happen when ProactivelySwapBrowsingInstance, RenderDocument or
+// back-forward cache is enabled on same-site main frame navigations.
 bool CanSameSiteMainFrameNavigationsChangeRenderFrameHosts();
 
 // Whether same-site navigations might result in a change of SiteInstances -
-// this will happen when ProactivelySwapBrowsingInstance is enabled on
-// same-site main frame navigations.
+// this will happen when ProactivelySwapBrowsingInstance or back-forward cache
+// is enabled on same-site main frame navigations.
 // Note that unlike CanSameSiteMainFrameNavigationsChangeRenderFrameHosts()
 // above, this will not be true when RenderDocument for main-frame is enabled.
 bool CanSameSiteMainFrameNavigationsChangeSiteInstances();
diff --git a/content/public/test/test_web_ui.cc b/content/public/test/test_web_ui.cc
index 70ea8e6..f8de9ec1 100644
--- a/content/public/test/test_web_ui.cc
+++ b/content/public/test/test_web_ui.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "base/callback.h"
 #include "base/memory/ptr_util.h"
 #include "base/notreached.h"
 #include "base/strings/string_piece.h"
@@ -95,12 +96,14 @@
 
 void TestWebUI::CallJavascriptFunctionUnsafe(const std::string& function_name) {
   call_data_.push_back(base::WrapUnique(new CallData(function_name)));
+  OnJavascriptCall(*call_data_.back());
 }
 
 void TestWebUI::CallJavascriptFunctionUnsafe(const std::string& function_name,
                                              const base::Value& arg1) {
   call_data_.push_back(base::WrapUnique(new CallData(function_name)));
   call_data_.back()->TakeAsArg1(arg1.CreateDeepCopy());
+  OnJavascriptCall(*call_data_.back());
 }
 
 void TestWebUI::CallJavascriptFunctionUnsafe(const std::string& function_name,
@@ -109,6 +112,7 @@
   call_data_.push_back(base::WrapUnique(new CallData(function_name)));
   call_data_.back()->TakeAsArg1(arg1.CreateDeepCopy());
   call_data_.back()->TakeAsArg2(arg2.CreateDeepCopy());
+  OnJavascriptCall(*call_data_.back());
 }
 
 void TestWebUI::CallJavascriptFunctionUnsafe(const std::string& function_name,
@@ -119,6 +123,7 @@
   call_data_.back()->TakeAsArg1(arg1.CreateDeepCopy());
   call_data_.back()->TakeAsArg2(arg2.CreateDeepCopy());
   call_data_.back()->TakeAsArg3(arg3.CreateDeepCopy());
+  OnJavascriptCall(*call_data_.back());
 }
 
 void TestWebUI::CallJavascriptFunctionUnsafe(const std::string& function_name,
@@ -131,6 +136,7 @@
   call_data_.back()->TakeAsArg2(arg2.CreateDeepCopy());
   call_data_.back()->TakeAsArg3(arg3.CreateDeepCopy());
   call_data_.back()->TakeAsArg4(arg4.CreateDeepCopy());
+  OnJavascriptCall(*call_data_.back());
 }
 
 void TestWebUI::CallJavascriptFunctionUnsafe(
@@ -139,6 +145,11 @@
   NOTREACHED();
 }
 
+void TestWebUI::OnJavascriptCall(const CallData& call_data) {
+  for (JavascriptCallObserver& observer : javascript_call_observers_)
+    observer.OnJavascriptFunctionCalled(call_data);
+}
+
 std::vector<std::unique_ptr<WebUIMessageHandler>>*
 TestWebUI::GetHandlersForTesting() {
   return &handlers_;
diff --git a/content/public/test/test_web_ui.h b/content/public/test/test_web_ui.h
index e8dc237..6808185 100644
--- a/content/public/test/test_web_ui.h
+++ b/content/public/test/test_web_ui.h
@@ -9,6 +9,8 @@
 #include <vector>
 
 #include "base/containers/flat_map.h"
+#include "base/observer_list.h"
+#include "base/observer_list_types.h"
 #include "base/values.h"
 #include "content/public/browser/web_ui.h"
 
@@ -95,7 +97,23 @@
     return call_data_;
   }
 
+  // An observer that will be notified of javascript calls.
+  class JavascriptCallObserver : public base::CheckedObserver {
+   public:
+    virtual void OnJavascriptFunctionCalled(const CallData& call_data) = 0;
+  };
+
+  void AddJavascriptCallObserver(JavascriptCallObserver* obs) {
+    javascript_call_observers_.AddObserver(obs);
+  }
+
+  void RemoveJavascriptCallObserver(JavascriptCallObserver* obs) {
+    javascript_call_observers_.RemoveObserver(obs);
+  }
+
  private:
+  void OnJavascriptCall(const CallData& call_data);
+
   base::flat_map<std::string, std::vector<MessageCallback>> message_callbacks_;
   std::vector<std::unique_ptr<CallData>> call_data_;
   std::vector<std::unique_ptr<WebUIMessageHandler>> handlers_;
@@ -104,6 +122,9 @@
   WebContents* web_contents_;
   std::unique_ptr<WebUIController> controller_;
 
+  // Observers to be notified on all javascript calls.
+  base::ObserverList<JavascriptCallObserver> javascript_call_observers_;
+
   DISALLOW_COPY_AND_ASSIGN(TestWebUI);
 };
 
diff --git a/content/public/test/test_web_ui_listener_observer.cc b/content/public/test/test_web_ui_listener_observer.cc
new file mode 100644
index 0000000..962ce20
--- /dev/null
+++ b/content/public/test/test_web_ui_listener_observer.cc
@@ -0,0 +1,59 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/public/test/test_web_ui_listener_observer.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "content/public/test/test_web_ui.h"
+
+namespace content {
+
+TestWebUIListenerObserver::TestWebUIListenerObserver(
+    content::TestWebUI* web_ui,
+    const std::string& listener_name)
+    : web_ui_(web_ui), listener_name_(listener_name) {
+  web_ui_->AddJavascriptCallObserver(this);
+}
+
+TestWebUIListenerObserver::~TestWebUIListenerObserver() {
+  web_ui_->RemoveJavascriptCallObserver(this);
+}
+
+void TestWebUIListenerObserver::Wait() {
+  run_loop_.Run();
+}
+
+void TestWebUIListenerObserver::OnJavascriptFunctionCalled(
+    const TestWebUI::CallData& call_data) {
+  // Ignore subsequent calls after a call matched the listener that this
+  // observer is waiting for.
+  if (call_args_.has_value())
+    return;
+
+  // See WebUIMessageHandler::FireWebUIListener.
+  if (call_data.function_name() != "cr.webUIListenerCallback")
+    return;
+  if (!call_data.arg1() || !call_data.arg1()->is_string() ||
+      call_data.arg1()->GetString() != listener_name_) {
+    return;
+  }
+
+  call_args_.emplace();
+  // TestWebUI::CallData supports up to 4 arguments, each of them has a
+  // different accessor. arg1() is the listener name for WebUI listeners (see
+  // above).
+  if (call_data.arg2()) {
+    call_args_.value().push_back(call_data.arg2()->Clone());
+  }
+  if (call_data.arg3()) {
+    call_args_.value().push_back(call_data.arg3()->Clone());
+  }
+  if (call_data.arg4()) {
+    call_args_.value().push_back(call_data.arg4()->Clone());
+  }
+  run_loop_.Quit();
+}
+
+}  // namespace content
diff --git a/content/public/test/test_web_ui_listener_observer.h b/content/public/test/test_web_ui_listener_observer.h
new file mode 100644
index 0000000..fab32a9
--- /dev/null
+++ b/content/public/test/test_web_ui_listener_observer.h
@@ -0,0 +1,52 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_PUBLIC_TEST_TEST_WEB_UI_LISTENER_OBSERVER_H_
+#define CONTENT_PUBLIC_TEST_TEST_WEB_UI_LISTENER_OBSERVER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/run_loop.h"
+#include "base/values.h"
+#include "content/public/test/test_web_ui.h"
+
+namespace content {
+
+// A test observer that allows blocking waits on WebUI calls back to javascript
+// listeners using cr.webUIListenerCallback.
+class TestWebUIListenerObserver : public TestWebUI::JavascriptCallObserver {
+ public:
+  // Constructs a TestWebUIListenerObserver which will observer |web_ui| for
+  // cr.webUIListenerCallback calls with the first argument |listener_name|.
+  TestWebUIListenerObserver(content::TestWebUI* web_ui,
+                            const std::string& listener_name);
+  ~TestWebUIListenerObserver() override;
+
+  TestWebUIListenerObserver(const TestWebUIListenerObserver& other) = delete;
+  TestWebUIListenerObserver& operator=(const TestWebUIListenerObserver& other) =
+      delete;
+
+  // Waits for the listener call to happen.
+  void Wait();
+
+  // Only callable after Wait() has returned. Contains the arguments passed to
+  // the listener.
+  std::vector<base::Value>& args() { return call_args_.value(); }
+
+ private:
+  void OnJavascriptFunctionCalled(
+      const TestWebUI::CallData& call_data) override;
+
+  content::TestWebUI* web_ui_;
+  std::string listener_name_;
+  base::RunLoop run_loop_;
+
+  // Only filled when a matching listener call has been observed.
+  base::Optional<std::vector<base::Value>> call_args_;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_PUBLIC_TEST_TEST_WEB_UI_LISTENER_OBSERVER_H_
diff --git a/content/renderer/BUILD.gn b/content/renderer/BUILD.gn
index 4a11f44..5f5d5a4 100644
--- a/content/renderer/BUILD.gn
+++ b/content/renderer/BUILD.gn
@@ -9,7 +9,6 @@
 import("//content/common/features.gni")
 import("//media/media_options.gni")
 import("//ppapi/buildflags/buildflags.gni")
-import("//printing/buildflags/buildflags.gni")
 import("//third_party/webrtc/webrtc.gni")
 import("//tools/ipc_fuzzer/ipc_fuzzer.gni")
 
@@ -628,10 +627,6 @@
     ]
   }
 
-  if (enable_basic_printing) {
-    deps += [ "//printing" ]
-  }
-
   if (is_linux || is_android) {
     deps += [ "//sandbox:sandbox_buildflags" ]
   }
diff --git a/content/renderer/accessibility/blink_ax_tree_source.h b/content/renderer/accessibility/blink_ax_tree_source.h
index fc75af5a..fb7ec524 100644
--- a/content/renderer/accessibility/blink_ax_tree_source.h
+++ b/content/renderer/accessibility/blink_ax_tree_source.h
@@ -38,9 +38,9 @@
   DISALLOW_COPY_AND_ASSIGN(ScopedFreezeBlinkAXTreeSource);
 };
 
-class BlinkAXTreeSource : public ui::AXTreeSource<blink::WebAXObject,
-                                                  AXContentNodeData,
-                                                  ui::AXTreeData> {
+class CONTENT_EXPORT BlinkAXTreeSource
+    : public ui::
+          AXTreeSource<blink::WebAXObject, AXContentNodeData, ui::AXTreeData> {
  public:
   BlinkAXTreeSource(RenderFrameImpl* render_frame, ui::AXMode mode);
   ~BlinkAXTreeSource() override;
diff --git a/content/renderer/accessibility/render_accessibility_impl.cc b/content/renderer/accessibility/render_accessibility_impl.cc
index ce7e18c..fbf2565 100644
--- a/content/renderer/accessibility/render_accessibility_impl.cc
+++ b/content/renderer/accessibility/render_accessibility_impl.cc
@@ -60,8 +60,21 @@
 
 namespace {
 
+// The amount of time, in milliseconds, to wait before sending accessibility
+// events that are deferred rather than being sent right away. As one
+// example this is used during initial page load.
 constexpr int kDelayForDeferredEvents = 350;
 
+// The minimum amount of time in milliseconds that should be spent
+// in serializing code in order to report the elapsed time as a URL-keyed
+// metric.
+constexpr int kMinSerializationTimeToSendInMS = 100;
+
+// When URL-keyed metrics for the amount of time spent in serializing code
+// are sent, the minimum amount of time to wait, in seconds, before
+// sending metrics. Metrics may also be sent once per page transition.
+constexpr int kMinUKMDelayInSeconds = 300;
+
 void SetAccessibilityCrashKey(ui::AXMode mode) {
   // Add a crash key with the ax_mode, to enable searching for top crashes that
   // occur when accessibility is turned on. This adds it for each renderer,
@@ -177,12 +190,14 @@
     : RenderFrameObserver(render_frame),
       render_accessibility_manager_(render_accessibility_manager),
       render_frame_(render_frame),
-      tree_source_(render_frame, mode),
-      serializer_(&tree_source_),
+      tree_source_(std::make_unique<BlinkAXTreeSource>(render_frame, mode)),
+      serializer_(std::make_unique<BlinkAXTreeSerializer>(tree_source_.get())),
       plugin_tree_source_(nullptr),
       last_scroll_offset_(gfx::Size()),
       event_schedule_status_(EventScheduleStatus::kNotWaiting),
-      reset_token_(0) {
+      reset_token_(0),
+      ukm_timer_(std::make_unique<base::ElapsedTimer>()),
+      last_ukm_source_id_(ukm::kInvalidSourceId) {
   mojo::PendingRemote<ukm::mojom::UkmRecorderInterface> recorder;
   content::RenderThread::Get()->BindHostReceiver(
       recorder.InitWithNewPipeAndPassReceiver());
@@ -243,6 +258,10 @@
   // Defer events during initial page load.
   event_schedule_mode_ = EventScheduleMode::kDeferEvents;
 
+  MaybeSendUKM();
+  slowest_serialization_ms_ = 0;
+  ukm_timer_ = std::make_unique<base::ElapsedTimer>();
+
   // Remove the image annotator if the page is loading and it was added for
   // the one-shot image annotation (i.e. AXMode for image annotation is not
   // set).
@@ -250,7 +269,7 @@
       GetAccessibilityMode().has_mode(ui::AXMode::kLabelImages)) {
     return;
   }
-  tree_source_.RemoveImageAnnotator();
+  tree_source_->RemoveImageAnnotator();
   ax_image_annotator_->Destroy();
   ax_image_annotator_.release();
   page_language_.clear();
@@ -260,7 +279,7 @@
   ui::AXMode old_mode = GetAccessibilityMode();
   if (old_mode == mode)
     return;
-  tree_source_.SetAccessibilityMode(mode);
+  tree_source_->SetAccessibilityMode(mode);
 
   SetAccessibilityCrashKey(mode);
 
@@ -275,8 +294,8 @@
       if (settings) {
         if (mode.has_mode(ui::AXMode::kInlineTextBoxes)) {
           settings->SetInlineTextBoxAccessibilityEnabled(true);
-          tree_source_.GetRoot().UpdateLayoutAndCheckValidity();
-          tree_source_.GetRoot().LoadInlineTextBoxes();
+          tree_source_->GetRoot().UpdateLayoutAndCheckValidity();
+          tree_source_->GetRoot().LoadInlineTextBoxes();
         } else {
           settings->SetInlineTextBoxAccessibilityEnabled(false);
         }
@@ -285,7 +304,7 @@
   }
 #endif  // !defined(OS_ANDROID)
 
-  serializer_.Reset();
+  serializer_->Reset();
   const WebDocument& document = GetMainDocument();
   if (!document.IsNull()) {
     StartOrStopLabelingImages(old_mode, mode);
@@ -322,8 +341,8 @@
 
   // If the result was in the same frame, return the result.
   AXContentNodeData data;
-  ScopedFreezeBlinkAXTreeSource freeze(&tree_source_);
-  tree_source_.SerializeNode(ax_object, &data);
+  ScopedFreezeBlinkAXTreeSource freeze(tree_source_.get());
+  tree_source_->SerializeNode(ax_object, &data);
   if (data.child_routing_id == MSG_ROUTING_NONE) {
     // Optionally fire an event, if requested to. This is a good fit for
     // features like touch exploration on Android, Chrome OS, and
@@ -494,7 +513,7 @@
 
 void RenderAccessibilityImpl::Reset(int32_t reset_token) {
   reset_token_ = reset_token;
-  serializer_.Reset();
+  serializer_->Reset();
   pending_events_.clear();
 
   const WebDocument& document = GetMainDocument();
@@ -522,7 +541,7 @@
   dirty_objects_.push_back(dirty_object);
 
   if (subtree)
-    serializer_.InvalidateSubtree(obj);
+    serializer_->InvalidateSubtree(obj);
 
   ScheduleSendPendingAccessibilityEvents();
 }
@@ -558,7 +577,7 @@
   // Force the newly focused node to be re-serialized so we include its
   // inline text boxes.
   if (event.event_type == ax::mojom::Event::kFocus)
-    serializer_.InvalidateSubtree(obj);
+    serializer_->InvalidateSubtree(obj);
 #endif
 
   // If a select tag is opened or closed, all the children must be updated
@@ -567,7 +586,7 @@
       event.event_type == ax::mojom::Event::kChildrenChanged) {
     WebAXObject popup_like_object = obj.ParentObject();
     if (!popup_like_object.IsDetached()) {
-      serializer_.InvalidateSubtree(popup_like_object);
+      serializer_->InvalidateSubtree(popup_like_object);
       HandleAXEvent(ui::AXEvent(popup_like_object.AxID(),
                                 ax::mojom::Event::kChildrenChanged));
     }
@@ -674,7 +693,7 @@
 }
 
 int RenderAccessibilityImpl::GenerateAXID() {
-  WebAXObject root = tree_source_.GetRoot();
+  WebAXObject root = tree_source_->GetRoot();
   return root.GenerateAXID();
 }
 
@@ -727,7 +746,7 @@
 void RenderAccessibilityImpl::SendPendingAccessibilityEvents() {
   TRACE_EVENT0("accessibility",
                "RenderAccessibilityImpl::SendPendingAccessibilityEvents");
-  base::ElapsedTimer timer_;
+  base::ElapsedTimer timer;
 
   // Clear status here in case we return early.
   event_schedule_status_ = EventScheduleStatus::kNotWaiting;
@@ -759,10 +778,10 @@
   // mode.
   bool had_load_complete_messages = false;
 
-  ScopedFreezeBlinkAXTreeSource freeze(&tree_source_);
+  ScopedFreezeBlinkAXTreeSource freeze(tree_source_.get());
 
   // Save the page language.
-  WebAXObject root = tree_source_.GetRoot();
+  WebAXObject root = tree_source_->GetRoot();
   page_language_ = root.Language().Utf8();
 
   // Loop over each event and generate an updated event message.
@@ -782,7 +801,7 @@
     // Make sure it's a descendant of our root node - exceptions include the
     // scroll area that's the parent of the main document (we ignore it), and
     // possibly nodes attached to a different document.
-    if (!tree_source_.IsInTree(obj))
+    if (!tree_source_->IsInTree(obj))
       continue;
 
     // If it's ignored, find the first ancestor that's not ignored.
@@ -863,7 +882,7 @@
   // because if so, the plugin subtree will need to be re-serialized.
   bool invalidate_plugin_subtree = false;
   if (plugin_tree_source_ && !plugin_host_node_.IsDetached()) {
-    invalidate_plugin_subtree = !serializer_.IsInClientTree(plugin_host_node_);
+    invalidate_plugin_subtree = !serializer_->IsInClientTree(plugin_host_node_);
   }
 
   // Now serialize all dirty objects. Keep track of IDs serialized
@@ -897,7 +916,7 @@
     if (plugin_tree_source_)
       update.has_tree_data = true;
 
-    if (!serializer_.SerializeChanges(obj, &update)) {
+    if (!serializer_->SerializeChanges(obj, &update)) {
       VLOG(1) << "Failed to serialize one accessibility event.";
       continue;
     }
@@ -939,10 +958,17 @@
   if (image_annotation_debugging_)
     AddImageAnnotationDebuggingAttributes(updates);
 
-  ukm::builders::Accessibility_Renderer(document.GetUkmSourceId())
-      .SetCpuTime_SendPendingAccessibilityEvents(
-          timer_.Elapsed().InMilliseconds())
-      .Record(ukm_recorder_.get());
+  // Measure the amount of time spent in this function. Keep track of the
+  // maximum within a time interval so we can upload UKM.
+  int elapsed_time_ms = timer.Elapsed().InMilliseconds();
+  if (elapsed_time_ms > slowest_serialization_ms_) {
+    last_ukm_source_id_ = document.GetUkmSourceId();
+    last_ukm_url_ = document.CanonicalUrlForSharing().GetString().Utf8();
+    slowest_serialization_ms_ = elapsed_time_ms;
+  }
+
+  if (ukm_timer_->Elapsed().InSeconds() >= kMinUKMDelayInSeconds)
+    MaybeSendUKM();
 }
 
 void RenderAccessibilityImpl::SendLocationChanges() {
@@ -951,7 +977,7 @@
   std::vector<mojom::LocationChangesPtr> changes;
 
   // Update layout on the root of the tree.
-  WebAXObject root = tree_source_.GetRoot();
+  WebAXObject root = tree_source_->GetRoot();
   if (!root.UpdateLayoutAndCheckValidity())
     return;
 
@@ -990,9 +1016,9 @@
 
     // Explore children of this object.
     std::vector<WebAXObject> children;
-    tree_source_.GetChildren(obj, &children);
-    for (size_t i = 0; i < children.size(); ++i)
-      objs_to_explore.push(children[i]);
+    tree_source_->GetChildren(obj, &children);
+    for (WebAXObject& child : children)
+      objs_to_explore.push(child);
   }
   locations_.swap(new_locations);
 
@@ -1020,11 +1046,11 @@
     return;
   const WebAXObject& obj = blink_target->WebAXObject();
 
-  ScopedFreezeBlinkAXTreeSource freeze(&tree_source_);
-  if (tree_source_.ShouldLoadInlineTextBoxes(obj))
+  ScopedFreezeBlinkAXTreeSource freeze(tree_source_.get());
+  if (tree_source_->ShouldLoadInlineTextBoxes(obj))
     return;
 
-  tree_source_.SetLoadInlineTextBoxesForId(obj.AxID());
+  tree_source_->SetLoadInlineTextBoxesForId(obj.AxID());
 
   const WebDocument& document = GetMainDocument();
   if (document.IsNull())
@@ -1032,7 +1058,7 @@
 
   // This object may not be a leaf node. Force the whole subtree to be
   // re-serialized.
-  serializer_.InvalidateSubtree(obj);
+  serializer_->InvalidateSubtree(obj);
 
   // Explicitly send a tree change update event now.
   HandleAXEvent(ui::AXEvent(obj.AxID(), ax::mojom::Event::kTreeChanged));
@@ -1046,18 +1072,18 @@
     return;
   const WebAXObject& obj = blink_target->WebAXObject();
 
-  ScopedFreezeBlinkAXTreeSource freeze(&tree_source_);
-  if (tree_source_.image_data_node_id() == obj.AxID())
+  ScopedFreezeBlinkAXTreeSource freeze(tree_source_.get());
+  if (tree_source_->image_data_node_id() == obj.AxID())
     return;
 
-  tree_source_.set_image_data_node_id(obj.AxID());
-  tree_source_.set_max_image_data_size(max_size);
+  tree_source_->set_image_data_node_id(obj.AxID());
+  tree_source_->set_max_image_data_size(max_size);
 
   const WebDocument& document = GetMainDocument();
   if (document.IsNull())
     return;
 
-  serializer_.InvalidateSubtree(obj);
+  serializer_->InvalidateSubtree(obj);
   HandleAXEvent(ui::AXEvent(obj.AxID(), ax::mojom::Event::kImageFrameUpdated));
 }
 
@@ -1109,7 +1135,7 @@
 
   ax_image_annotator_ =
       std::make_unique<AXImageAnnotator>(this, std::move(annotator));
-  tree_source_.AddImageAnnotator(ax_image_annotator_.get());
+  tree_source_->AddImageAnnotator(ax_image_annotator_.get());
 }
 
 void RenderAccessibilityImpl::StartOrStopLabelingImages(ui::AXMode old_mode,
@@ -1122,16 +1148,16 @@
     CreateAXImageAnnotator();
   } else if (old_mode.has_mode(ui::AXMode::kLabelImages) &&
              !new_mode.has_mode(ui::AXMode::kLabelImages)) {
-    tree_source_.RemoveImageAnnotator();
+    tree_source_->RemoveImageAnnotator();
     ax_image_annotator_->Destroy();
     ax_image_annotator_.release();
   }
 }
 
 void RenderAccessibilityImpl::MarkAllAXObjectsDirty(ax::mojom::Role role) {
-  ScopedFreezeBlinkAXTreeSource freeze(&tree_source_);
+  ScopedFreezeBlinkAXTreeSource freeze(tree_source_.get());
   base::queue<WebAXObject> objs_to_explore;
-  objs_to_explore.push(tree_source_.GetRoot());
+  objs_to_explore.push(tree_source_->GetRoot());
   while (objs_to_explore.size()) {
     WebAXObject obj = objs_to_explore.front();
     objs_to_explore.pop();
@@ -1140,9 +1166,9 @@
       MarkWebAXObjectDirty(obj, /* subtree */ false);
 
     std::vector<blink::WebAXObject> children;
-    tree_source_.GetChildren(obj, &children);
-    for (size_t i = 0; i < children.size(); ++i)
-      objs_to_explore.push(children[i]);
+    tree_source_->GetChildren(obj, &children);
+    for (WebAXObject& child : children)
+      objs_to_explore.push(child);
   }
 }
 
@@ -1282,8 +1308,8 @@
 }
 
 WebAXObject RenderAccessibilityImpl::GetPluginRoot() {
-  ScopedFreezeBlinkAXTreeSource freeze(&tree_source_);
-  WebAXObject root = tree_source_.GetRoot();
+  ScopedFreezeBlinkAXTreeSource freeze(tree_source_.get());
+  WebAXObject root = tree_source_->GetRoot();
   if (!root.UpdateLayoutAndCheckValidity())
     return WebAXObject();
 
@@ -1303,7 +1329,7 @@
 
     // Explore children of this object.
     std::vector<WebAXObject> children;
-    tree_source_.GetChildren(obj, &children);
+    tree_source_->GetChildren(obj, &children);
     for (const auto& child : children)
       objs_to_explore.push(child);
   }
@@ -1324,6 +1350,23 @@
   }
 }
 
+void RenderAccessibilityImpl::MaybeSendUKM() {
+  if (slowest_serialization_ms_ < kMinSerializationTimeToSendInMS)
+    return;
+
+  ukm::builders::Accessibility_Renderer(last_ukm_source_id_)
+      .SetCpuTime_SendPendingAccessibilityEvents(slowest_serialization_ms_)
+      .Record(ukm_recorder_.get());
+  ResetUKMData();
+}
+
+void RenderAccessibilityImpl::ResetUKMData() {
+  slowest_serialization_ms_ = 0;
+  ukm_timer_ = std::make_unique<base::ElapsedTimer>();
+  last_ukm_source_id_ = ukm::kInvalidSourceId;
+  last_ukm_url_ = "";
+}
+
 RenderAccessibilityImpl::DirtyObject::DirtyObject() = default;
 RenderAccessibilityImpl::DirtyObject::DirtyObject(const DirtyObject& other) =
     default;
diff --git a/content/renderer/accessibility/render_accessibility_impl.h b/content/renderer/accessibility/render_accessibility_impl.h
index 05b1f05..95d5bd1 100644
--- a/content/renderer/accessibility/render_accessibility_impl.h
+++ b/content/renderer/accessibility/render_accessibility_impl.h
@@ -29,6 +29,10 @@
 #include "ui/accessibility/ax_tree_serializer.h"
 #include "ui/gfx/geometry/rect_f.h"
 
+namespace base {
+class ElapsedTimer;
+}  // namespace base
+
 namespace blink {
 class WebDocument;
 }  // namespace blink
@@ -114,7 +118,7 @@
   ~RenderAccessibilityImpl() override;
 
   ui::AXMode GetAccessibilityMode() {
-    return tree_source_.accessibility_mode();
+    return tree_source_->accessibility_mode();
   }
 
   // RenderAccessibility implementation.
@@ -233,6 +237,16 @@
   // Cancels scheduled events that are not yet in flight
   void CancelScheduledEvents();
 
+  // Sends the URL-keyed metrics for the maximum amount of time spent in
+  // SendPendingAccessibilityEvents if they meet the minimum criteria for
+  // sending.
+  void MaybeSendUKM();
+
+  // Reset all of the UKM data. This can be called after sending UKM data,
+  // or after navigating to a new page when any previous data will no
+  // longer be valid.
+  void ResetUKMData();
+
   // The RenderAccessibilityManager that owns us.
   RenderAccessibilityManager* render_accessibility_manager_;
 
@@ -255,10 +269,10 @@
   std::vector<DirtyObject> dirty_objects_;
 
   // The adapter that exposes Blink's accessibility tree to AXTreeSerializer.
-  BlinkAXTreeSource tree_source_;
+  std::unique_ptr<BlinkAXTreeSource> tree_source_;
 
   // The serializer that sends accessibility messages to the browser process.
-  BlinkAXTreeSerializer serializer_;
+  std::unique_ptr<BlinkAXTreeSerializer> serializer_;
 
   using PluginAXTreeSerializer = ui::AXTreeSerializer<const ui::AXNode*,
                                                       ui::AXNodeData,
@@ -296,8 +310,23 @@
   // The specified page language, or empty if unknown.
   std::string page_language_;
 
+  // The URL-keyed metrics recorder interface.
   std::unique_ptr<ukm::MojoUkmRecorder> ukm_recorder_;
 
+  // The longest amount of time spent serializing the accessibility tree
+  // in SendPendingAccessibilityEvents. This is periodically uploaded as
+  // a UKM and then reset.
+  int slowest_serialization_ms_ = 0;
+
+  // The amount of time since the last UKM upload.
+  std::unique_ptr<base::ElapsedTimer> ukm_timer_;
+
+  // The UKM Source ID that corresponds to the web page represented by
+  // slowest_serialization_ms_. We report UKM before the user navigates
+  // away, or every few minutes.
+  ukm::SourceId last_ukm_source_id_;
+  std::string last_ukm_url_;
+
   // So we can queue up tasks to be executed later.
   base::WeakPtrFactory<RenderAccessibilityImpl>
       weak_factory_for_pending_events_{this};
@@ -305,6 +334,7 @@
   friend class AXImageAnnotatorTest;
   friend class PluginActionHandlingTest;
   friend class RenderAccessibilityImplTest;
+  friend class RenderAccessibilityImplUKMTest;
 
   DISALLOW_COPY_AND_ASSIGN(RenderAccessibilityImpl);
 };
diff --git a/content/renderer/accessibility/render_accessibility_impl_browsertest.cc b/content/renderer/accessibility/render_accessibility_impl_browsertest.cc
index 1c3dec2..bc4cb23 100644
--- a/content/renderer/accessibility/render_accessibility_impl_browsertest.cc
+++ b/content/renderer/accessibility/render_accessibility_impl_browsertest.cc
@@ -38,6 +38,7 @@
 #include "ppapi/c/private/ppp_pdf.h"
 #include "services/image_annotation/public/cpp/image_processor.h"
 #include "services/image_annotation/public/mojom/image_annotation.mojom.h"
+#include "services/metrics/public/cpp/mojo_ukm_recorder.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
@@ -885,8 +886,8 @@
     GetRenderAccessibilityImpl()->ax_image_annotator_ =
         std::make_unique<TestAXImageAnnotator>(GetRenderAccessibilityImpl(),
                                                mock_annotator().GetRemote());
-    GetRenderAccessibilityImpl()->tree_source_.RemoveImageAnnotator();
-    GetRenderAccessibilityImpl()->tree_source_.AddImageAnnotator(
+    GetRenderAccessibilityImpl()->tree_source_->RemoveImageAnnotator();
+    GetRenderAccessibilityImpl()->tree_source_->AddImageAnnotator(
         GetRenderAccessibilityImpl()->ax_image_annotator_.get());
   }
 
@@ -1007,4 +1008,135 @@
   EXPECT_EQ(3u, mock_annotator().callbacks_.size());
 }
 
+// URL-keyed metrics recorder implementation that just counts the number
+// of times it's been called.
+class MockUkmRecorder : public ukm::MojoUkmRecorder {
+ public:
+  MockUkmRecorder()
+      : ukm::MojoUkmRecorder(
+            mojo::PendingRemote<ukm::mojom::UkmRecorderInterface>()) {}
+
+  void AddEntry(ukm::mojom::UkmEntryPtr entry) override { calls_++; }
+
+  int calls() const { return calls_; }
+
+ private:
+  int calls_ = 0;
+};
+
+// Subclass of BlinkAXTreeSource that retains the functionality but
+// enables simulating a serialize operation taking an arbitrarily long
+// amount of time (using simulated time).
+class TimeDelayBlinkAXTreeSource : public BlinkAXTreeSource {
+ public:
+  TimeDelayBlinkAXTreeSource(RenderFrameImpl* rfi,
+                             ui::AXMode mode,
+                             base::test::TaskEnvironment* task_environment)
+      : BlinkAXTreeSource(rfi, mode), task_environment_(task_environment) {}
+
+  void SetTimeDelayForNextSerialize(int time_delay_ms) {
+    time_delay_ms_ = time_delay_ms;
+  }
+
+  void SerializeNode(blink::WebAXObject node,
+                     AXContentNodeData* out_data) const override {
+    BlinkAXTreeSource::SerializeNode(node, out_data);
+    if (time_delay_ms_) {
+      task_environment_->FastForwardBy(
+          base::TimeDelta::FromMilliseconds(time_delay_ms_));
+      time_delay_ms_ = 0;
+    }
+  }
+
+ private:
+  mutable int time_delay_ms_ = 0;
+  base::test::TaskEnvironment* task_environment_;
+};
+
+// Tests for URL-keyed metrics.
+class RenderAccessibilityImplUKMTest : public RenderAccessibilityImplTest {
+ public:
+  void SetUp() override {
+    RenderAccessibilityImplTest::SetUp();
+    GetRenderAccessibilityImpl()->ukm_recorder_ =
+        std::make_unique<MockUkmRecorder>();
+    GetRenderAccessibilityImpl()->tree_source_ =
+        std::make_unique<TimeDelayBlinkAXTreeSource>(
+            GetRenderAccessibilityImpl()->render_frame_,
+            GetRenderAccessibilityImpl()->GetAccessibilityMode(),
+            &task_environment_);
+    GetRenderAccessibilityImpl()->serializer_ =
+        std::make_unique<BlinkAXTreeSerializer>(
+            GetRenderAccessibilityImpl()->tree_source_.get());
+  }
+
+  void TearDown() override { RenderAccessibilityImplTest::TearDown(); }
+
+  MockUkmRecorder* ukm_recorder() {
+    return static_cast<MockUkmRecorder*>(
+        GetRenderAccessibilityImpl()->ukm_recorder_.get());
+  }
+
+  void SetTimeDelayForNextSerialize(int time_delay_ms) {
+    static_cast<TimeDelayBlinkAXTreeSource*>(
+        GetRenderAccessibilityImpl()->tree_source_.get())
+        ->SetTimeDelayForNextSerialize(time_delay_ms);
+  }
+};
+
+TEST_F(RenderAccessibilityImplUKMTest, TestFireUKMs) {
+  LoadHTMLAndRefreshAccessibilityTree(R"HTML(
+      <body>
+        <input id="text" value="Hello, World">
+      </body>
+      )HTML");
+
+  // No URL-keyed metrics should be fired initially.
+  EXPECT_EQ(0, ukm_recorder()->calls());
+
+  // No URL-keyed metrics should be fired after we send one event.
+  WebDocument document = GetMainFrame()->GetDocument();
+  WebAXObject root_obj = WebAXObject::FromWebDocument(document);
+  GetRenderAccessibilityImpl()->HandleAXEvent(
+      ui::AXEvent(root_obj.AxID(), ax::mojom::Event::kChildrenChanged));
+  SendPendingAccessibilityEvents();
+  EXPECT_EQ(0, ukm_recorder()->calls());
+
+  // No URL-keyed metrics should be fired even after an event that takes
+  // 300 ms, but we should now have something to send.
+  // This must be >= kMinSerializationTimeToSendInMS
+  SetTimeDelayForNextSerialize(300);
+  GetRenderAccessibilityImpl()->HandleAXEvent(
+      ui::AXEvent(root_obj.AxID(), ax::mojom::Event::kChildrenChanged));
+  SendPendingAccessibilityEvents();
+  EXPECT_EQ(0, ukm_recorder()->calls());
+
+  // After 1000 seconds have passed, the next time we send an event we should
+  // send URL-keyed metrics.
+  task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1000));
+  GetRenderAccessibilityImpl()->HandleAXEvent(
+      ui::AXEvent(root_obj.AxID(), ax::mojom::Event::kChildrenChanged));
+  SendPendingAccessibilityEvents();
+  EXPECT_EQ(1, ukm_recorder()->calls());
+
+  // Send another event that takes a long (simulated) time to serialize.
+  // This must be >= kMinSerializationTimeToSendInMS
+  SetTimeDelayForNextSerialize(200);
+  GetRenderAccessibilityImpl()->HandleAXEvent(
+      ui::AXEvent(root_obj.AxID(), ax::mojom::Event::kChildrenChanged));
+  SendPendingAccessibilityEvents();
+
+  // We shouldn't have a new call to the UKM recorder yet, not enough
+  // time has elapsed.
+  EXPECT_EQ(1, ukm_recorder()->calls());
+
+  // Navigate to a new page.
+  GetRenderAccessibilityImpl()->DidCommitProvisionalLoad(
+      ui::PAGE_TRANSITION_LINK);
+
+  // Now we should have yet another UKM recorded because of the page
+  // transition.
+  EXPECT_EQ(2, ukm_recorder()->calls());
+}
+
 }  // namespace content
diff --git a/content/renderer/loader/child_url_loader_factory_bundle.cc b/content/renderer/loader/child_url_loader_factory_bundle.cc
index efbe934..ed1ce72 100644
--- a/content/renderer/loader/child_url_loader_factory_bundle.cc
+++ b/content/renderer/loader/child_url_loader_factory_bundle.cc
@@ -10,9 +10,13 @@
 #include <vector>
 
 #include "base/check.h"
+#include "base/debug/crash_logging.h"
+#include "base/debug/dump_without_crashing.h"
+#include "base/optional.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.h"
 #include "url/gurl.h"
+#include "url/origin.h"
 #include "url/url_constants.h"
 
 namespace content {
@@ -115,6 +119,39 @@
   return output;
 }
 
+class ScopedRequestCrashKeys {
+ public:
+  explicit ScopedRequestCrashKeys(const network::ResourceRequest& request);
+  ~ScopedRequestCrashKeys();
+
+  ScopedRequestCrashKeys(const ScopedRequestCrashKeys&) = delete;
+  ScopedRequestCrashKeys& operator=(const ScopedRequestCrashKeys&) = delete;
+
+ private:
+  base::debug::ScopedCrashKeyString url_;
+  url::debug::ScopedOriginCrashKey request_initiator_;
+};
+
+base::debug::CrashKeyString* GetRequestUrlCrashKey() {
+  static auto* crash_key = base::debug::AllocateCrashKeyString(
+      "request_url", base::debug::CrashKeySize::Size256);
+  return crash_key;
+}
+
+base::debug::CrashKeyString* GetRequestInitiatorCrashKey() {
+  static auto* crash_key = base::debug::AllocateCrashKeyString(
+      "request_initiator", base::debug::CrashKeySize::Size64);
+  return crash_key;
+}
+
+ScopedRequestCrashKeys::ScopedRequestCrashKeys(
+    const network::ResourceRequest& request)
+    : url_(GetRequestUrlCrashKey(), request.url.possibly_invalid_spec()),
+      request_initiator_(GetRequestInitiatorCrashKey(),
+                         base::OptionalOrNullptr(request.request_initiator)) {}
+
+ScopedRequestCrashKeys::~ScopedRequestCrashKeys() = default;
+
 }  // namespace
 
 ChildPendingURLLoaderFactoryBundle::ChildPendingURLLoaderFactoryBundle() =
@@ -168,6 +205,8 @@
       std::move(pending_isolated_world_factories_);
   other->direct_network_factory_remote_ =
       std::move(direct_network_factory_remote_);
+  if (is_deprecated_process_wide_factory_)
+    other->MarkAsDeprecatedProcessWideFactory();
   other->pending_prefetch_loader_factory_ =
       std::move(pending_prefetch_loader_factory_);
   other->bypass_redirect_checks_ = bypass_redirect_checks_;
@@ -198,6 +237,16 @@
   if (base_result)
     return base_result;
 
+  // All renderer-initiated requests need to provide a value for
+  // |request_initiator| - this is enforced by
+  // CorsURLLoaderFactory::IsValidRequest.
+  DCHECK(request.request_initiator.has_value());
+  if (is_deprecated_process_wide_factory_ &&
+      !request.request_initiator->opaque()) {
+    ScopedRequestCrashKeys crash_keys(request);
+    base::debug::DumpWithoutCrashing();
+  }
+
   InitDirectNetworkFactoryIfNecessary();
   DCHECK(direct_network_factory_);
   return direct_network_factory_.get();
@@ -264,7 +313,10 @@
   if (info->direct_network_factory_remote()) {
     direct_network_factory_.Bind(
         std::move(info->direct_network_factory_remote()));
+    is_deprecated_process_wide_factory_ =
+        info->is_deprecated_process_wide_factory();
   }
+
   if (info->pending_prefetch_loader_factory()) {
     prefetch_loader_factory_.Bind(
         std::move(info->pending_prefetch_loader_factory()));
@@ -335,13 +387,16 @@
   // Currently there is no need to override subresources from workers,
   // therefore |subresource_overrides| are not shared with the clones.
 
-  return std::make_unique<ChildPendingURLLoaderFactoryBundle>(
+  auto result = std::make_unique<ChildPendingURLLoaderFactoryBundle>(
       std::move(default_factory_pending_remote),
       std::move(appcache_factory_pending_remote),
       CloneRemoteMapToPendingRemoteMap(scheme_specific_factories_),
       CloneRemoteMapToPendingRemoteMap(isolated_world_factories_),
       std::move(direct_network_factory_remote),
       std::move(pending_prefetch_loader_factory), bypass_redirect_checks_);
+  if (is_deprecated_process_wide_factory_)
+    result->MarkAsDeprecatedProcessWideFactory();
+  return result;
 }
 
 std::unique_ptr<ChildPendingURLLoaderFactoryBundle>
@@ -369,12 +424,15 @@
     pending_prefetch_loader_factory = prefetch_loader_factory_.Unbind();
   }
 
-  return std::make_unique<ChildPendingURLLoaderFactoryBundle>(
+  auto result = std::make_unique<ChildPendingURLLoaderFactoryBundle>(
       std::move(pending_default_factory), std::move(pending_appcache_factory),
       BoundRemoteMapToPendingRemoteMap(std::move(scheme_specific_factories_)),
       BoundRemoteMapToPendingRemoteMap(std::move(isolated_world_factories_)),
       std::move(direct_network_factory_remote),
       std::move(pending_prefetch_loader_factory), bypass_redirect_checks_);
+  if (is_deprecated_process_wide_factory_)
+    result->MarkAsDeprecatedProcessWideFactory();
+  return result;
 }
 
 }  // namespace content
diff --git a/content/renderer/loader/child_url_loader_factory_bundle.h b/content/renderer/loader/child_url_loader_factory_bundle.h
index b80e598..91b35704 100644
--- a/content/renderer/loader/child_url_loader_factory_bundle.h
+++ b/content/renderer/loader/child_url_loader_factory_bundle.h
@@ -58,10 +58,19 @@
     return pending_prefetch_loader_factory_;
   }
 
+  void MarkAsDeprecatedProcessWideFactory() {
+    is_deprecated_process_wide_factory_ = true;
+  }
+  bool is_deprecated_process_wide_factory() const {
+    return is_deprecated_process_wide_factory_;
+  }
+
  protected:
   // PendingURLLoaderFactoryBundle overrides.
   scoped_refptr<network::SharedURLLoaderFactory> CreateFactory() override;
 
+  bool is_deprecated_process_wide_factory_ = false;
+
   mojo::PendingRemote<network::mojom::URLLoaderFactory>
       direct_network_factory_remote_;
   mojo::PendingRemote<network::mojom::URLLoaderFactory>
@@ -119,6 +128,10 @@
 
   virtual bool IsHostChildURLLoaderFactoryBundle() const;
 
+  void MarkAsDeprecatedProcessWideFactory() {
+    is_deprecated_process_wide_factory_ = true;
+  }
+
  protected:
   ~ChildURLLoaderFactoryBundle() override;
 
@@ -135,6 +148,8 @@
   mojo::Remote<network::mojom::URLLoaderFactory> direct_network_factory_;
   mojo::Remote<network::mojom::URLLoaderFactory> prefetch_loader_factory_;
 
+  bool is_deprecated_process_wide_factory_ = false;
+
   std::map<GURL, mojom::TransferrableURLLoaderPtr> subresource_overrides_;
 };
 
diff --git a/content/renderer/loader/web_url_request_util.cc b/content/renderer/loader/web_url_request_util.cc
index e351493..ecbbf6e 100644
--- a/content/renderer/loader/web_url_request_util.cc
+++ b/content/renderer/loader/web_url_request_util.cc
@@ -264,7 +264,7 @@
     const WebURLRequest& request) {
   return blink::WebMixedContent::ContextTypeFromRequestContext(
       request.GetRequestContext(),
-      /*strict_mixed_content_checking_for_plugin=*/false);
+      blink::WebMixedContent::CheckModeForPlugin::kLax);
 }
 
 #undef STATIC_ASSERT_ENUM
diff --git a/content/renderer/pepper/pepper_media_stream_video_track_host.cc b/content/renderer/pepper/pepper_media_stream_video_track_host.cc
index fd1f41b..92822bd 100644
--- a/content/renderer/pepper/pepper_media_stream_video_track_host.cc
+++ b/content/renderer/pepper/pepper_media_stream_video_track_host.cc
@@ -23,6 +23,7 @@
 #include "ppapi/host/host_message_context.h"
 #include "ppapi/proxy/ppapi_messages.h"
 #include "ppapi/shared_impl/media_stream_buffer.h"
+#include "third_party/blink/public/platform/web_string.h"
 #include "third_party/blink/public/web/modules/mediastream/media_stream_video_source.h"
 #include "third_party/blink/public/web/modules/mediastream/web_media_stream_utils.h"
 #include "third_party/libyuv/include/libyuv.h"
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 22cefbf3..7f73a8952 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -5815,6 +5815,7 @@
            GetLoadingUrl().spec() == url::kAboutBlankURL);
     loader_factories->Update(
         CreateDefaultURLLoaderFactoryBundle()->PassInterface());
+    loader_factories->MarkAsDeprecatedProcessWideFactory();
   }
 
   if (info) {
diff --git a/content/renderer/render_frame_proxy.cc b/content/renderer/render_frame_proxy.cc
index 817b908..44235f4 100644
--- a/content/renderer/render_frame_proxy.cc
+++ b/content/renderer/render_frame_proxy.cc
@@ -36,7 +36,6 @@
 #include "ipc/ipc_message.h"
 #include "ipc/ipc_message_macros.h"
 #include "mojo/public/cpp/bindings/pending_associated_remote.h"
-#include "printing/buildflags/buildflags.h"
 #include "third_party/blink/public/common/feature_policy/feature_policy.h"
 #include "third_party/blink/public/common/frame/frame_policy.h"
 #include "third_party/blink/public/common/navigation/triggering_event_info.h"
@@ -48,12 +47,6 @@
 #include "third_party/blink/public/web/web_view.h"
 #include "ui/gfx/geometry/size_conversions.h"
 
-#if BUILDFLAG(ENABLE_PRINTING)
-// nogncheck because dependency on //printing is conditional upon
-// enable_basic_printing flags.
-#include "printing/metafile_skia.h"          // nogncheck
-#endif
-
 namespace content {
 
 namespace {
@@ -722,26 +715,6 @@
   return GetContentClient()->renderer()->GetSadWebViewBitmap();
 }
 
-uint32_t RenderFrameProxy::Print(const blink::WebRect& rect,
-                                 cc::PaintCanvas* canvas) {
-#if BUILDFLAG(ENABLE_PRINTING)
-  auto* metafile = canvas->GetPrintingMetafile();
-  DCHECK(metafile);
-
-  // Create a place holder content for the remote frame so it can be replaced
-  // with actual content later.
-  uint32_t content_id =
-      metafile->CreateContentForRemoteFrame(rect, routing_id_);
-
-  // Inform browser to print the remote subframe.
-  Send(new FrameHostMsg_PrintCrossProcessSubframe(
-      routing_id_, rect, metafile->GetDocumentCookie()));
-  return content_id;
-#else
-  return 0;
-#endif
-}
-
 const viz::LocalSurfaceId& RenderFrameProxy::GetLocalSurfaceId() const {
   return parent_local_surface_id_allocator_
       ->GetCurrentLocalSurfaceIdAllocation()
diff --git a/content/renderer/render_frame_proxy.h b/content/renderer/render_frame_proxy.h
index 1359011..17d302d 100644
--- a/content/renderer/render_frame_proxy.h
+++ b/content/renderer/render_frame_proxy.h
@@ -197,7 +197,6 @@
   void UpdateRemoteViewportIntersection(
       const blink::ViewportIntersectionState& intersection_state) override;
   base::UnguessableToken GetDevToolsFrameToken() override;
-  uint32_t Print(const blink::WebRect& rect, cc::PaintCanvas* canvas) override;
 
   void DidStartLoading();
 
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index 2b6bff2..9d75e168 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -1380,27 +1380,7 @@
   web_screen_info.is_monochrome = info.is_monochrome;
   web_screen_info.rect = info.rect;
   web_screen_info.available_rect = info.available_rect;
-  switch (info.orientation_type) {
-    case SCREEN_ORIENTATION_VALUES_PORTRAIT_PRIMARY:
-      web_screen_info.orientation_type =
-          blink::kWebScreenOrientationPortraitPrimary;
-      break;
-    case SCREEN_ORIENTATION_VALUES_PORTRAIT_SECONDARY:
-      web_screen_info.orientation_type =
-          blink::kWebScreenOrientationPortraitSecondary;
-      break;
-    case SCREEN_ORIENTATION_VALUES_LANDSCAPE_PRIMARY:
-      web_screen_info.orientation_type =
-          blink::kWebScreenOrientationLandscapePrimary;
-      break;
-    case SCREEN_ORIENTATION_VALUES_LANDSCAPE_SECONDARY:
-      web_screen_info.orientation_type =
-          blink::kWebScreenOrientationLandscapeSecondary;
-      break;
-    default:
-      web_screen_info.orientation_type = blink::kWebScreenOrientationUndefined;
-      break;
-  }
+  web_screen_info.orientation_type = info.orientation_type;
   web_screen_info.orientation_angle = info.orientation_angle;
 
   return web_screen_info;
diff --git a/content/renderer/render_widget_browsertest.cc b/content/renderer/render_widget_browsertest.cc
index 6e43f93..aa7ffbc 100644
--- a/content/renderer/render_widget_browsertest.cc
+++ b/content/renderer/render_widget_browsertest.cc
@@ -120,7 +120,7 @@
   OnSynchronizeVisualProperties(visual_properties);
 
   visual_properties.screen_info.orientation_type =
-      SCREEN_ORIENTATION_VALUES_PORTRAIT_PRIMARY;
+      blink::mojom::ScreenOrientation::kPortraitPrimary;
   OnSynchronizeVisualProperties(visual_properties);
 }
 
diff --git a/content/renderer/render_widget_screen_metrics_emulator.cc b/content/renderer/render_widget_screen_metrics_emulator.cc
index 4278c4533..7ef03b65 100644
--- a/content/renderer/render_widget_screen_metrics_emulator.cc
+++ b/content/renderer/render_widget_screen_metrics_emulator.cc
@@ -109,29 +109,13 @@
   if (emulation_params_.device_scale_factor)
     device_scale_factor = emulation_params_.device_scale_factor;
 
-  ScreenOrientationValues orientation_type =
+  blink::mojom::ScreenOrientation orientation_type =
       original_screen_info().orientation_type;
   uint16_t orientation_angle = original_screen_info().orientation_angle;
-
-  switch (emulation_params_.screen_orientation_type) {
-    case blink::kWebScreenOrientationUndefined:
-      break;  // Leave as the real value.
-    case blink::kWebScreenOrientationPortraitPrimary:
-      orientation_type = SCREEN_ORIENTATION_VALUES_PORTRAIT_PRIMARY;
-      orientation_angle = emulation_params_.screen_orientation_angle;
-      break;
-    case blink::kWebScreenOrientationPortraitSecondary:
-      orientation_type = SCREEN_ORIENTATION_VALUES_PORTRAIT_SECONDARY;
-      orientation_angle = emulation_params_.screen_orientation_angle;
-      break;
-    case blink::kWebScreenOrientationLandscapePrimary:
-      orientation_type = SCREEN_ORIENTATION_VALUES_LANDSCAPE_PRIMARY;
-      orientation_angle = emulation_params_.screen_orientation_angle;
-      break;
-    case blink::kWebScreenOrientationLandscapeSecondary:
-      orientation_type = SCREEN_ORIENTATION_VALUES_LANDSCAPE_SECONDARY;
-      orientation_angle = emulation_params_.screen_orientation_angle;
-      break;
+  if (emulation_params_.screen_orientation_type !=
+      blink::mojom::ScreenOrientation::kUndefined) {
+    orientation_type = emulation_params_.screen_orientation_type;
+    orientation_angle = emulation_params_.screen_orientation_angle;
   }
 
   // Pass three emulation parameters to the blink side:
diff --git a/content/renderer/renderer_blink_platform_impl.h b/content/renderer/renderer_blink_platform_impl.h
index 83d6f87..bb40ad48 100644
--- a/content/renderer/renderer_blink_platform_impl.h
+++ b/content/renderer/renderer_blink_platform_impl.h
@@ -25,7 +25,6 @@
 #include "mojo/public/cpp/bindings/shared_remote.h"
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
-#include "third_party/blink/public/common/screen_orientation/web_screen_orientation_type.h"
 #include "third_party/blink/public/common/user_agent/user_agent_metadata.h"
 #include "third_party/blink/public/mojom/cache_storage/cache_storage.mojom.h"
 #include "third_party/blink/public/mojom/loader/code_cache.mojom.h"
diff --git a/content/shell/browser/web_test/web_test_bluetooth_adapter_provider.cc b/content/shell/browser/web_test/web_test_bluetooth_adapter_provider.cc
index a15af14..40931c52 100644
--- a/content/shell/browser/web_test/web_test_bluetooth_adapter_provider.cc
+++ b/content/shell/browser/web_test/web_test_bluetooth_adapter_provider.cc
@@ -726,7 +726,7 @@
     const std::string descriptorName = kCharacteristicUserDescription;
     auto user_description = std::make_unique<NiceMockBluetoothGattDescriptor>(
         measurement_interval.get(), descriptorName,
-        BluetoothUUID(kUserDescriptionUUID), false /* is_local */,
+        BluetoothUUID(kUserDescriptionUUID),
         device::BluetoothRemoteGattCharacteristic::PROPERTY_READ);
 
     ON_CALL(*user_description, ReadRemoteDescriptor_(_, _))
@@ -744,7 +744,7 @@
 
     auto client_config = std::make_unique<NiceMockBluetoothGattDescriptor>(
         measurement_interval.get(), "gatt.client_characteristic_configuration",
-        BluetoothUUID(kClientConfigUUID), false /* is_local */,
+        BluetoothUUID(kClientConfigUUID),
         device::BluetoothRemoteGattCharacteristic::PROPERTY_READ |
             device::BluetoothRemoteGattCharacteristic::PROPERTY_WRITE);
 
@@ -760,7 +760,7 @@
 
     auto no_read_descriptor = std::make_unique<NiceMockBluetoothGattDescriptor>(
         measurement_interval.get(), kBlocklistedReadDescriptorUUID,
-        BluetoothUUID(kBlocklistedReadDescriptorUUID), false,
+        BluetoothUUID(kBlocklistedReadDescriptorUUID),
         device::BluetoothRemoteGattCharacteristic::PROPERTY_READ |
             device::BluetoothRemoteGattCharacteristic::PROPERTY_WRITE);
 
@@ -780,7 +780,7 @@
     auto blocklisted_descriptor =
         std::make_unique<NiceMockBluetoothGattDescriptor>(
             measurement_interval.get(), kBlocklistedDescriptorUUID,
-            BluetoothUUID(kBlocklistedDescriptorUUID), false,
+            BluetoothUUID(kBlocklistedDescriptorUUID),
             device::BluetoothRemoteGattCharacteristic::PROPERTY_READ |
                 device::BluetoothRemoteGattCharacteristic::PROPERTY_WRITE);
 
@@ -1119,7 +1119,7 @@
 
   auto user_descriptor = std::make_unique<NiceMockBluetoothGattDescriptor>(
       measurement_interval.get(), kCharacteristicUserDescription,
-      BluetoothUUID(kUserDescriptionUUID), false,
+      BluetoothUUID(kUserDescriptionUUID),
       device::BluetoothRemoteGattCharacteristic::PROPERTY_READ);
 
   ON_CALL(*user_descriptor, ReadRemoteDescriptor_(_, _))
@@ -1440,8 +1440,7 @@
     MockBluetoothDevice* device,
     const std::string& uuid) {
   auto service = std::make_unique<NiceMockBluetoothGattService>(
-      device, identifier, BluetoothUUID(uuid), true /* is_primary */,
-      false /* is_local */);
+      device, identifier, BluetoothUUID(uuid), /*is_primary=*/true);
 
   return service;
 }
@@ -1698,8 +1697,8 @@
     const std::string& uuid,
     BluetoothRemoteGattCharacteristic::Properties properties) {
   auto characteristic = std::make_unique<NiceMockBluetoothGattCharacteristic>(
-      service, identifier, BluetoothUUID(uuid), false /* is_local */,
-      properties, BluetoothGattCharacteristic::Permission::PERMISSION_NONE);
+      service, identifier, BluetoothUUID(uuid), properties,
+      BluetoothGattCharacteristic::Permission::PERMISSION_NONE);
 
   ON_CALL(*characteristic, ReadRemoteCharacteristic_(_, _))
       .WillByDefault(
@@ -1753,7 +1752,7 @@
   // Add error descriptor to |characteristic|
   auto error_descriptor = std::make_unique<NiceMockBluetoothGattDescriptor>(
       characteristic.get(), kCharacteristicUserDescription,
-      BluetoothUUID(kUserDescriptionUUID), false,
+      BluetoothUUID(kUserDescriptionUUID),
       device::BluetoothRemoteGattCharacteristic::PROPERTY_READ);
 
   ON_CALL(*error_descriptor, ReadRemoteDescriptor_(_, _))
diff --git a/content/shell/renderer/web_test/mock_screen_orientation_client.cc b/content/shell/renderer/web_test/mock_screen_orientation_client.cc
index 880a060..96ff64c 100644
--- a/content/shell/renderer/web_test/mock_screen_orientation_client.cc
+++ b/content/shell/renderer/web_test/mock_screen_orientation_client.cc
@@ -17,27 +17,22 @@
 
 namespace content {
 
-MockScreenOrientationClient::MockScreenOrientationClient()
-    : main_frame_(nullptr),
-      current_lock_(blink::kWebScreenOrientationLockDefault),
-      device_orientation_(blink::kWebScreenOrientationPortraitPrimary),
-      current_orientation_(blink::kWebScreenOrientationPortraitPrimary),
-      is_disabled_(false) {}
+MockScreenOrientationClient::MockScreenOrientationClient() = default;
 
-MockScreenOrientationClient::~MockScreenOrientationClient() {}
+MockScreenOrientationClient::~MockScreenOrientationClient() = default;
 
 void MockScreenOrientationClient::ResetData() {
   main_frame_ = nullptr;
   current_lock_ = blink::kWebScreenOrientationLockDefault;
-  device_orientation_ = blink::kWebScreenOrientationPortraitPrimary;
-  current_orientation_ = blink::kWebScreenOrientationPortraitPrimary;
+  device_orientation_ = blink::mojom::ScreenOrientation::kPortraitPrimary;
+  current_orientation_ = blink::mojom::ScreenOrientation::kPortraitPrimary;
   is_disabled_ = false;
   receivers_.Clear();
 }
 
 bool MockScreenOrientationClient::UpdateDeviceOrientation(
     blink::WebLocalFrame* main_frame,
-    blink::WebScreenOrientationType orientation) {
+    blink::mojom::ScreenOrientation orientation) {
   main_frame_ = main_frame;
 
   if (device_orientation_ == orientation)
@@ -49,7 +44,7 @@
 }
 
 bool MockScreenOrientationClient::UpdateScreenOrientation(
-    blink::WebScreenOrientationType orientation) {
+    blink::mojom::ScreenOrientation orientation) {
   if (current_orientation_ == orientation)
     return false;
   current_orientation_ = orientation;
@@ -60,7 +55,7 @@
   return false;
 }
 
-blink::WebScreenOrientationType
+blink::mojom::ScreenOrientation
 MockScreenOrientationClient::CurrentOrientationType() const {
   return current_orientation_;
 }
@@ -74,19 +69,19 @@
 }
 
 unsigned MockScreenOrientationClient::OrientationTypeToAngle(
-    blink::WebScreenOrientationType type) {
+    blink::mojom::ScreenOrientation type) {
   unsigned angle;
   // FIXME(ostap): This relationship between orientationType and
   // orientationAngle is temporary. The test should be able to specify
   // the angle in addition to the orientation type.
   switch (type) {
-    case blink::kWebScreenOrientationLandscapePrimary:
+    case blink::mojom::ScreenOrientation::kLandscapePrimary:
       angle = 90;
       break;
-    case blink::kWebScreenOrientationLandscapeSecondary:
+    case blink::mojom::ScreenOrientation::kLandscapeSecondary:
       angle = 270;
       break;
-    case blink::kWebScreenOrientationPortraitSecondary:
+    case blink::mojom::ScreenOrientation::kPortraitSecondary:
       angle = 180;
       break;
     default:
@@ -96,25 +91,25 @@
 }
 
 bool MockScreenOrientationClient::IsOrientationAllowedByCurrentLock(
-    blink::WebScreenOrientationType orientation) {
+    blink::mojom::ScreenOrientation orientation) {
   if (current_lock_ == blink::kWebScreenOrientationLockDefault ||
       current_lock_ == blink::kWebScreenOrientationLockAny) {
     return true;
   }
 
   switch (orientation) {
-    case blink::kWebScreenOrientationPortraitPrimary:
+    case blink::mojom::ScreenOrientation::kPortraitPrimary:
       return current_lock_ == blink::kWebScreenOrientationLockPortraitPrimary ||
              current_lock_ == blink::kWebScreenOrientationLockPortrait;
-    case blink::kWebScreenOrientationPortraitSecondary:
+    case blink::mojom::ScreenOrientation::kPortraitSecondary:
       return current_lock_ ==
                  blink::kWebScreenOrientationLockPortraitSecondary ||
              current_lock_ == blink::kWebScreenOrientationLockPortrait;
-    case blink::kWebScreenOrientationLandscapePrimary:
+    case blink::mojom::ScreenOrientation::kLandscapePrimary:
       return current_lock_ ==
                  blink::kWebScreenOrientationLockLandscapePrimary ||
              current_lock_ == blink::kWebScreenOrientationLockLandscape;
-    case blink::kWebScreenOrientationLandscapeSecondary:
+    case blink::mojom::ScreenOrientation::kLandscapeSecondary:
       return current_lock_ ==
                  blink::kWebScreenOrientationLockLandscapeSecondary ||
              current_lock_ == blink::kWebScreenOrientationLockLandscape;
@@ -180,18 +175,18 @@
     UpdateScreenOrientation(device_orientation_);
 }
 
-blink::WebScreenOrientationType
+blink::mojom::ScreenOrientation
 MockScreenOrientationClient::SuitableOrientationForCurrentLock() {
   switch (current_lock_) {
     case blink::kWebScreenOrientationLockPortraitSecondary:
-      return blink::kWebScreenOrientationPortraitSecondary;
+      return blink::mojom::ScreenOrientation::kPortraitSecondary;
     case blink::kWebScreenOrientationLockLandscapePrimary:
     case blink::kWebScreenOrientationLockLandscape:
-      return blink::kWebScreenOrientationLandscapePrimary;
+      return blink::mojom::ScreenOrientation::kLandscapePrimary;
     case blink::kWebScreenOrientationLockLandscapeSecondary:
-      return blink::kWebScreenOrientationLandscapePrimary;
+      return blink::mojom::ScreenOrientation::kLandscapePrimary;
     default:
-      return blink::kWebScreenOrientationPortraitPrimary;
+      return blink::mojom::ScreenOrientation::kPortraitPrimary;
   }
 }
 
diff --git a/content/shell/renderer/web_test/mock_screen_orientation_client.h b/content/shell/renderer/web_test/mock_screen_orientation_client.h
index 65bbf9a..20587f2 100644
--- a/content/shell/renderer/web_test/mock_screen_orientation_client.h
+++ b/content/shell/renderer/web_test/mock_screen_orientation_client.h
@@ -13,7 +13,7 @@
 #include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
 #include "services/device/public/mojom/screen_orientation.mojom.h"
 #include "third_party/blink/public/common/screen_orientation/web_screen_orientation_lock_type.h"
-#include "third_party/blink/public/common/screen_orientation/web_screen_orientation_type.h"
+#include "third_party/blink/public/mojom/widget/screen_orientation.mojom.h"
 
 namespace blink {
 class WebLocalFrame;
@@ -28,9 +28,9 @@
 
   void ResetData();
   bool UpdateDeviceOrientation(blink::WebLocalFrame* main_frame,
-                               blink::WebScreenOrientationType orientation);
+                               blink::mojom::ScreenOrientation orientation);
 
-  blink::WebScreenOrientationType CurrentOrientationType() const;
+  blink::mojom::ScreenOrientation CurrentOrientationType() const;
   unsigned CurrentOrientationAngle() const;
   bool IsDisabled() const { return is_disabled_; }
   void SetDisabled(bool disabled);
@@ -48,16 +48,19 @@
                       LockOrientationCallback callback);
   void ResetLockSync();
 
-  bool UpdateScreenOrientation(blink::WebScreenOrientationType);
-  bool IsOrientationAllowedByCurrentLock(blink::WebScreenOrientationType);
-  blink::WebScreenOrientationType SuitableOrientationForCurrentLock();
-  static unsigned OrientationTypeToAngle(blink::WebScreenOrientationType);
+  bool UpdateScreenOrientation(blink::mojom::ScreenOrientation);
+  bool IsOrientationAllowedByCurrentLock(blink::mojom::ScreenOrientation);
+  blink::mojom::ScreenOrientation SuitableOrientationForCurrentLock();
+  static unsigned OrientationTypeToAngle(blink::mojom::ScreenOrientation);
 
-  blink::WebLocalFrame* main_frame_;
-  blink::WebScreenOrientationLockType current_lock_;
-  blink::WebScreenOrientationType device_orientation_;
-  blink::WebScreenOrientationType current_orientation_;
-  bool is_disabled_;
+  blink::WebLocalFrame* main_frame_ = nullptr;
+  blink::WebScreenOrientationLockType current_lock_ =
+      blink::kWebScreenOrientationLockDefault;
+  blink::mojom::ScreenOrientation device_orientation_ =
+      blink::mojom::ScreenOrientation::kPortraitPrimary;
+  blink::mojom::ScreenOrientation current_orientation_ =
+      blink::mojom::ScreenOrientation::kPortraitPrimary;
+  bool is_disabled_ = false;
   mojo::AssociatedReceiverSet<device::mojom::ScreenOrientation> receivers_;
 
   DISALLOW_COPY_AND_ASSIGN(MockScreenOrientationClient);
diff --git a/content/shell/renderer/web_test/test_runner.cc b/content/shell/renderer/web_test/test_runner.cc
index f65cd03c..5639f60 100644
--- a/content/shell/renderer/web_test/test_runner.cc
+++ b/content/shell/renderer/web_test/test_runner.cc
@@ -2786,17 +2786,17 @@
 }
 
 void TestRunner::SetMockScreenOrientation(const std::string& orientation_str) {
-  blink::WebScreenOrientationType orientation;
+  blink::mojom::ScreenOrientation orientation;
 
   if (orientation_str == "portrait-primary") {
-    orientation = blink::kWebScreenOrientationPortraitPrimary;
+    orientation = blink::mojom::ScreenOrientation::kPortraitPrimary;
   } else if (orientation_str == "portrait-secondary") {
-    orientation = blink::kWebScreenOrientationPortraitSecondary;
+    orientation = blink::mojom::ScreenOrientation::kPortraitSecondary;
   } else if (orientation_str == "landscape-primary") {
-    orientation = blink::kWebScreenOrientationLandscapePrimary;
+    orientation = blink::mojom::ScreenOrientation::kLandscapePrimary;
   } else {
     DCHECK_EQ("landscape-secondary", orientation_str);
-    orientation = blink::kWebScreenOrientationLandscapeSecondary;
+    orientation = blink::mojom::ScreenOrientation::kLandscapeSecondary;
   }
 
   // TODO(lukasza): Need to make MockScreenOrientation updates work for
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index d326d76..4afcc24 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -227,6 +227,8 @@
     "../public/test/test_web_ui.h",
     "../public/test/test_web_ui_data_source.cc",
     "../public/test/test_web_ui_data_source.h",
+    "../public/test/test_web_ui_listener_observer.cc",
+    "../public/test/test_web_ui_listener_observer.h",
     "../public/test/text_input_test_utils.cc",
     "../public/test/text_input_test_utils.h",
     "../public/test/text_input_test_utils_mac.mm",
@@ -243,8 +245,8 @@
     "../public/test/web_contents_tester.h",
     "../public/test/web_ui_browsertest_util.cc",
     "../public/test/web_ui_browsertest_util.h",
-    "content_browser_sequence_checker.cc",
-    "content_browser_sequence_checker.h",
+    "content_browser_consistency_checker.cc",
+    "content_browser_consistency_checker.h",
     "content_test_suite.cc",
     "content_test_suite.h",
     "did_commit_navigation_interceptor.cc",
@@ -347,8 +349,8 @@
     "test_web_contents.cc",
     "test_web_contents.h",
     "test_web_contents_factory.cc",
-    "web_contents_observer_sequence_checker.cc",
-    "web_contents_observer_sequence_checker.h",
+    "web_contents_observer_consistency_checker.cc",
+    "web_contents_observer_consistency_checker.h",
     "web_gesture_curve_mock.cc",
     "web_gesture_curve_mock.h",
   ]
diff --git a/content/test/content_browser_consistency_checker.cc b/content/test/content_browser_consistency_checker.cc
new file mode 100644
index 0000000..a55aefdb
--- /dev/null
+++ b/content/test/content_browser_consistency_checker.cc
@@ -0,0 +1,40 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/test/content_browser_consistency_checker.h"
+
+#include "base/bind.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/test/web_contents_observer_consistency_checker.h"
+
+namespace content {
+
+namespace {
+bool g_consistency_checks_already_enabled = false;
+}
+
+ContentBrowserConsistencyChecker::ContentBrowserConsistencyChecker() {
+  CHECK(!g_consistency_checks_already_enabled)
+      << "Tried to enable ContentBrowserConsistencyChecker, but it's already "
+      << "been enabled.";
+  g_consistency_checks_already_enabled = true;
+
+  creation_hook_ = base::BindRepeating(
+      &ContentBrowserConsistencyChecker::OnWebContentsCreated,
+      base::Unretained(this));
+  WebContentsImpl::FriendWrapper::AddCreatedCallbackForTesting(creation_hook_);
+}
+
+ContentBrowserConsistencyChecker::~ContentBrowserConsistencyChecker() {
+  WebContentsImpl::FriendWrapper::RemoveCreatedCallbackForTesting(
+      creation_hook_);
+  g_consistency_checks_already_enabled = false;
+}
+
+void ContentBrowserConsistencyChecker::OnWebContentsCreated(
+    WebContents* web_contents) {
+  WebContentsObserverConsistencyChecker::Enable(web_contents);
+}
+
+}  // namespace content
diff --git a/content/test/content_browser_consistency_checker.h b/content/test/content_browser_consistency_checker.h
new file mode 100644
index 0000000..01ecabd34
--- /dev/null
+++ b/content/test/content_browser_consistency_checker.h
@@ -0,0 +1,42 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_TEST_CONTENT_BROWSER_CONSISTENCY_CHECKER_H_
+#define CONTENT_TEST_CONTENT_BROWSER_CONSISTENCY_CHECKER_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+
+namespace content {
+
+class WebContents;
+
+// While an instance of this class exists, a bunch of consistency checks are
+// enabled to validate the correctness of the browser side of the content layer
+// implementation. It's good to enable these in both unit tests and browser
+// tests, as a means of detecting bugs in the implementation of the content api.
+//
+// These checks should typically be enabled by the base class of any test
+// framework that would instantiate a WebContents -- ContentBrowserTest::SetUp,
+// RenderViewHostTestHarness::SetUp, and so forth. Individual tests won't
+// typically need to enable them.
+//
+// For the nuts and bolts of what the checks enforce, see the implementation.
+class ContentBrowserConsistencyChecker {
+ public:
+  ContentBrowserConsistencyChecker();
+  ~ContentBrowserConsistencyChecker();
+
+ private:
+  void OnWebContentsCreated(WebContents* web_contents);
+
+  // The callback needs to be cached so that it can be unregistered.
+  base::RepeatingCallback<void(WebContents*)> creation_hook_;
+
+  DISALLOW_COPY_AND_ASSIGN(ContentBrowserConsistencyChecker);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_TEST_CONTENT_BROWSER_CONSISTENCY_CHECKER_H_
diff --git a/content/test/content_browser_sequence_checker.cc b/content/test/content_browser_sequence_checker.cc
deleted file mode 100644
index c6b89f5..0000000
--- a/content/test/content_browser_sequence_checker.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/test/content_browser_sequence_checker.h"
-
-#include "base/bind.h"
-#include "content/browser/web_contents/web_contents_impl.h"
-#include "content/test/web_contents_observer_sequence_checker.h"
-
-namespace content {
-
-namespace {
-bool g_sequence_checks_already_enabled = false;
-}
-
-ContentBrowserSequenceChecker::ContentBrowserSequenceChecker() {
-  CHECK(!g_sequence_checks_already_enabled)
-      << "Tried to enable ContentBrowserSequenceChecker, but it's already been "
-      << "enabled.";
-  g_sequence_checks_already_enabled = true;
-
-  creation_hook_ =
-      base::BindRepeating(&ContentBrowserSequenceChecker::OnWebContentsCreated,
-                          base::Unretained(this));
-  WebContentsImpl::FriendWrapper::AddCreatedCallbackForTesting(creation_hook_);
-}
-
-ContentBrowserSequenceChecker::~ContentBrowserSequenceChecker() {
-  WebContentsImpl::FriendWrapper::RemoveCreatedCallbackForTesting(
-      creation_hook_);
-  g_sequence_checks_already_enabled = false;
-}
-
-void ContentBrowserSequenceChecker::OnWebContentsCreated(
-    WebContents* web_contents) {
-  WebContentsObserverSequenceChecker::Enable(web_contents);
-}
-
-}  // namespace content
diff --git a/content/test/content_browser_sequence_checker.h b/content/test/content_browser_sequence_checker.h
deleted file mode 100644
index 8acb84f..0000000
--- a/content/test/content_browser_sequence_checker.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_TEST_CONTENT_BROWSER_SEQUENCE_CHECKER_H_
-#define CONTENT_TEST_CONTENT_BROWSER_SEQUENCE_CHECKER_H_
-
-#include "base/callback.h"
-#include "base/macros.h"
-
-namespace content {
-
-class WebContents;
-
-// While an instance of this class exists, a bunch of sequence checks are
-// enabled to validate the correctness of the browser side of the content layer
-// implementation. It's good to enable these in both unit tests and browser
-// tests, as a means of detecting bugs in the implementation of the content api.
-//
-// These checks should typically be enabled by the base class of any test
-// framework that would instantiate a WebContents -- ContentBrowserTest::SetUp,
-// RenderViewHostTestHarness::SetUp, and so forth. Individual tests won't
-// typically need to enable them.
-//
-// For the nuts and bolts of what the checks enforce, see the implementation.
-class ContentBrowserSequenceChecker {
- public:
-  ContentBrowserSequenceChecker();
-  ~ContentBrowserSequenceChecker();
-
- private:
-  void OnWebContentsCreated(WebContents* web_contents);
-
-  // The callback needs to be cached so that it can be unregistered.
-  base::RepeatingCallback<void(WebContents*)> creation_hook_;
-
-  DISALLOW_COPY_AND_ASSIGN(ContentBrowserSequenceChecker);
-};
-
-}  // namespace content
-
-#endif  // CONTENT_TEST_CONTENT_BROWSER_SEQUENCE_CHECKER_H_
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
index 9f7fac7..ca2c75e 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
@@ -133,6 +133,13 @@
 crbug.com/979444 [ win ] conformance/textures/misc/texture-corner-case-videos.html [ RetryOnFailure ]
 crbug.com/979444 [ android ] conformance/textures/misc/texture-corner-case-videos.html [ RetryOnFailure ]
 
+# TODO(crbug.com/1097338): simplify this expectation once fuchsia case is fixed
+crbug.com/1105129 [ android ] conformance/context/context-creation.html [ RetryOnFailure ]
+crbug.com/1105129 [ chromeos ] conformance/context/context-creation.html [ RetryOnFailure ]
+crbug.com/1105129 [ linux ] conformance/context/context-creation.html [ RetryOnFailure ]
+crbug.com/1105129 [ mac ] conformance/context/context-creation.html [ RetryOnFailure ]
+crbug.com/1105129 [ win ] conformance/context/context-creation.html [ RetryOnFailure ]
+
 # Win / AMD / Passthrough command decoder / D3D11
 crbug.com/685232 [ win amd d3d11 passthrough ] conformance/textures/misc/copytexsubimage2d-subrects.html [ RetryOnFailure ]
 crbug.com/772037 [ win7 debug amd d3d11 passthrough ] conformance/textures/misc/texture-sub-image-cube-maps.html [ RetryOnFailure ]
diff --git a/content/test/web_contents_observer_consistency_checker.cc b/content/test/web_contents_observer_consistency_checker.cc
new file mode 100644
index 0000000..b50017e
--- /dev/null
+++ b/content/test/web_contents_observer_consistency_checker.cc
@@ -0,0 +1,418 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/test/web_contents_observer_consistency_checker.h"
+
+#include "base/memory/ptr_util.h"
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+#include "build/build_config.h"
+#include "content/browser/frame_host/render_frame_host_impl.h"
+#include "content/common/content_navigation_policy.h"
+#include "content/common/frame_messages.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/site_instance.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "net/base/net_errors.h"
+
+namespace content {
+
+namespace {
+
+const char kWebContentsObserverConsistencyCheckerKey[] =
+    "WebContentsObserverConsistencyChecker";
+
+GlobalRoutingID GetRoutingPair(RenderFrameHost* host) {
+  if (!host)
+    return GlobalRoutingID(0, 0);
+  return GlobalRoutingID(host->GetProcess()->GetID(), host->GetRoutingID());
+}
+
+}  // namespace
+
+// static
+void WebContentsObserverConsistencyChecker::Enable(WebContents* web_contents) {
+  if (web_contents->GetUserData(&kWebContentsObserverConsistencyCheckerKey))
+    return;
+  web_contents->SetUserData(
+      &kWebContentsObserverConsistencyCheckerKey,
+      base::WrapUnique(
+          new WebContentsObserverConsistencyChecker(web_contents)));
+}
+
+void WebContentsObserverConsistencyChecker::RenderFrameCreated(
+    RenderFrameHost* render_frame_host) {
+  CHECK(!web_contents_destroyed_);
+  GlobalRoutingID routing_pair = GetRoutingPair(render_frame_host);
+  bool frame_exists = !live_routes_.insert(routing_pair).second;
+  deleted_routes_.erase(routing_pair);
+
+  if (frame_exists) {
+    CHECK(false) << "RenderFrameCreated called more than once for routing pair:"
+                 << Format(render_frame_host);
+  }
+
+  CHECK(render_frame_host->IsRenderFrameCreated())
+      << "RenderFrameCreated was called for a RenderFrameHost that has not been"
+         "marked created.";
+  CHECK(render_frame_host->GetProcess()->IsInitializedAndNotDead())
+      << "RenderFrameCreated was called for a RenderFrameHost whose render "
+         "process is not currently live, so there's no way for the RenderFrame "
+         "to have been created.";
+  CHECK(render_frame_host->IsRenderFrameLive())
+      << "RenderFrameCreated called on for a RenderFrameHost that thinks it is "
+         "not alive.";
+
+  EnsureStableParentValue(render_frame_host);
+  CHECK(!HasAnyChildren(render_frame_host));
+  if (render_frame_host->GetParent()) {
+    // It should also be a current host.
+    GlobalRoutingID parent_routing_pair =
+        GetRoutingPair(render_frame_host->GetParent());
+
+    CHECK(current_hosts_.count(parent_routing_pair))
+        << "RenderFrameCreated called for a RenderFrameHost whose parent was "
+        << "not a current RenderFrameHost. Only the current frame should be "
+        << "spawning children.";
+  }
+}
+
+void WebContentsObserverConsistencyChecker::RenderFrameDeleted(
+    RenderFrameHost* render_frame_host) {
+  CHECK(!web_contents_destroyed_);
+  CHECK(!render_frame_host->IsRenderFrameCreated())
+      << "RenderFrameDeleted was called for a RenderFrameHost that is"
+         "(still) marked as created.";
+  CHECK(!render_frame_host->IsRenderFrameLive())
+      << "RenderFrameDeleted was called for a RenderFrameHost that is"
+         "still live.";
+
+  GlobalRoutingID routing_pair = GetRoutingPair(render_frame_host);
+  bool was_live = !!live_routes_.erase(routing_pair);
+  bool was_dead_already = !deleted_routes_.insert(routing_pair).second;
+
+  if (was_dead_already) {
+    CHECK(false) << "RenderFrameDeleted called more than once for routing pair "
+                 << Format(render_frame_host);
+  } else if (!was_live) {
+    CHECK(false) << "RenderFrameDeleted called for routing pair "
+                 << Format(render_frame_host)
+                 << " for which RenderFrameCreated was never called";
+  }
+
+  EnsureStableParentValue(render_frame_host);
+  CHECK(!HasAnyChildren(render_frame_host));
+  if (render_frame_host->GetParent())
+    AssertRenderFrameExists(render_frame_host->GetParent());
+
+  // All players should have been paused by this point.
+  for (const auto& id : active_media_players_)
+    CHECK_NE(id.render_frame_host, render_frame_host);
+}
+
+void WebContentsObserverConsistencyChecker::
+    RenderFrameForInterstitialPageCreated(RenderFrameHost* render_frame_host) {
+  // TODO(nick): Record this.
+}
+
+void WebContentsObserverConsistencyChecker::RenderFrameHostChanged(
+    RenderFrameHost* old_host,
+    RenderFrameHost* new_host) {
+  CHECK(new_host);
+  CHECK_NE(new_host, old_host);
+  CHECK(GetRoutingPair(old_host) != GetRoutingPair(new_host));
+
+  if (old_host) {
+    EnsureStableParentValue(old_host);
+    CHECK_EQ(old_host->GetParent(), new_host->GetParent());
+    GlobalRoutingID routing_pair = GetRoutingPair(old_host);
+    // If the navigation requires a new RFH, IsCurrent on old host should be
+    // false.
+    CHECK(!old_host->IsCurrent());
+    bool old_did_exist = !!current_hosts_.erase(routing_pair);
+    if (!old_did_exist) {
+      CHECK(false)
+          << "RenderFrameHostChanged called with old host that did not exist:"
+          << Format(old_host);
+    }
+  }
+
+  CHECK(new_host->IsCurrent());
+  EnsureStableParentValue(new_host);
+  if (new_host->GetParent()) {
+    AssertRenderFrameExists(new_host->GetParent());
+    CHECK(current_hosts_.count(GetRoutingPair(new_host->GetParent())))
+        << "Parent of frame being committed must be current.";
+  }
+
+  GlobalRoutingID routing_pair = GetRoutingPair(new_host);
+  bool host_exists = !current_hosts_.insert(routing_pair).second;
+  if (host_exists) {
+    CHECK(false)
+        << "RenderFrameHostChanged called more than once for routing pair:"
+        << Format(new_host);
+  }
+
+  // If |new_host| is restored from the BackForwardCache, it can contain
+  // iframes, otherwise it has just been created and can't contain iframes for
+  // the moment.
+  if (!IsBackForwardCacheEnabled()) {
+    CHECK(!HasAnyChildren(new_host))
+        << "A frame should not have children before it is committed.";
+  }
+}
+
+void WebContentsObserverConsistencyChecker::FrameDeleted(
+    RenderFrameHost* render_frame_host) {
+  // A frame can be deleted before RenderFrame in the renderer process is
+  // created, so there is not much that can be enforced here.
+  CHECK(!web_contents_destroyed_);
+
+  EnsureStableParentValue(render_frame_host);
+
+  CHECK(!HasAnyChildren(render_frame_host))
+      << "All children should be deleted before a frame is detached.";
+
+  GlobalRoutingID routing_pair = GetRoutingPair(render_frame_host);
+  CHECK(current_hosts_.erase(routing_pair))
+      << "FrameDeleted called with a non-current RenderFrameHost.";
+
+  if (render_frame_host->GetParent())
+    AssertRenderFrameExists(render_frame_host->GetParent());
+}
+
+void WebContentsObserverConsistencyChecker::DidStartNavigation(
+    NavigationHandle* navigation_handle) {
+  CHECK(!NavigationIsOngoing(navigation_handle));
+
+  CHECK(!navigation_handle->HasCommitted());
+  CHECK(!navigation_handle->IsErrorPage());
+  CHECK_EQ(navigation_handle->GetWebContents(), web_contents());
+
+  ongoing_navigations_.insert(navigation_handle);
+}
+
+void WebContentsObserverConsistencyChecker::DidRedirectNavigation(
+    NavigationHandle* navigation_handle) {
+  CHECK(NavigationIsOngoing(navigation_handle));
+
+  CHECK(navigation_handle->GetNetErrorCode() == net::OK);
+  CHECK(!navigation_handle->HasCommitted());
+  CHECK(!navigation_handle->IsErrorPage());
+  CHECK_EQ(navigation_handle->GetWebContents(), web_contents());
+}
+
+void WebContentsObserverConsistencyChecker::ReadyToCommitNavigation(
+    NavigationHandle* navigation_handle) {
+  CHECK(NavigationIsOngoing(navigation_handle));
+
+  CHECK(!navigation_handle->HasCommitted());
+  CHECK(navigation_handle->GetRenderFrameHost());
+  CHECK_EQ(navigation_handle->GetWebContents(), web_contents());
+  CHECK(navigation_handle->GetRenderFrameHost() != nullptr);
+
+  ready_to_commit_hosts_.insert(
+      std::make_pair(navigation_handle->GetNavigationId(),
+                     navigation_handle->GetRenderFrameHost()));
+}
+
+void WebContentsObserverConsistencyChecker::DidFinishNavigation(
+    NavigationHandle* navigation_handle) {
+  CHECK(NavigationIsOngoing(navigation_handle));
+
+  CHECK(!(navigation_handle->HasCommitted() &&
+          !navigation_handle->IsErrorPage()) ||
+        navigation_handle->GetNetErrorCode() == net::OK);
+  CHECK_EQ(navigation_handle->GetWebContents(), web_contents());
+
+  CHECK(!navigation_handle->HasCommitted() ||
+        navigation_handle->GetRenderFrameHost() != nullptr);
+
+  CHECK(!navigation_handle->HasCommitted() ||
+        navigation_handle->GetRenderFrameHost()->IsCurrent());
+
+  // If ReadyToCommitNavigation was dispatched, verify that the
+  // |navigation_handle| has the same RenderFrameHost at this time as the one
+  // returned at ReadyToCommitNavigation.
+  if (base::Contains(ready_to_commit_hosts_,
+                     navigation_handle->GetNavigationId())) {
+    CHECK_EQ(ready_to_commit_hosts_[navigation_handle->GetNavigationId()],
+             navigation_handle->GetRenderFrameHost());
+    ready_to_commit_hosts_.erase(navigation_handle->GetNavigationId());
+  }
+
+  ongoing_navigations_.erase(navigation_handle);
+}
+
+void WebContentsObserverConsistencyChecker::DocumentAvailableInMainFrame() {
+  AssertMainFrameExists();
+}
+
+void WebContentsObserverConsistencyChecker::
+    DocumentOnLoadCompletedInMainFrame() {
+  CHECK(web_contents()->IsDocumentOnLoadCompletedInMainFrame());
+  AssertMainFrameExists();
+}
+
+void WebContentsObserverConsistencyChecker::DOMContentLoaded(
+    RenderFrameHost* render_frame_host) {
+  AssertRenderFrameExists(render_frame_host);
+}
+
+void WebContentsObserverConsistencyChecker::DidFinishLoad(
+    RenderFrameHost* render_frame_host,
+    const GURL& validated_url) {
+  AssertRenderFrameExists(render_frame_host);
+}
+
+void WebContentsObserverConsistencyChecker::DidFailLoad(
+    RenderFrameHost* render_frame_host,
+    const GURL& validated_url,
+    int error_code) {
+  AssertRenderFrameExists(render_frame_host);
+}
+
+void WebContentsObserverConsistencyChecker::DidOpenRequestedURL(
+    WebContents* new_contents,
+    RenderFrameHost* source_render_frame_host,
+    const GURL& url,
+    const Referrer& referrer,
+    WindowOpenDisposition disposition,
+    ui::PageTransition transition,
+    bool started_from_context_menu,
+    bool renderer_initiated) {
+  AssertRenderFrameExists(source_render_frame_host);
+}
+
+void WebContentsObserverConsistencyChecker::MediaStartedPlaying(
+    const MediaPlayerInfo& media_info,
+    const MediaPlayerId& id) {
+  CHECK(!web_contents_destroyed_);
+  CHECK(!base::Contains(active_media_players_, id));
+  active_media_players_.push_back(id);
+}
+
+void WebContentsObserverConsistencyChecker::MediaStoppedPlaying(
+    const MediaPlayerInfo& media_info,
+    const MediaPlayerId& id,
+    WebContentsObserver::MediaStoppedReason reason) {
+  CHECK(!web_contents_destroyed_);
+  CHECK(base::Contains(active_media_players_, id));
+  base::Erase(active_media_players_, id);
+}
+
+bool WebContentsObserverConsistencyChecker::OnMessageReceived(
+    const IPC::Message& message,
+    RenderFrameHost* render_frame_host) {
+  CHECK(render_frame_host->IsRenderFrameLive());
+
+  AssertRenderFrameExists(render_frame_host);
+  return false;
+}
+
+void WebContentsObserverConsistencyChecker::WebContentsDestroyed() {
+  CHECK(!web_contents_destroyed_);
+  web_contents_destroyed_ = true;
+  CHECK(ongoing_navigations_.empty());
+  CHECK(active_media_players_.empty());
+  CHECK(live_routes_.empty());
+}
+
+void WebContentsObserverConsistencyChecker::DidStartLoading() {
+  // TODO(clamy): add checks for the loading state in the rest of observer
+  // methods.
+  CHECK(!is_loading_);
+  CHECK(web_contents()->IsLoading());
+  is_loading_ = true;
+}
+
+void WebContentsObserverConsistencyChecker::DidStopLoading() {
+  // TODO(crbug.com/466089): Add back CHECK(is_loading_). The CHECK was removed
+  // because of flaky failures during browser_test shutdown.
+  CHECK(!web_contents()->IsLoading());
+  is_loading_ = false;
+}
+
+WebContentsObserverConsistencyChecker::WebContentsObserverConsistencyChecker(
+    WebContents* web_contents)
+    : WebContentsObserver(web_contents),
+      is_loading_(false),
+      web_contents_destroyed_(false) {}
+
+WebContentsObserverConsistencyChecker::
+    ~WebContentsObserverConsistencyChecker() {
+  CHECK(web_contents_destroyed_);
+  CHECK(ready_to_commit_hosts_.empty());
+}
+
+void WebContentsObserverConsistencyChecker::AssertRenderFrameExists(
+    RenderFrameHost* render_frame_host) {
+  CHECK(!web_contents_destroyed_);
+  GlobalRoutingID routing_pair = GetRoutingPair(render_frame_host);
+
+  bool render_frame_created_happened = live_routes_.count(routing_pair) != 0;
+  bool render_frame_deleted_happened = deleted_routes_.count(routing_pair) != 0;
+
+  CHECK(render_frame_created_happened)
+      << "A RenderFrameHost pointer was passed to a WebContentsObserver "
+      << "method, but WebContentsObserver::RenderFrameCreated was never called "
+      << "for that RenderFrameHost: " << Format(render_frame_host);
+  CHECK(!render_frame_deleted_happened)
+      << "A RenderFrameHost pointer was passed to a WebContentsObserver "
+      << "method, but WebContentsObserver::RenderFrameDeleted had already been "
+      << "called on that frame:" << Format(render_frame_host);
+}
+
+void WebContentsObserverConsistencyChecker::AssertMainFrameExists() {
+  AssertRenderFrameExists(web_contents()->GetMainFrame());
+}
+
+std::string WebContentsObserverConsistencyChecker::Format(
+    RenderFrameHost* render_frame_host) {
+  return base::StringPrintf(
+      "(%d, %d -> %s)", render_frame_host->GetProcess()->GetID(),
+      render_frame_host->GetRoutingID(),
+      render_frame_host->GetSiteInstance()->GetSiteURL().spec().c_str());
+}
+
+bool WebContentsObserverConsistencyChecker::NavigationIsOngoing(
+    NavigationHandle* navigation_handle) {
+  auto it = ongoing_navigations_.find(navigation_handle);
+  return it != ongoing_navigations_.end();
+}
+
+void WebContentsObserverConsistencyChecker::EnsureStableParentValue(
+    RenderFrameHost* render_frame_host) {
+  GlobalRoutingID routing_pair = GetRoutingPair(render_frame_host);
+  GlobalRoutingID parent_routing_pair =
+      GetRoutingPair(render_frame_host->GetParent());
+
+  auto it = parent_ids_.find(routing_pair);
+  if (it == parent_ids_.end()) {
+    parent_ids_.insert(std::make_pair(routing_pair, parent_routing_pair));
+  } else {
+    GlobalRoutingID former_parent_routing_pair = it->second;
+    CHECK(former_parent_routing_pair == parent_routing_pair)
+        << "RFH's parent value changed over time! That is really not good!";
+  }
+}
+
+bool WebContentsObserverConsistencyChecker::HasAnyChildren(
+    RenderFrameHost* parent) {
+  GlobalRoutingID parent_routing_pair = GetRoutingPair(parent);
+  for (auto& entry : parent_ids_) {
+    if (entry.second == parent_routing_pair) {
+      if (live_routes_.count(entry.first))
+        return true;
+      if (current_hosts_.count(entry.first))
+        return true;
+    }
+  }
+  return false;
+}
+
+}  // namespace content
diff --git a/content/test/web_contents_observer_consistency_checker.h b/content/test/web_contents_observer_consistency_checker.h
new file mode 100644
index 0000000..0887dd9c
--- /dev/null
+++ b/content/test/web_contents_observer_consistency_checker.h
@@ -0,0 +1,116 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_TEST_WEB_CONTENTS_OBSERVER_CONSISTENCY_CHECKER_H_
+#define CONTENT_TEST_WEB_CONTENTS_OBSERVER_CONSISTENCY_CHECKER_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/supports_user_data.h"
+#include "content/public/browser/global_routing_id.h"
+#include "content/public/browser/media_player_id.h"
+#include "content/public/browser/web_contents_observer.h"
+
+namespace content {
+
+// If your test framework enables a ContentBrowserConsistencyChecker, this
+// consistency checker is automatically installed on all WebContentses during
+// your test.
+//
+// WebContentsObserverConsistencyChecker is a WebContentsObserver that checks
+// the consistency of observer calls, and CHECK()s if they are inconsistent.
+// These checks are test-only code designed to find bugs in the implementation
+// of the content layer by validating the contract between WebContents and its
+// observers.
+//
+// For example, WebContentsObserver::RenderFrameCreated announces the existence
+// of a new RenderFrameHost, so that method call must occur before the
+// RenderFrameHost is referenced by some other WebContentsObserver method.
+class WebContentsObserverConsistencyChecker
+    : public WebContentsObserver,
+      public base::SupportsUserData::Data {
+ public:
+  ~WebContentsObserverConsistencyChecker() override;
+
+  // Enables these checks on |web_contents|. Usually
+  // ContentBrowserConsistencyChecker should call this for you.
+  static void Enable(WebContents* web_contents);
+
+  // WebContentsObserver implementation.
+  void RenderFrameCreated(RenderFrameHost* render_frame_host) override;
+  void RenderFrameDeleted(RenderFrameHost* render_frame_host) override;
+  void RenderFrameForInterstitialPageCreated(
+      RenderFrameHost* render_frame_host) override;
+  void RenderFrameHostChanged(RenderFrameHost* old_host,
+                              RenderFrameHost* new_host) override;
+  void FrameDeleted(RenderFrameHost* render_frame_host) override;
+  void DidStartNavigation(NavigationHandle* navigation_handle) override;
+  void DidRedirectNavigation(NavigationHandle* navigation_handle) override;
+  void ReadyToCommitNavigation(NavigationHandle* navigation_handle) override;
+  void DidFinishNavigation(NavigationHandle* navigation_handle) override;
+  void DocumentAvailableInMainFrame() override;
+  void DocumentOnLoadCompletedInMainFrame() override;
+  void DOMContentLoaded(RenderFrameHost* render_frame_host) override;
+  void DidFinishLoad(RenderFrameHost* render_frame_host,
+                     const GURL& validated_url) override;
+  void DidFailLoad(RenderFrameHost* render_frame_host,
+                   const GURL& validated_url,
+                   int error_code) override;
+  void DidOpenRequestedURL(WebContents* new_contents,
+                           RenderFrameHost* source_render_frame_host,
+                           const GURL& url,
+                           const Referrer& referrer,
+                           WindowOpenDisposition disposition,
+                           ui::PageTransition transition,
+                           bool started_from_context_menu,
+                           bool renderer_initiated) override;
+  void MediaStartedPlaying(const MediaPlayerInfo& media_info,
+                           const MediaPlayerId& id) override;
+  void MediaStoppedPlaying(
+      const MediaPlayerInfo& media_info,
+      const MediaPlayerId& id,
+      WebContentsObserver::MediaStoppedReason reason) override;
+  bool OnMessageReceived(const IPC::Message& message,
+                         RenderFrameHost* render_frame_host) override;
+  void WebContentsDestroyed() override;
+  void DidStartLoading() override;
+  void DidStopLoading() override;
+
+ private:
+  explicit WebContentsObserverConsistencyChecker(WebContents* web_contents);
+
+  std::string Format(RenderFrameHost* render_frame_host);
+  void AssertRenderFrameExists(RenderFrameHost* render_frame_host);
+  void AssertMainFrameExists();
+
+  bool NavigationIsOngoing(NavigationHandle* navigation_handle);
+
+  void EnsureStableParentValue(RenderFrameHost* render_frame_host);
+  bool HasAnyChildren(RenderFrameHost* render_frame_host);
+
+  std::map<int64_t, RenderFrameHost*> ready_to_commit_hosts_;
+  std::set<GlobalRoutingID> current_hosts_;
+  std::set<GlobalRoutingID> live_routes_;
+  std::set<GlobalRoutingID> deleted_routes_;
+
+  std::set<NavigationHandle*> ongoing_navigations_;
+  std::vector<MediaPlayerId> active_media_players_;
+
+  // Remembers parents to make sure RenderFrameHost::GetParent() never changes.
+  std::map<GlobalRoutingID, GlobalRoutingID> parent_ids_;
+
+  bool is_loading_;
+
+  bool web_contents_destroyed_;
+
+  DISALLOW_COPY_AND_ASSIGN(WebContentsObserverConsistencyChecker);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_TEST_WEB_CONTENTS_OBSERVER_CONSISTENCY_CHECKER_H_
diff --git a/content/test/web_contents_observer_sequence_checker.cc b/content/test/web_contents_observer_sequence_checker.cc
deleted file mode 100644
index 0824463..0000000
--- a/content/test/web_contents_observer_sequence_checker.cc
+++ /dev/null
@@ -1,415 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/test/web_contents_observer_sequence_checker.h"
-
-#include "base/memory/ptr_util.h"
-#include "base/stl_util.h"
-#include "base/strings/stringprintf.h"
-#include "build/build_config.h"
-#include "content/browser/frame_host/render_frame_host_impl.h"
-#include "content/common/content_navigation_policy.h"
-#include "content/common/frame_messages.h"
-#include "content/public/browser/navigation_handle.h"
-#include "content/public/browser/render_frame_host.h"
-#include "content/public/browser/render_process_host.h"
-#include "content/public/browser/site_instance.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/browser/web_contents_observer.h"
-#include "net/base/net_errors.h"
-
-namespace content {
-
-namespace {
-
-const char kWebContentsObserverSequenceCheckerKey[] =
-    "WebContentsObserverSequenceChecker";
-
-GlobalRoutingID GetRoutingPair(RenderFrameHost* host) {
-  if (!host)
-    return GlobalRoutingID(0, 0);
-  return GlobalRoutingID(host->GetProcess()->GetID(), host->GetRoutingID());
-}
-
-}  // namespace
-
-// static
-void WebContentsObserverSequenceChecker::Enable(WebContents* web_contents) {
-  if (web_contents->GetUserData(&kWebContentsObserverSequenceCheckerKey))
-    return;
-  web_contents->SetUserData(
-      &kWebContentsObserverSequenceCheckerKey,
-      base::WrapUnique(new WebContentsObserverSequenceChecker(web_contents)));
-}
-
-void WebContentsObserverSequenceChecker::RenderFrameCreated(
-    RenderFrameHost* render_frame_host) {
-  CHECK(!web_contents_destroyed_);
-  GlobalRoutingID routing_pair = GetRoutingPair(render_frame_host);
-  bool frame_exists = !live_routes_.insert(routing_pair).second;
-  deleted_routes_.erase(routing_pair);
-
-  if (frame_exists) {
-    CHECK(false) << "RenderFrameCreated called more than once for routing pair:"
-                 << Format(render_frame_host);
-  }
-
-  CHECK(render_frame_host->IsRenderFrameCreated())
-      << "RenderFrameCreated was called for a RenderFrameHost that has not been"
-         "marked created.";
-  CHECK(render_frame_host->GetProcess()->IsInitializedAndNotDead())
-      << "RenderFrameCreated was called for a RenderFrameHost whose render "
-         "process is not currently live, so there's no way for the RenderFrame "
-         "to have been created.";
-  CHECK(render_frame_host->IsRenderFrameLive())
-      << "RenderFrameCreated called on for a RenderFrameHost that thinks it is "
-         "not alive.";
-
-  EnsureStableParentValue(render_frame_host);
-  CHECK(!HasAnyChildren(render_frame_host));
-  if (render_frame_host->GetParent()) {
-    // It should also be a current host.
-    GlobalRoutingID parent_routing_pair =
-        GetRoutingPair(render_frame_host->GetParent());
-
-    CHECK(current_hosts_.count(parent_routing_pair))
-        << "RenderFrameCreated called for a RenderFrameHost whose parent was "
-        << "not a current RenderFrameHost. Only the current frame should be "
-        << "spawning children.";
-  }
-}
-
-void WebContentsObserverSequenceChecker::RenderFrameDeleted(
-    RenderFrameHost* render_frame_host) {
-  CHECK(!web_contents_destroyed_);
-  CHECK(!render_frame_host->IsRenderFrameCreated())
-      << "RenderFrameDeleted was called for a RenderFrameHost that is"
-         "(still) marked as created.";
-  CHECK(!render_frame_host->IsRenderFrameLive())
-      << "RenderFrameDeleted was called for a RenderFrameHost that is"
-         "still live.";
-
-  GlobalRoutingID routing_pair = GetRoutingPair(render_frame_host);
-  bool was_live = !!live_routes_.erase(routing_pair);
-  bool was_dead_already = !deleted_routes_.insert(routing_pair).second;
-
-  if (was_dead_already) {
-    CHECK(false) << "RenderFrameDeleted called more than once for routing pair "
-                 << Format(render_frame_host);
-  } else if (!was_live) {
-    CHECK(false) << "RenderFrameDeleted called for routing pair "
-                 << Format(render_frame_host)
-                 << " for which RenderFrameCreated was never called";
-  }
-
-  EnsureStableParentValue(render_frame_host);
-  CHECK(!HasAnyChildren(render_frame_host));
-  if (render_frame_host->GetParent())
-    AssertRenderFrameExists(render_frame_host->GetParent());
-
-  // All players should have been paused by this point.
-  for (const auto& id : active_media_players_)
-    CHECK_NE(id.render_frame_host, render_frame_host);
-}
-
-void WebContentsObserverSequenceChecker::RenderFrameForInterstitialPageCreated(
-    RenderFrameHost* render_frame_host) {
-  // TODO(nick): Record this.
-}
-
-void WebContentsObserverSequenceChecker::RenderFrameHostChanged(
-    RenderFrameHost* old_host,
-    RenderFrameHost* new_host) {
-  CHECK(new_host);
-  CHECK_NE(new_host, old_host);
-  CHECK(GetRoutingPair(old_host) != GetRoutingPair(new_host));
-
-  if (old_host) {
-    EnsureStableParentValue(old_host);
-    CHECK_EQ(old_host->GetParent(), new_host->GetParent());
-    GlobalRoutingID routing_pair = GetRoutingPair(old_host);
-    // If the navigation requires a new RFH, IsCurrent on old host should be
-    // false.
-    CHECK(!old_host->IsCurrent());
-    bool old_did_exist = !!current_hosts_.erase(routing_pair);
-    if (!old_did_exist) {
-      CHECK(false)
-          << "RenderFrameHostChanged called with old host that did not exist:"
-          << Format(old_host);
-    }
-  }
-
-  CHECK(new_host->IsCurrent());
-  EnsureStableParentValue(new_host);
-  if (new_host->GetParent()) {
-    AssertRenderFrameExists(new_host->GetParent());
-    CHECK(current_hosts_.count(GetRoutingPair(new_host->GetParent())))
-        << "Parent of frame being committed must be current.";
-  }
-
-  GlobalRoutingID routing_pair = GetRoutingPair(new_host);
-  bool host_exists = !current_hosts_.insert(routing_pair).second;
-  if (host_exists) {
-    CHECK(false)
-        << "RenderFrameHostChanged called more than once for routing pair:"
-        << Format(new_host);
-  }
-
-  // If |new_host| is restored from the BackForwardCache, it can contain
-  // iframes, otherwise it has just been created and can't contain iframes for
-  // the moment.
-  if (!IsBackForwardCacheEnabled()) {
-    CHECK(!HasAnyChildren(new_host))
-        << "A frame should not have children before it is committed.";
-  }
-}
-
-void WebContentsObserverSequenceChecker::FrameDeleted(
-    RenderFrameHost* render_frame_host) {
-  // A frame can be deleted before RenderFrame in the renderer process is
-  // created, so there is not much that can be enforced here.
-  CHECK(!web_contents_destroyed_);
-
-  EnsureStableParentValue(render_frame_host);
-
-  CHECK(!HasAnyChildren(render_frame_host))
-      << "All children should be deleted before a frame is detached.";
-
-  GlobalRoutingID routing_pair = GetRoutingPair(render_frame_host);
-  CHECK(current_hosts_.erase(routing_pair))
-      << "FrameDeleted called with a non-current RenderFrameHost.";
-
-  if (render_frame_host->GetParent())
-    AssertRenderFrameExists(render_frame_host->GetParent());
-}
-
-void WebContentsObserverSequenceChecker::DidStartNavigation(
-    NavigationHandle* navigation_handle) {
-  CHECK(!NavigationIsOngoing(navigation_handle));
-
-  CHECK(!navigation_handle->HasCommitted());
-  CHECK(!navigation_handle->IsErrorPage());
-  CHECK_EQ(navigation_handle->GetWebContents(), web_contents());
-
-  ongoing_navigations_.insert(navigation_handle);
-}
-
-void WebContentsObserverSequenceChecker::DidRedirectNavigation(
-    NavigationHandle* navigation_handle) {
-  CHECK(NavigationIsOngoing(navigation_handle));
-
-  CHECK(navigation_handle->GetNetErrorCode() == net::OK);
-  CHECK(!navigation_handle->HasCommitted());
-  CHECK(!navigation_handle->IsErrorPage());
-  CHECK_EQ(navigation_handle->GetWebContents(), web_contents());
-}
-
-void WebContentsObserverSequenceChecker::ReadyToCommitNavigation(
-    NavigationHandle* navigation_handle) {
-  CHECK(NavigationIsOngoing(navigation_handle));
-
-  CHECK(!navigation_handle->HasCommitted());
-  CHECK(navigation_handle->GetRenderFrameHost());
-  CHECK_EQ(navigation_handle->GetWebContents(), web_contents());
-  CHECK(navigation_handle->GetRenderFrameHost() != nullptr);
-
-  ready_to_commit_hosts_.insert(
-      std::make_pair(navigation_handle->GetNavigationId(),
-                     navigation_handle->GetRenderFrameHost()));
-}
-
-void WebContentsObserverSequenceChecker::DidFinishNavigation(
-    NavigationHandle* navigation_handle) {
-  CHECK(NavigationIsOngoing(navigation_handle));
-
-  CHECK(!(navigation_handle->HasCommitted() &&
-          !navigation_handle->IsErrorPage()) ||
-        navigation_handle->GetNetErrorCode() == net::OK);
-  CHECK_EQ(navigation_handle->GetWebContents(), web_contents());
-
-  CHECK(!navigation_handle->HasCommitted() ||
-        navigation_handle->GetRenderFrameHost() != nullptr);
-
-  CHECK(!navigation_handle->HasCommitted() ||
-        navigation_handle->GetRenderFrameHost()->IsCurrent());
-
-  // If ReadyToCommitNavigation was dispatched, verify that the
-  // |navigation_handle| has the same RenderFrameHost at this time as the one
-  // returned at ReadyToCommitNavigation.
-  if (base::Contains(ready_to_commit_hosts_,
-                     navigation_handle->GetNavigationId())) {
-    CHECK_EQ(ready_to_commit_hosts_[navigation_handle->GetNavigationId()],
-             navigation_handle->GetRenderFrameHost());
-    ready_to_commit_hosts_.erase(navigation_handle->GetNavigationId());
-  }
-
-  ongoing_navigations_.erase(navigation_handle);
-}
-
-void WebContentsObserverSequenceChecker::DocumentAvailableInMainFrame() {
-  AssertMainFrameExists();
-}
-
-void WebContentsObserverSequenceChecker::DocumentOnLoadCompletedInMainFrame() {
-  CHECK(web_contents()->IsDocumentOnLoadCompletedInMainFrame());
-  AssertMainFrameExists();
-}
-
-void WebContentsObserverSequenceChecker::DOMContentLoaded(
-    RenderFrameHost* render_frame_host) {
-  AssertRenderFrameExists(render_frame_host);
-}
-
-void WebContentsObserverSequenceChecker::DidFinishLoad(
-    RenderFrameHost* render_frame_host,
-    const GURL& validated_url) {
-  AssertRenderFrameExists(render_frame_host);
-}
-
-void WebContentsObserverSequenceChecker::DidFailLoad(
-    RenderFrameHost* render_frame_host,
-    const GURL& validated_url,
-    int error_code) {
-  AssertRenderFrameExists(render_frame_host);
-}
-
-void WebContentsObserverSequenceChecker::DidOpenRequestedURL(
-    WebContents* new_contents,
-    RenderFrameHost* source_render_frame_host,
-    const GURL& url,
-    const Referrer& referrer,
-    WindowOpenDisposition disposition,
-    ui::PageTransition transition,
-    bool started_from_context_menu,
-    bool renderer_initiated) {
-  AssertRenderFrameExists(source_render_frame_host);
-}
-
-void WebContentsObserverSequenceChecker::MediaStartedPlaying(
-    const MediaPlayerInfo& media_info,
-    const MediaPlayerId& id) {
-  CHECK(!web_contents_destroyed_);
-  CHECK(!base::Contains(active_media_players_, id));
-  active_media_players_.push_back(id);
-}
-
-void WebContentsObserverSequenceChecker::MediaStoppedPlaying(
-    const MediaPlayerInfo& media_info,
-    const MediaPlayerId& id,
-    WebContentsObserver::MediaStoppedReason reason) {
-  CHECK(!web_contents_destroyed_);
-  CHECK(base::Contains(active_media_players_, id));
-  base::Erase(active_media_players_, id);
-}
-
-bool WebContentsObserverSequenceChecker::OnMessageReceived(
-    const IPC::Message& message,
-    RenderFrameHost* render_frame_host) {
-  CHECK(render_frame_host->IsRenderFrameLive());
-
-  AssertRenderFrameExists(render_frame_host);
-  return false;
-}
-
-void WebContentsObserverSequenceChecker::WebContentsDestroyed() {
-  CHECK(!web_contents_destroyed_);
-  web_contents_destroyed_ = true;
-  CHECK(ongoing_navigations_.empty());
-  CHECK(active_media_players_.empty());
-  CHECK(live_routes_.empty());
-}
-
-void WebContentsObserverSequenceChecker::DidStartLoading() {
-  // TODO(clamy): add checks for the loading state in the rest of observer
-  // methods.
-  CHECK(!is_loading_);
-  CHECK(web_contents()->IsLoading());
-  is_loading_ = true;
-}
-
-void WebContentsObserverSequenceChecker::DidStopLoading() {
-  // TODO(crbug.com/466089): Add back CHECK(is_loading_). The CHECK was removed
-  // because of flaky failures during browser_test shutdown.
-  CHECK(!web_contents()->IsLoading());
-  is_loading_ = false;
-}
-
-WebContentsObserverSequenceChecker::WebContentsObserverSequenceChecker(
-    WebContents* web_contents)
-    : WebContentsObserver(web_contents),
-      is_loading_(false),
-      web_contents_destroyed_(false) {}
-
-WebContentsObserverSequenceChecker::~WebContentsObserverSequenceChecker() {
-  CHECK(web_contents_destroyed_);
-  CHECK(ready_to_commit_hosts_.empty());
-}
-
-void WebContentsObserverSequenceChecker::AssertRenderFrameExists(
-    RenderFrameHost* render_frame_host) {
-  CHECK(!web_contents_destroyed_);
-  GlobalRoutingID routing_pair = GetRoutingPair(render_frame_host);
-
-  bool render_frame_created_happened = live_routes_.count(routing_pair) != 0;
-  bool render_frame_deleted_happened = deleted_routes_.count(routing_pair) != 0;
-
-  CHECK(render_frame_created_happened)
-      << "A RenderFrameHost pointer was passed to a WebContentsObserver "
-      << "method, but WebContentsObserver::RenderFrameCreated was never called "
-      << "for that RenderFrameHost: " << Format(render_frame_host);
-  CHECK(!render_frame_deleted_happened)
-      << "A RenderFrameHost pointer was passed to a WebContentsObserver "
-      << "method, but WebContentsObserver::RenderFrameDeleted had already been "
-      << "called on that frame:" << Format(render_frame_host);
-}
-
-void WebContentsObserverSequenceChecker::AssertMainFrameExists() {
-  AssertRenderFrameExists(web_contents()->GetMainFrame());
-}
-
-std::string WebContentsObserverSequenceChecker::Format(
-    RenderFrameHost* render_frame_host) {
-  return base::StringPrintf(
-      "(%d, %d -> %s)", render_frame_host->GetProcess()->GetID(),
-      render_frame_host->GetRoutingID(),
-      render_frame_host->GetSiteInstance()->GetSiteURL().spec().c_str());
-}
-
-bool WebContentsObserverSequenceChecker::NavigationIsOngoing(
-    NavigationHandle* navigation_handle) {
-  auto it = ongoing_navigations_.find(navigation_handle);
-  return it != ongoing_navigations_.end();
-}
-
-void WebContentsObserverSequenceChecker::EnsureStableParentValue(
-    RenderFrameHost* render_frame_host) {
-  GlobalRoutingID routing_pair = GetRoutingPair(render_frame_host);
-  GlobalRoutingID parent_routing_pair =
-      GetRoutingPair(render_frame_host->GetParent());
-
-  auto it = parent_ids_.find(routing_pair);
-  if (it == parent_ids_.end()) {
-    parent_ids_.insert(std::make_pair(routing_pair, parent_routing_pair));
-  } else {
-    GlobalRoutingID former_parent_routing_pair = it->second;
-    CHECK(former_parent_routing_pair == parent_routing_pair)
-        << "RFH's parent value changed over time! That is really not good!";
-  }
-}
-
-bool WebContentsObserverSequenceChecker::HasAnyChildren(
-    RenderFrameHost* parent) {
-  GlobalRoutingID parent_routing_pair = GetRoutingPair(parent);
-  for (auto& entry : parent_ids_) {
-    if (entry.second == parent_routing_pair) {
-      if (live_routes_.count(entry.first))
-        return true;
-      if (current_hosts_.count(entry.first))
-        return true;
-    }
-  }
-  return false;
-}
-
-}  // namespace content
diff --git a/content/test/web_contents_observer_sequence_checker.h b/content/test/web_contents_observer_sequence_checker.h
deleted file mode 100644
index fbfa973..0000000
--- a/content/test/web_contents_observer_sequence_checker.h
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_TEST_WEB_CONTENTS_OBSERVER_SEQUENCE_CHECKER_H_
-#define CONTENT_TEST_WEB_CONTENTS_OBSERVER_SEQUENCE_CHECKER_H_
-
-#include <map>
-#include <set>
-#include <string>
-#include <vector>
-
-#include "base/macros.h"
-#include "base/supports_user_data.h"
-#include "content/public/browser/global_routing_id.h"
-#include "content/public/browser/media_player_id.h"
-#include "content/public/browser/web_contents_observer.h"
-
-namespace content {
-
-// If your test framework enables a ContentBrowserSequenceChecker, this sequence
-// checker is automatically installed on all WebContentses during your test.
-//
-// WebContentsObserverSequenceChecker is a WebContentsObserver that checks the
-// sequence of observer calls, and CHECK()s if they are inconsistent. These
-// checks are test-only code designed to find bugs in the implementation of the
-// content layer by validating the contract between WebContents and its
-// observers.
-//
-// For example, WebContentsObserver::RenderFrameCreated announces the existence
-// of a new RenderFrameHost, so that method call must occur before the
-// RenderFrameHost is referenced by some other WebContentsObserver method.
-class WebContentsObserverSequenceChecker : public WebContentsObserver,
-                                           public base::SupportsUserData::Data {
- public:
-  ~WebContentsObserverSequenceChecker() override;
-
-  // Enables these checks on |web_contents|. Usually
-  // ContentBrowserSequenceChecker should call this for you.
-  static void Enable(WebContents* web_contents);
-
-  // WebContentsObserver implementation.
-  void RenderFrameCreated(RenderFrameHost* render_frame_host) override;
-  void RenderFrameDeleted(RenderFrameHost* render_frame_host) override;
-  void RenderFrameForInterstitialPageCreated(
-      RenderFrameHost* render_frame_host) override;
-  void RenderFrameHostChanged(RenderFrameHost* old_host,
-                              RenderFrameHost* new_host) override;
-  void FrameDeleted(RenderFrameHost* render_frame_host) override;
-  void DidStartNavigation(NavigationHandle* navigation_handle) override;
-  void DidRedirectNavigation(NavigationHandle* navigation_handle) override;
-  void ReadyToCommitNavigation(NavigationHandle* navigation_handle) override;
-  void DidFinishNavigation(NavigationHandle* navigation_handle) override;
-  void DocumentAvailableInMainFrame() override;
-  void DocumentOnLoadCompletedInMainFrame() override;
-  void DOMContentLoaded(RenderFrameHost* render_frame_host) override;
-  void DidFinishLoad(RenderFrameHost* render_frame_host,
-                     const GURL& validated_url) override;
-  void DidFailLoad(RenderFrameHost* render_frame_host,
-                   const GURL& validated_url,
-                   int error_code) override;
-  void DidOpenRequestedURL(WebContents* new_contents,
-                           RenderFrameHost* source_render_frame_host,
-                           const GURL& url,
-                           const Referrer& referrer,
-                           WindowOpenDisposition disposition,
-                           ui::PageTransition transition,
-                           bool started_from_context_menu,
-                           bool renderer_initiated) override;
-  void MediaStartedPlaying(const MediaPlayerInfo& media_info,
-                           const MediaPlayerId& id) override;
-  void MediaStoppedPlaying(
-      const MediaPlayerInfo& media_info,
-      const MediaPlayerId& id,
-      WebContentsObserver::MediaStoppedReason reason) override;
-  bool OnMessageReceived(const IPC::Message& message,
-                         RenderFrameHost* render_frame_host) override;
-  void WebContentsDestroyed() override;
-  void DidStartLoading() override;
-  void DidStopLoading() override;
-
- private:
-  explicit WebContentsObserverSequenceChecker(WebContents* web_contents);
-
-  std::string Format(RenderFrameHost* render_frame_host);
-  void AssertRenderFrameExists(RenderFrameHost* render_frame_host);
-  void AssertMainFrameExists();
-
-  bool NavigationIsOngoing(NavigationHandle* navigation_handle);
-
-  void EnsureStableParentValue(RenderFrameHost* render_frame_host);
-  bool HasAnyChildren(RenderFrameHost* render_frame_host);
-
-  std::map<int64_t, RenderFrameHost*> ready_to_commit_hosts_;
-  std::set<GlobalRoutingID> current_hosts_;
-  std::set<GlobalRoutingID> live_routes_;
-  std::set<GlobalRoutingID> deleted_routes_;
-
-  std::set<NavigationHandle*> ongoing_navigations_;
-  std::vector<MediaPlayerId> active_media_players_;
-
-  // Remembers parents to make sure RenderFrameHost::GetParent() never changes.
-  std::map<GlobalRoutingID, GlobalRoutingID> parent_ids_;
-
-  bool is_loading_;
-
-  bool web_contents_destroyed_;
-
-  DISALLOW_COPY_AND_ASSIGN(WebContentsObserverSequenceChecker);
-};
-
-}  // namespace content
-
-#endif  // CONTENT_TEST_WEB_CONTENTS_OBSERVER_SEQUENCE_CHECKER_H_
diff --git a/device/bluetooth/BUILD.gn b/device/bluetooth/BUILD.gn
index f228f33..951b0cb 100644
--- a/device/bluetooth/BUILD.gn
+++ b/device/bluetooth/BUILD.gn
@@ -6,6 +6,11 @@
 if (is_android) {
   import("//build/config/android/rules.gni")  # For generate_jni().
 }
+if (is_chromeos) {
+  import("//chromeos/dbus/use_real_dbus_clients.gni")
+} else if (use_dbus) {
+  use_real_dbus_clients = false
+}
 
 config("bluetooth_config") {
   if (is_win) {
@@ -370,42 +375,6 @@
         "dbus/bluez_dbus_client.h",
         "dbus/bluez_dbus_manager.cc",
         "dbus/bluez_dbus_manager.h",
-        "dbus/fake_bluetooth_adapter_client.cc",
-        "dbus/fake_bluetooth_adapter_client.h",
-        "dbus/fake_bluetooth_agent_manager_client.cc",
-        "dbus/fake_bluetooth_agent_manager_client.h",
-        "dbus/fake_bluetooth_agent_service_provider.cc",
-        "dbus/fake_bluetooth_agent_service_provider.h",
-        "dbus/fake_bluetooth_debug_manager_client.cc",
-        "dbus/fake_bluetooth_debug_manager_client.h",
-        "dbus/fake_bluetooth_device_client.cc",
-        "dbus/fake_bluetooth_device_client.h",
-        "dbus/fake_bluetooth_gatt_application_service_provider.cc",
-        "dbus/fake_bluetooth_gatt_application_service_provider.h",
-        "dbus/fake_bluetooth_gatt_characteristic_client.cc",
-        "dbus/fake_bluetooth_gatt_characteristic_client.h",
-        "dbus/fake_bluetooth_gatt_characteristic_service_provider.cc",
-        "dbus/fake_bluetooth_gatt_characteristic_service_provider.h",
-        "dbus/fake_bluetooth_gatt_descriptor_client.cc",
-        "dbus/fake_bluetooth_gatt_descriptor_client.h",
-        "dbus/fake_bluetooth_gatt_descriptor_service_provider.cc",
-        "dbus/fake_bluetooth_gatt_descriptor_service_provider.h",
-        "dbus/fake_bluetooth_gatt_manager_client.cc",
-        "dbus/fake_bluetooth_gatt_manager_client.h",
-        "dbus/fake_bluetooth_gatt_service_client.cc",
-        "dbus/fake_bluetooth_gatt_service_client.h",
-        "dbus/fake_bluetooth_gatt_service_service_provider.cc",
-        "dbus/fake_bluetooth_gatt_service_service_provider.h",
-        "dbus/fake_bluetooth_input_client.cc",
-        "dbus/fake_bluetooth_input_client.h",
-        "dbus/fake_bluetooth_le_advertisement_service_provider.cc",
-        "dbus/fake_bluetooth_le_advertisement_service_provider.h",
-        "dbus/fake_bluetooth_le_advertising_manager_client.cc",
-        "dbus/fake_bluetooth_le_advertising_manager_client.h",
-        "dbus/fake_bluetooth_profile_manager_client.cc",
-        "dbus/fake_bluetooth_profile_manager_client.h",
-        "dbus/fake_bluetooth_profile_service_provider.cc",
-        "dbus/fake_bluetooth_profile_service_provider.h",
       ]
       if (is_linux) {
         sources += [
@@ -416,12 +385,53 @@
         ]
       }
       if (is_chromeos) {
+        configs += [ "//chromeos/dbus:use_real_dbus_clients_config" ]
         sources += [
           "chromeos/bluetooth_utils.cc",
           "chromeos/bluetooth_utils.h",
         ]
         deps += [ "//services/data_decoder/public/mojom" ]
       }
+      if (!use_real_dbus_clients) {
+        sources += [
+          "dbus/fake_bluetooth_adapter_client.cc",
+          "dbus/fake_bluetooth_adapter_client.h",
+          "dbus/fake_bluetooth_agent_manager_client.cc",
+          "dbus/fake_bluetooth_agent_manager_client.h",
+          "dbus/fake_bluetooth_agent_service_provider.cc",
+          "dbus/fake_bluetooth_agent_service_provider.h",
+          "dbus/fake_bluetooth_debug_manager_client.cc",
+          "dbus/fake_bluetooth_debug_manager_client.h",
+          "dbus/fake_bluetooth_device_client.cc",
+          "dbus/fake_bluetooth_device_client.h",
+          "dbus/fake_bluetooth_gatt_application_service_provider.cc",
+          "dbus/fake_bluetooth_gatt_application_service_provider.h",
+          "dbus/fake_bluetooth_gatt_characteristic_client.cc",
+          "dbus/fake_bluetooth_gatt_characteristic_client.h",
+          "dbus/fake_bluetooth_gatt_characteristic_service_provider.cc",
+          "dbus/fake_bluetooth_gatt_characteristic_service_provider.h",
+          "dbus/fake_bluetooth_gatt_descriptor_client.cc",
+          "dbus/fake_bluetooth_gatt_descriptor_client.h",
+          "dbus/fake_bluetooth_gatt_descriptor_service_provider.cc",
+          "dbus/fake_bluetooth_gatt_descriptor_service_provider.h",
+          "dbus/fake_bluetooth_gatt_manager_client.cc",
+          "dbus/fake_bluetooth_gatt_manager_client.h",
+          "dbus/fake_bluetooth_gatt_service_client.cc",
+          "dbus/fake_bluetooth_gatt_service_client.h",
+          "dbus/fake_bluetooth_gatt_service_service_provider.cc",
+          "dbus/fake_bluetooth_gatt_service_service_provider.h",
+          "dbus/fake_bluetooth_input_client.cc",
+          "dbus/fake_bluetooth_input_client.h",
+          "dbus/fake_bluetooth_le_advertisement_service_provider.cc",
+          "dbus/fake_bluetooth_le_advertisement_service_provider.h",
+          "dbus/fake_bluetooth_le_advertising_manager_client.cc",
+          "dbus/fake_bluetooth_le_advertising_manager_client.h",
+          "dbus/fake_bluetooth_profile_manager_client.cc",
+          "dbus/fake_bluetooth_profile_manager_client.h",
+          "dbus/fake_bluetooth_profile_service_provider.cc",
+          "dbus/fake_bluetooth_profile_service_provider.h",
+        ]
+      }
       deps += [ "//dbus" ]
     } else {  # !use_dbus
       if (is_chromecast && is_linux) {
diff --git a/device/bluetooth/bluetooth_adapter_unittest.cc b/device/bluetooth/bluetooth_adapter_unittest.cc
index 329c8fb..6f688a2 100644
--- a/device/bluetooth/bluetooth_adapter_unittest.cc
+++ b/device/bluetooth/bluetooth_adapter_unittest.cc
@@ -128,24 +128,22 @@
     return nullptr;
   }
 
-  void TestErrorCallback() {}
-
   void OnStartDiscoverySessionQuitLoop(
-      base::Closure run_loop_quit,
+      base::OnceClosure run_loop_quit,
       std::unique_ptr<device::BluetoothDiscoverySession> discovery_session) {
     ++callback_count_;
-    run_loop_quit.Run();
+    std::move(run_loop_quit).Run();
     discovery_sessions_holder_.push(std::move(discovery_session));
   }
 
-  void OnRemoveDiscoverySession(base::Closure run_loop_quit) {
+  void OnRemoveDiscoverySession(base::OnceClosure run_loop_quit) {
     ++callback_count_;
-    run_loop_quit.Run();
+    std::move(run_loop_quit).Run();
   }
 
-  void OnRemoveDiscoverySessionError(base::Closure run_loop_quit) {
+  void OnRemoveDiscoverySessionError(base::OnceClosure run_loop_quit) {
     ++callback_count_;
-    run_loop_quit.Run();
+    std::move(run_loop_quit).Run();
   }
 
   void set_discovery_session_outcome(
@@ -153,32 +151,28 @@
     discovery_session_outcome_ = outcome;
   }
 
-  void StopDiscoverySession(base::Closure run_loop_quit) {
+  void StopDiscoverySession(base::OnceClosure run_loop_quit) {
+    auto copyable_callback =
+        base::AdaptCallbackForRepeating(std::move(run_loop_quit));
     discovery_sessions_holder_.front()->Stop(
-        base::BindRepeating(&TestBluetoothAdapter::OnRemoveDiscoverySession,
-                            this, run_loop_quit),
-        base::BindRepeating(
-            &TestBluetoothAdapter::OnRemoveDiscoverySessionError, this,
-            run_loop_quit));
+        base::BindOnce(&TestBluetoothAdapter::OnRemoveDiscoverySession, this,
+                       copyable_callback),
+        base::BindOnce(&TestBluetoothAdapter::OnRemoveDiscoverySessionError,
+                       this, copyable_callback));
     discovery_sessions_holder_.pop();
   }
 
-  void StopAllDiscoverySessions(base::Closure run_loop_quit) {
-    int num_stop_requests = discovery_sessions_holder_.size();
+  void StopAllDiscoverySessions(base::OnceClosure run_loop_quit) {
+    base::RepeatingClosure closure = base::BarrierClosure(
+        discovery_sessions_holder_.size(), std::move(run_loop_quit));
     while (!discovery_sessions_holder_.empty()) {
       discovery_sessions_holder_.front()->Stop(
-          base::BindLambdaForTesting(
-              [run_loop_quit, num_stop_requests, this]() {
-                num_requests_returned_++;
-                ++callback_count_;
-                if (num_requests_returned_ == num_stop_requests) {
-                  num_requests_returned_ = 0;
-                  run_loop_quit.Run();
-                }
-              }),
-          base::BindRepeating(
-              &TestBluetoothAdapter::OnRemoveDiscoverySessionError, this,
-              run_loop_quit));
+          base::BindLambdaForTesting([closure, this]() {
+            ++callback_count_;
+            closure.Run();
+          }),
+          base::BindOnce(&TestBluetoothAdapter::OnRemoveDiscoverySessionError,
+                         this, closure));
       discovery_sessions_holder_.pop();
     }
   }
@@ -189,32 +183,29 @@
     std::swap(discovery_sessions_holder_, empty_queue);
   }
 
-  void QueueStartRequests(base::Closure run_loop_quit, int num_requests) {
+  void QueueStartRequests(base::OnceClosure run_loop_quit, int num_requests) {
+    base::RepeatingClosure closure =
+        base::BarrierClosure(num_requests, std::move(run_loop_quit));
     for (int i = 0; i < num_requests; ++i) {
       StartDiscoverySession(
           base::BindLambdaForTesting(
-              [run_loop_quit, num_requests,
-               this](std::unique_ptr<device::BluetoothDiscoverySession>
-                         discovery_session) {
+              [closure, this](std::unique_ptr<device::BluetoothDiscoverySession>
+                                  discovery_session) {
                 ++callback_count_;
-                num_requests_returned_++;
                 discovery_sessions_holder_.push(std::move(discovery_session));
-                if (num_requests_returned_ == num_requests) {
-                  num_requests_returned_ = 0;
-                  run_loop_quit.Run();
-                }
+                closure.Run();
               }),
-          base::BindOnce(&TestBluetoothAdapter::TestErrorCallback, this));
-    };
+          closure);
+    }
   }
 
   void StartSessionWithFilter(
       std::unique_ptr<BluetoothDiscoveryFilter> discovery_filter,
-      base::RepeatingClosure run_loop_quit) {
+      base::OnceClosure run_loop_quit) {
     StartDiscoverySessionWithFilter(
         std::move(discovery_filter),
         base::BindOnce(&TestBluetoothAdapter::OnStartDiscoverySessionQuitLoop,
-                       this, run_loop_quit),
+                       this, std::move(run_loop_quit)),
         base::DoNothing());
   }
 
@@ -290,8 +281,6 @@
   void RemovePairingDelegateInternal(
       BluetoothDevice::PairingDelegate* pairing_delegate) override {}
 
-  int num_requests_returned_ = 0;
-
  private:
   void PostDelayedTask(base::OnceClosure callback) {
     base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
diff --git a/device/bluetooth/bluetooth_adapter_win_unittest.cc b/device/bluetooth/bluetooth_adapter_win_unittest.cc
index 97bbdb5..d5f61585 100644
--- a/device/bluetooth/bluetooth_adapter_win_unittest.cc
+++ b/device/bluetooth/bluetooth_adapter_win_unittest.cc
@@ -79,10 +79,10 @@
       active_discovery_sessions_;
 
   void DiscoverySessionCallbackPassthrough(
-      const base::RepeatingClosure& callback,
+      base::OnceClosure callback,
       std::unique_ptr<BluetoothDiscoverySession> new_session) {
     active_discovery_sessions_.push(std::move(new_session));
-    callback.Run();
+    std::move(callback).Run();
   }
 
   void IncrementNumStartDiscoveryCallbacks() {
@@ -107,7 +107,7 @@
   typedef base::OnceCallback<void(UMABluetoothDiscoverySessionOutcome)>
       DiscoverySessionErrorCallback;
 
-  using ErrorCallback = base::RepeatingClosure;
+  using ErrorCallback = base::OnceClosure;
 
   void CallStartDiscoverySession() {
     adapter_win_->StartDiscoverySession(
@@ -122,9 +122,10 @@
             base::Unretained(this)));
   }
 
-  void StopTopDiscoverySession(const base::RepeatingClosure& callback,
+  void StopTopDiscoverySession(base::OnceClosure callback,
                                ErrorCallback error_callback) {
-    active_discovery_sessions_.front()->Stop(callback, error_callback);
+    active_discovery_sessions_.front()->Stop(std::move(callback),
+                                             std::move(error_callback));
     active_discovery_sessions_.pop();
   }
 
diff --git a/device/bluetooth/bluetooth_device_unittest.cc b/device/bluetooth/bluetooth_device_unittest.cc
index d46bfd8..f5b86d5 100644
--- a/device/bluetooth/bluetooth_device_unittest.cc
+++ b/device/bluetooth/bluetooth_device_unittest.cc
@@ -147,7 +147,7 @@
 
   SimulatePairingPinCode(device, "123456");
   TestPairingDelegate pairing_delegate;
-  device->Pair(&pairing_delegate, GetOnceCallback(Call::EXPECTED),
+  device->Pair(&pairing_delegate, GetCallback(Call::EXPECTED),
                GetConnectErrorCallback(Call::NOT_EXPECTED));
   base::RunLoop().RunUntilIdle();
 
@@ -178,7 +178,7 @@
 
   SimulatePairingPinCode(device, "123456");
   TestPairingDelegate pairing_delegate;
-  device->Pair(&pairing_delegate, GetOnceCallback(Call::NOT_EXPECTED),
+  device->Pair(&pairing_delegate, GetCallback(Call::NOT_EXPECTED),
                GetConnectErrorCallback(Call::EXPECTED));
   base::RunLoop().RunUntilIdle();
 
@@ -210,7 +210,7 @@
 
   SimulatePairingPinCode(device, "123456");
   TestPairingDelegate pairing_delegate;
-  device->Pair(&pairing_delegate, GetOnceCallback(Call::NOT_EXPECTED),
+  device->Pair(&pairing_delegate, GetCallback(Call::NOT_EXPECTED),
                GetConnectErrorCallback(Call::EXPECTED));
   base::RunLoop().RunUntilIdle();
 
@@ -242,7 +242,7 @@
 
   SimulatePairingPinCode(device, "123456");
   TestPairingDelegate pairing_delegate;
-  device->Pair(&pairing_delegate, GetOnceCallback(Call::NOT_EXPECTED),
+  device->Pair(&pairing_delegate, GetCallback(Call::NOT_EXPECTED),
                GetConnectErrorCallback(Call::EXPECTED));
   base::RunLoop().RunUntilIdle();
 
diff --git a/device/bluetooth/bluetooth_socket.h b/device/bluetooth/bluetooth_socket.h
index bc7f311..b80ee37 100644
--- a/device/bluetooth/bluetooth_socket.h
+++ b/device/bluetooth/bluetooth_socket.h
@@ -32,18 +32,16 @@
  public:
   enum ErrorReason { kSystemError, kIOPending, kDisconnected };
 
-  typedef base::Callback<void(int)> SendCompletionCallback;
-  typedef base::Callback<void(int, scoped_refptr<net::IOBuffer> io_buffer)>
-      ReceiveCompletionCallback;
-  typedef base::Callback<void(const BluetoothDevice* device,
-                              scoped_refptr<BluetoothSocket> socket)>
-      AcceptCompletionCallback;
-  typedef base::Callback<void(const std::string& error_message)>
-      ErrorCompletionCallback;
-  using ErrorCompletionOnceCallback =
+  using SendCompletionCallback = base::OnceCallback<void(int)>;
+  using ReceiveCompletionCallback =
+      base::OnceCallback<void(int, scoped_refptr<net::IOBuffer> io_buffer)>;
+  using AcceptCompletionCallback =
+      base::OnceCallback<void(const BluetoothDevice* device,
+                              scoped_refptr<BluetoothSocket> socket)>;
+  using ErrorCompletionCallback =
       base::OnceCallback<void(const std::string& error_message)>;
-  typedef base::Callback<void(ErrorReason, const std::string& error_message)>
-      ReceiveErrorCompletionCallback;
+  using ReceiveErrorCompletionCallback =
+      base::OnceCallback<void(ErrorReason, const std::string& error_message)>;
 
   // Destroys resources associated with the socket. After calling this method,
   // it is illegal to call any method on this socket instance (except for the
@@ -52,31 +50,30 @@
 
   // Gracefully disconnects the socket and calls |callback| upon completion.
   // There is no failure case, as this is a best effort operation.
-  virtual void Disconnect(const base::Closure& success_callback) = 0;
+  virtual void Disconnect(base::OnceClosure success_callback) = 0;
 
   // Receives data from the socket and calls |success_callback| when data is
   // available. |buffer_size| specifies the maximum number of bytes that can be
   // received. If an error occurs, calls |error_callback| with a reason and an
   // error message.
-  virtual void Receive(
-      int buffer_size,
-      const ReceiveCompletionCallback& success_callback,
-      const ReceiveErrorCompletionCallback& error_callback) = 0;
+  virtual void Receive(int buffer_size,
+                       ReceiveCompletionCallback success_callback,
+                       ReceiveErrorCompletionCallback error_callback) = 0;
 
   // Sends |buffer| to the socket and calls |success_callback| when data has
   // been successfully sent. |buffer_size| is the number of bytes contained in
   // |buffer|. If an error occurs, calls |error_callback| with an error message.
   virtual void Send(scoped_refptr<net::IOBuffer> buffer,
                     int buffer_size,
-                    const SendCompletionCallback& success_callback,
-                    const ErrorCompletionCallback& error_callback) = 0;
+                    SendCompletionCallback success_callback,
+                    ErrorCompletionCallback error_callback) = 0;
 
   // Accepts a pending client connection from the socket and calls
   // |success_callback| on completion, passing a new BluetoothSocket instance
   // for the new client. If an error occurs, calls |error_callback| with a
   // reason and an error message.
-  virtual void Accept(const AcceptCompletionCallback& success_callback,
-                      const ErrorCompletionCallback& error_callback) = 0;
+  virtual void Accept(AcceptCompletionCallback success_callback,
+                      ErrorCompletionCallback error_callback) = 0;
 
  protected:
   friend class base::RefCountedThreadSafe<BluetoothSocket>;
diff --git a/device/bluetooth/bluetooth_socket_mac.h b/device/bluetooth/bluetooth_socket_mac.h
index 4966dc67..b77b3b5 100644
--- a/device/bluetooth/bluetooth_socket_mac.h
+++ b/device/bluetooth/bluetooth_socket_mac.h
@@ -47,7 +47,7 @@
   void Connect(IOBluetoothDevice* device,
                const BluetoothUUID& uuid,
                base::OnceClosure success_callback,
-               ErrorCompletionOnceCallback error_callback);
+               ErrorCompletionCallback error_callback);
 
   // Listens for incoming RFCOMM connections using this socket: Publishes an
   // RFCOMM service on the |adapter| as UUID |uuid| with Channel
@@ -60,7 +60,7 @@
                          const BluetoothUUID& uuid,
                          const BluetoothAdapter::ServiceOptions& options,
                          base::OnceClosure success_callback,
-                         ErrorCompletionOnceCallback error_callback);
+                         ErrorCompletionCallback error_callback);
 
   // Listens for incoming L2CAP connections using this socket: Publishes an
   // L2CAP service on the |adapter| as UUID |uuid| with PSM |options.psm|, or an
@@ -73,20 +73,20 @@
                         const BluetoothUUID& uuid,
                         const BluetoothAdapter::ServiceOptions& options,
                         base::OnceClosure success_callback,
-                        ErrorCompletionOnceCallback error_callback);
+                        ErrorCompletionCallback error_callback);
 
   // BluetoothSocket:
   void Close() override;
-  void Disconnect(const base::Closure& callback) override;
+  void Disconnect(base::OnceClosure callback) override;
   void Receive(int /* buffer_size */,
-               const ReceiveCompletionCallback& success_callback,
-               const ReceiveErrorCompletionCallback& error_callback) override;
+               ReceiveCompletionCallback success_callback,
+               ReceiveErrorCompletionCallback error_callback) override;
   void Send(scoped_refptr<net::IOBuffer> buffer,
             int buffer_size,
-            const SendCompletionCallback& success_callback,
-            const ErrorCompletionCallback& error_callback) override;
-  void Accept(const AcceptCompletionCallback& success_callback,
-              const ErrorCompletionCallback& error_callback) override;
+            SendCompletionCallback success_callback,
+            ErrorCompletionCallback error_callback) override;
+  void Accept(AcceptCompletionCallback success_callback,
+              ErrorCompletionCallback error_callback) override;
 
   // Callback that is invoked when the OS completes an SDP query.
   // |status| is the returned status from the SDP query, |device| is the
@@ -95,7 +95,7 @@
   void OnSDPQueryComplete(IOReturn status,
                           IOBluetoothDevice* device,
                           base::OnceClosure success_callback,
-                          ErrorCompletionOnceCallback error_callback);
+                          ErrorCompletionCallback error_callback);
 
   // Called by BluetoothRfcommConnectionListener and
   // BluetoothL2capConnectionListener.
@@ -140,7 +140,7 @@
     ConnectCallbacks();
     ~ConnectCallbacks();
     base::OnceClosure success_callback;
-    ErrorCompletionOnceCallback error_callback;
+    ErrorCompletionCallback error_callback;
   };
 
   BluetoothSocketMac();
diff --git a/device/bluetooth/bluetooth_socket_mac.mm b/device/bluetooth/bluetooth_socket_mac.mm
index 350b8106..b75b8e2 100644
--- a/device/bluetooth/bluetooth_socket_mac.mm
+++ b/device/bluetooth/bluetooth_socket_mac.mm
@@ -44,7 +44,7 @@
 
   // Callbacks associated with the request that triggered this SDP query.
   base::OnceClosure _success_callback;
-  BluetoothSocket::ErrorCompletionOnceCallback _error_callback;
+  BluetoothSocket::ErrorCompletionCallback _error_callback;
 
   // The device being queried.
   IOBluetoothDevice* _device;  // weak
@@ -53,8 +53,7 @@
 - (id)initWithSocket:(scoped_refptr<device::BluetoothSocketMac>)socket
               device:(IOBluetoothDevice*)device
     success_callback:(base::OnceClosure)success_callback
-      error_callback:
-          (BluetoothSocket::ErrorCompletionOnceCallback)error_callback;
+      error_callback:(BluetoothSocket::ErrorCompletionCallback)error_callback;
 - (void)sdpQueryComplete:(IOBluetoothDevice*)device status:(IOReturn)status;
 
 @end
@@ -64,8 +63,7 @@
 - (id)initWithSocket:(scoped_refptr<device::BluetoothSocketMac>)socket
               device:(IOBluetoothDevice*)device
     success_callback:(base::OnceClosure)success_callback
-      error_callback:
-          (BluetoothSocket::ErrorCompletionOnceCallback)error_callback {
+      error_callback:(BluetoothSocket::ErrorCompletionCallback)error_callback {
   if ((self = [super init])) {
     _socket = socket;
     _device = device;
@@ -322,7 +320,7 @@
 // to-be-registered service was not configured correctly, returns nil.
 IOBluetoothSDPServiceRecord* RegisterService(
     NSDictionary* service_definition,
-    const base::Callback<bool(IOBluetoothSDPServiceRecord*)>&
+    base::OnceCallback<bool(IOBluetoothSDPServiceRecord*)>
         verify_service_callback) {
   // Attempt to register the service.
   IOBluetoothSDPServiceRecord* service_record = [IOBluetoothSDPServiceRecord
@@ -330,7 +328,8 @@
 
   // Verify that the registered service was configured correctly. If not,
   // withdraw the service.
-  if (!service_record || !verify_service_callback.Run(service_record)) {
+  if (!service_record ||
+      !std::move(verify_service_callback).Run(service_record)) {
     [service_record removeServiceRecord];
     service_record = nil;
   }
@@ -370,8 +369,8 @@
     BluetoothRFCOMMChannelID* registered_channel_id) {
   return RegisterService(
       BuildRfcommServiceDefinition(uuid, options),
-      base::Bind(
-          &VerifyRfcommService, options.channel.get(), registered_channel_id));
+      base::BindOnce(&VerifyRfcommService, options.channel.get(),
+                     registered_channel_id));
 }
 
 // Returns true iff the |requested_psm| was registered in the L2CAP
@@ -418,7 +417,7 @@
 void BluetoothSocketMac::Connect(IOBluetoothDevice* device,
                                  const BluetoothUUID& uuid,
                                  base::OnceClosure success_callback,
-                                 ErrorCompletionOnceCallback error_callback) {
+                                 ErrorCompletionCallback error_callback) {
   DCHECK(thread_checker_.CalledOnValidThread());
 
   uuid_ = uuid;
@@ -442,7 +441,7 @@
     const BluetoothUUID& uuid,
     const BluetoothAdapter::ServiceOptions& options,
     base::OnceClosure success_callback,
-    ErrorCompletionOnceCallback error_callback) {
+    ErrorCompletionCallback error_callback) {
   DCHECK(thread_checker_.CalledOnValidThread());
 
   adapter_ = adapter;
@@ -470,7 +469,7 @@
     const BluetoothUUID& uuid,
     const BluetoothAdapter::ServiceOptions& options,
     base::OnceClosure success_callback,
-    ErrorCompletionOnceCallback error_callback) {
+    ErrorCompletionCallback error_callback) {
   DCHECK(thread_checker_.CalledOnValidThread());
 
   adapter_ = adapter;
@@ -495,7 +494,7 @@
     IOReturn status,
     IOBluetoothDevice* device,
     base::OnceClosure success_callback,
-    ErrorCompletionOnceCallback error_callback) {
+    ErrorCompletionCallback error_callback) {
   DCHECK(thread_checker_.CalledOnValidThread());
   DVLOG(1) << BluetoothClassicDeviceMac::GetDeviceAddress(device) << " "
            << uuid_.canonical_value() << ": SDP query complete.";
@@ -628,32 +627,34 @@
     ReleaseListener();
 }
 
-void BluetoothSocketMac::Disconnect(const base::Closure& callback) {
+void BluetoothSocketMac::Disconnect(base::OnceClosure callback) {
   DCHECK(thread_checker_.CalledOnValidThread());
 
   Close();
-  callback.Run();
+  std::move(callback).Run();
 }
 
 void BluetoothSocketMac::Receive(
     int /* buffer_size */,
-    const ReceiveCompletionCallback& success_callback,
-    const ReceiveErrorCompletionCallback& error_callback) {
+    ReceiveCompletionCallback success_callback,
+    ReceiveErrorCompletionCallback error_callback) {
   DCHECK(thread_checker_.CalledOnValidThread());
 
   if (is_connecting()) {
-    error_callback.Run(BluetoothSocket::kSystemError, kSocketConnecting);
+    std::move(error_callback)
+        .Run(BluetoothSocket::kSystemError, kSocketConnecting);
     return;
   }
 
   if (!channel_) {
-    error_callback.Run(BluetoothSocket::kDisconnected, kSocketNotConnected);
+    std::move(error_callback)
+        .Run(BluetoothSocket::kDisconnected, kSocketNotConnected);
     return;
   }
 
   // Only one pending read at a time
   if (receive_callbacks_) {
-    error_callback.Run(BluetoothSocket::kIOPending, kReceivePending);
+    std::move(error_callback).Run(BluetoothSocket::kIOPending, kReceivePending);
     return;
   }
 
@@ -661,14 +662,14 @@
   if (!receive_queue_.empty()) {
     scoped_refptr<net::IOBufferWithSize> buffer = receive_queue_.front();
     receive_queue_.pop();
-    success_callback.Run(buffer->size(), buffer);
+    std::move(success_callback).Run(buffer->size(), buffer);
     return;
   }
 
   // Set the receive callback to use when data is received.
   receive_callbacks_.reset(new ReceiveCallbacks());
-  receive_callbacks_->success_callback = success_callback;
-  receive_callbacks_->error_callback = error_callback;
+  receive_callbacks_->success_callback = std::move(success_callback);
+  receive_callbacks_->error_callback = std::move(error_callback);
 }
 
 void BluetoothSocketMac::OnChannelDataReceived(void* data, size_t length) {
@@ -682,7 +683,7 @@
   // If there is a pending read callback, call it now.
   if (receive_callbacks_) {
     std::unique_ptr<ReceiveCallbacks> temp = std::move(receive_callbacks_);
-    temp->success_callback.Run(buffer->size(), buffer);
+    std::move(temp->success_callback).Run(buffer->size(), buffer);
     return;
   }
 
@@ -692,17 +693,17 @@
 
 void BluetoothSocketMac::Send(scoped_refptr<net::IOBuffer> buffer,
                               int buffer_size,
-                              const SendCompletionCallback& success_callback,
-                              const ErrorCompletionCallback& error_callback) {
+                              SendCompletionCallback success_callback,
+                              ErrorCompletionCallback error_callback) {
   DCHECK(thread_checker_.CalledOnValidThread());
 
   if (is_connecting()) {
-    error_callback.Run(kSocketConnecting);
+    std::move(error_callback).Run(kSocketConnecting);
     return;
   }
 
   if (!channel_) {
-    error_callback.Run(kSocketNotConnected);
+    std::move(error_callback).Run(kSocketNotConnected);
     return;
   }
 
@@ -710,8 +711,8 @@
   auto request = std::make_unique<SendRequest>();
   SendRequest* request_ptr = request.get();
   request->buffer_size = buffer_size;
-  request->success_callback = success_callback;
-  request->error_callback = error_callback;
+  request->success_callback = std::move(success_callback);
+  request->error_callback = std::move(error_callback);
   send_queue_.push(std::move(request));
 
   // |writeAsync| accepts buffers of max. mtu bytes per call, so we need to emit
@@ -734,7 +735,7 @@
       if (request_ptr->status == kIOReturnSuccess)
         request_ptr->status = status;
       request_ptr->error_signaled = true;
-      request_ptr->error_callback.Run(error.str());
+      std::move(request_ptr->error_callback).Run(error.str());
       // We may have failed to issue any write operation. In that case, there
       // will be no corresponding completion callback for this particular
       // request, so we must forget about it now.
@@ -779,10 +780,10 @@
       error << "Failed to connect bluetooth socket ("
             << channel_->GetDeviceAddress() << "): (" << status << ")";
       request->error_signaled = true;
-      request->error_callback.Run(error.str());
+      std::move(request->error_callback).Run(error.str());
     }
   } else {
-    request->success_callback.Run(request->buffer_size);
+    std::move(request->success_callback).Run(request->buffer_size);
   }
 }
 
@@ -791,27 +792,26 @@
 
   if (receive_callbacks_) {
     std::unique_ptr<ReceiveCallbacks> temp = std::move(receive_callbacks_);
-    temp->error_callback.Run(BluetoothSocket::kDisconnected,
-                             kSocketNotConnected);
+    std::move(temp->error_callback)
+        .Run(BluetoothSocket::kDisconnected, kSocketNotConnected);
   }
 
   ReleaseChannel();
 }
 
-void BluetoothSocketMac::Accept(
-    const AcceptCompletionCallback& success_callback,
-    const ErrorCompletionCallback& error_callback) {
+void BluetoothSocketMac::Accept(AcceptCompletionCallback success_callback,
+                                ErrorCompletionCallback error_callback) {
   DCHECK(thread_checker_.CalledOnValidThread());
 
   // Allow only one pending accept at a time.
   if (accept_request_) {
-    error_callback.Run(net::ErrorToString(net::ERR_IO_PENDING));
+    std::move(error_callback).Run(net::ErrorToString(net::ERR_IO_PENDING));
     return;
   }
 
   accept_request_.reset(new AcceptRequest);
-  accept_request_->success_callback = success_callback;
-  accept_request_->error_callback = error_callback;
+  accept_request_->success_callback = std::move(success_callback);
+  accept_request_->error_callback = std::move(error_callback);
 
   if (accept_queue_.size() >= 1)
     AcceptConnectionRequest();
@@ -839,10 +839,10 @@
   // Make sure to first set the new socket to be connecting and hook it up to
   // run the accept callback with the device object.
   client_socket->connect_callbacks_.reset(new ConnectCallbacks());
-  client_socket->connect_callbacks_->success_callback =
-      base::Bind(accept_request_->success_callback, device, client_socket);
+  client_socket->connect_callbacks_->success_callback = base::BindOnce(
+      std::move(accept_request_->success_callback), device, client_socket);
   client_socket->connect_callbacks_->error_callback =
-      accept_request_->error_callback;
+      std::move(accept_request_->error_callback);
   accept_request_.reset();
 
   // Now it's safe to associate the socket with the channel.
diff --git a/device/bluetooth/bluetooth_socket_net.cc b/device/bluetooth/bluetooth_socket_net.cc
index 7a80149..7c95d63 100644
--- a/device/bluetooth/bluetooth_socket_net.cc
+++ b/device/bluetooth/bluetooth_socket_net.cc
@@ -9,6 +9,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/callback_helpers.h"
 #include "base/check.h"
 #include "base/containers/queue.h"
 #include "base/location.h"
@@ -62,42 +63,42 @@
       FROM_HERE, base::BindOnce(&BluetoothSocketNet::DoClose, this));
 }
 
-void BluetoothSocketNet::Disconnect(
-    const base::Closure& success_callback) {
+void BluetoothSocketNet::Disconnect(base::OnceClosure success_callback) {
   DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
   socket_thread_->task_runner()->PostTask(
-      FROM_HERE, base::BindOnce(&BluetoothSocketNet::DoDisconnect, this,
-                                base::Bind(&BluetoothSocketNet::PostSuccess,
-                                           this, success_callback)));
+      FROM_HERE,
+      base::BindOnce(&BluetoothSocketNet::DoDisconnect, this,
+                     base::BindOnce(&BluetoothSocketNet::PostSuccess, this,
+                                    std::move(success_callback))));
 }
 
 void BluetoothSocketNet::Receive(
     int buffer_size,
-    const ReceiveCompletionCallback& success_callback,
-    const ReceiveErrorCompletionCallback& error_callback) {
+    ReceiveCompletionCallback success_callback,
+    ReceiveErrorCompletionCallback error_callback) {
   DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
   socket_thread_->task_runner()->PostTask(
       FROM_HERE,
-      base::BindOnce(&BluetoothSocketNet::DoReceive, this, buffer_size,
-                     base::Bind(&BluetoothSocketNet::PostReceiveCompletion,
-                                this, success_callback),
-                     base::Bind(&BluetoothSocketNet::PostReceiveErrorCompletion,
-                                this, error_callback)));
+      base::BindOnce(
+          &BluetoothSocketNet::DoReceive, this, buffer_size,
+          base::BindOnce(&BluetoothSocketNet::PostReceiveCompletion, this,
+                         std::move(success_callback)),
+          base::BindOnce(&BluetoothSocketNet::PostReceiveErrorCompletion, this,
+                         std::move(error_callback))));
 }
 
-void BluetoothSocketNet::Send(
-    scoped_refptr<net::IOBuffer> buffer,
-    int buffer_size,
-    const SendCompletionCallback& success_callback,
-    const ErrorCompletionCallback& error_callback) {
+void BluetoothSocketNet::Send(scoped_refptr<net::IOBuffer> buffer,
+                              int buffer_size,
+                              SendCompletionCallback success_callback,
+                              ErrorCompletionCallback error_callback) {
   DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
   socket_thread_->task_runner()->PostTask(
       FROM_HERE,
       base::BindOnce(&BluetoothSocketNet::DoSend, this, buffer, buffer_size,
-                     base::Bind(&BluetoothSocketNet::PostSendCompletion, this,
-                                success_callback),
-                     base::Bind(&BluetoothSocketNet::PostErrorCompletion, this,
-                                error_callback)));
+                     base::BindOnce(&BluetoothSocketNet::PostSendCompletion,
+                                    this, std::move(success_callback)),
+                     base::BindOnce(&BluetoothSocketNet::PostErrorCompletion,
+                                    this, std::move(error_callback))));
 }
 
 void BluetoothSocketNet::ResetData() {
@@ -116,9 +117,8 @@
   ui_task_runner_->PostTask(FROM_HERE, std::move(callback));
 }
 
-void BluetoothSocketNet::PostErrorCompletion(
-    ErrorCompletionOnceCallback callback,
-    const std::string& error) {
+void BluetoothSocketNet::PostErrorCompletion(ErrorCompletionCallback callback,
+                                             const std::string& error) {
   ui_task_runner_->PostTask(FROM_HERE,
                             base::BindOnce(std::move(callback), error));
 }
@@ -143,82 +143,86 @@
   ResetData();
 }
 
-void BluetoothSocketNet::DoDisconnect(const base::Closure& callback) {
+void BluetoothSocketNet::DoDisconnect(base::OnceClosure callback) {
   DCHECK(socket_thread_->task_runner()->RunsTasksInCurrentSequence());
 
   DoClose();
-  callback.Run();
+  std::move(callback).Run();
 }
 
 void BluetoothSocketNet::DoReceive(
     int buffer_size,
-    const ReceiveCompletionCallback& success_callback,
-    const ReceiveErrorCompletionCallback& error_callback) {
+    ReceiveCompletionCallback success_callback,
+    ReceiveErrorCompletionCallback error_callback) {
   DCHECK(socket_thread_->task_runner()->RunsTasksInCurrentSequence());
   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                 base::BlockingType::MAY_BLOCK);
 
   if (!tcp_socket_) {
-    error_callback.Run(BluetoothSocket::kDisconnected, kSocketNotConnected);
+    std::move(error_callback)
+        .Run(BluetoothSocket::kDisconnected, kSocketNotConnected);
     return;
   }
 
   // Only one pending read at a time
   if (read_buffer_.get()) {
-    error_callback.Run(BluetoothSocket::kIOPending,
-                       net::ErrorToString(net::ERR_IO_PENDING));
+    std::move(error_callback)
+        .Run(BluetoothSocket::kIOPending,
+             net::ErrorToString(net::ERR_IO_PENDING));
     return;
   }
 
-  auto buffer = base::MakeRefCounted<net::IOBufferWithSize>(buffer_size);
-  int read_result = tcp_socket_->Read(
-      buffer.get(), buffer->size(),
+  auto copyable_callback = base::AdaptCallbackForRepeating(
       base::BindOnce(&BluetoothSocketNet::OnSocketReadComplete, this,
-                     success_callback, error_callback));
+                     std::move(success_callback), std::move(error_callback)));
+  auto buffer = base::MakeRefCounted<net::IOBufferWithSize>(buffer_size);
+  int read_result =
+      tcp_socket_->Read(buffer.get(), buffer->size(), copyable_callback);
 
   read_buffer_ = buffer;
+
+  // Read() will not have run |copyable_callback| if there is no pending I/O.
   if (read_result != net::ERR_IO_PENDING)
-    OnSocketReadComplete(success_callback, error_callback, read_result);
+    copyable_callback.Run(read_result);
 }
 
 void BluetoothSocketNet::OnSocketReadComplete(
-    const ReceiveCompletionCallback& success_callback,
-    const ReceiveErrorCompletionCallback& error_callback,
+    ReceiveCompletionCallback success_callback,
+    ReceiveErrorCompletionCallback error_callback,
     int read_result) {
   DCHECK(socket_thread_->task_runner()->RunsTasksInCurrentSequence());
 
   scoped_refptr<net::IOBufferWithSize> buffer;
   buffer.swap(read_buffer_);
   if (read_result > 0) {
-    success_callback.Run(read_result, buffer);
+    std::move(success_callback).Run(read_result, buffer);
   } else if (read_result == net::OK ||
              read_result == net::ERR_CONNECTION_CLOSED ||
              read_result == net::ERR_CONNECTION_RESET) {
-    error_callback.Run(BluetoothSocket::kDisconnected,
-                       net::ErrorToString(read_result));
+    std::move(error_callback)
+        .Run(BluetoothSocket::kDisconnected, net::ErrorToString(read_result));
   } else {
-    error_callback.Run(BluetoothSocket::kSystemError,
-                       net::ErrorToString(read_result));
+    std::move(error_callback)
+        .Run(BluetoothSocket::kSystemError, net::ErrorToString(read_result));
   }
 }
 
-void BluetoothSocketNet::DoSend(
-    scoped_refptr<net::IOBuffer> buffer,
-    int buffer_size,
-    const SendCompletionCallback& success_callback,
-    const ErrorCompletionCallback& error_callback) {
+void BluetoothSocketNet::DoSend(scoped_refptr<net::IOBuffer> buffer,
+                                int buffer_size,
+                                SendCompletionCallback success_callback,
+                                ErrorCompletionCallback error_callback) {
   DCHECK(socket_thread_->task_runner()->RunsTasksInCurrentSequence());
 
   if (!tcp_socket_) {
-    error_callback.Run(kSocketNotConnected);
+    std::move(error_callback).Run(kSocketNotConnected);
     return;
   }
 
   auto request = std::make_unique<WriteRequest>();
   request->buffer = buffer;
   request->buffer_size = buffer_size;
-  request->success_callback = success_callback;
-  request->error_callback = error_callback;
+  request->success_callback = std::move(success_callback);
+  request->error_callback = std::move(error_callback);
 
   write_queue_.push(std::move(request));
   if (write_queue_.size() == 1) {
@@ -238,9 +242,10 @@
     return;
 
   WriteRequest* request = write_queue_.front().get();
-  net::CompletionRepeatingCallback callback =
-      base::BindRepeating(&BluetoothSocketNet::OnSocketWriteComplete, this,
-                          request->success_callback, request->error_callback);
+  auto copyable_callback = base::AdaptCallbackForRepeating(
+      base::BindOnce(&BluetoothSocketNet::OnSocketWriteComplete, this,
+                     std::move(request->success_callback),
+                     std::move(request->error_callback)));
   net::NetworkTrafficAnnotationTag traffic_annotation =
       net::DefineNetworkTrafficAnnotation("bluetooth_socket", R"(
         semantics {
@@ -266,25 +271,25 @@
             "not implemented for other platforms."
         })");
   int send_result =
-      tcp_socket_->Write(request->buffer.get(), request->buffer_size, callback,
-                         traffic_annotation);
-  if (send_result != net::ERR_IO_PENDING) {
-    callback.Run(send_result);
-  }
+      tcp_socket_->Write(request->buffer.get(), request->buffer_size,
+                         copyable_callback, traffic_annotation);
+  // Write() will not have run |copyable_callback| if there is no pending I/O.
+  if (send_result != net::ERR_IO_PENDING)
+    copyable_callback.Run(send_result);
 }
 
 void BluetoothSocketNet::OnSocketWriteComplete(
-    const SendCompletionCallback& success_callback,
-    const ErrorCompletionCallback& error_callback,
+    SendCompletionCallback success_callback,
+    ErrorCompletionCallback error_callback,
     int send_result) {
   DCHECK(socket_thread_->task_runner()->RunsTasksInCurrentSequence());
 
   write_queue_.pop();
 
   if (send_result >= net::OK) {
-    success_callback.Run(send_result);
+    std::move(success_callback).Run(send_result);
   } else {
-    error_callback.Run(net::ErrorToString(send_result));
+    std::move(error_callback).Run(net::ErrorToString(send_result));
   }
 
   // Don't call directly to avoid potentail large recursion.
@@ -294,25 +299,26 @@
 }
 
 void BluetoothSocketNet::PostReceiveCompletion(
-    const ReceiveCompletionCallback& callback,
+    ReceiveCompletionCallback callback,
     int io_buffer_size,
     scoped_refptr<net::IOBuffer> io_buffer) {
   ui_task_runner_->PostTask(
-      FROM_HERE, base::BindOnce(callback, io_buffer_size, io_buffer));
+      FROM_HERE,
+      base::BindOnce(std::move(callback), io_buffer_size, io_buffer));
 }
 
 void BluetoothSocketNet::PostReceiveErrorCompletion(
-    const ReceiveErrorCompletionCallback& callback,
+    ReceiveErrorCompletionCallback callback,
     ErrorReason reason,
     const std::string& error_message) {
-  ui_task_runner_->PostTask(FROM_HERE,
-                            base::BindOnce(callback, reason, error_message));
+  ui_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback), reason, error_message));
 }
 
-void BluetoothSocketNet::PostSendCompletion(
-    const SendCompletionCallback& callback,
-    int bytes_written) {
-  ui_task_runner_->PostTask(FROM_HERE, base::BindOnce(callback, bytes_written));
+void BluetoothSocketNet::PostSendCompletion(SendCompletionCallback callback,
+                                            int bytes_written) {
+  ui_task_runner_->PostTask(FROM_HERE,
+                            base::BindOnce(std::move(callback), bytes_written));
 }
 
 }  // namespace device
diff --git a/device/bluetooth/bluetooth_socket_net.h b/device/bluetooth/bluetooth_socket_net.h
index f0eef230..9c7459d 100644
--- a/device/bluetooth/bluetooth_socket_net.h
+++ b/device/bluetooth/bluetooth_socket_net.h
@@ -31,14 +31,14 @@
  public:
   // BluetoothSocket:
   void Close() override;
-  void Disconnect(const base::Closure& callback) override;
+  void Disconnect(base::OnceClosure callback) override;
   void Receive(int buffer_size,
-               const ReceiveCompletionCallback& success_callback,
-               const ReceiveErrorCompletionCallback& error_callback) override;
+               ReceiveCompletionCallback success_callback,
+               ReceiveErrorCompletionCallback error_callback) override;
   void Send(scoped_refptr<net::IOBuffer> buffer,
             int buffer_size,
-            const SendCompletionCallback& success_callback,
-            const ErrorCompletionCallback& error_callback) override;
+            SendCompletionCallback success_callback,
+            ErrorCompletionCallback error_callback) override;
 
  protected:
   BluetoothSocketNet(scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
@@ -64,7 +64,7 @@
   void SetTCPSocket(std::unique_ptr<net::TCPSocket> tcp_socket);
 
   void PostSuccess(base::OnceClosure callback);
-  void PostErrorCompletion(ErrorCompletionOnceCallback callback,
+  void PostErrorCompletion(ErrorCompletionCallback callback,
                            const std::string& error);
 
  private:
@@ -79,32 +79,29 @@
   };
 
   void DoClose();
-  void DoDisconnect(const base::Closure& callback);
+  void DoDisconnect(base::OnceClosure callback);
   void DoReceive(int buffer_size,
-                 const ReceiveCompletionCallback& success_callback,
-                 const ReceiveErrorCompletionCallback& error_callback);
-  void OnSocketReadComplete(
-      const ReceiveCompletionCallback& success_callback,
-      const ReceiveErrorCompletionCallback& error_callback,
-      int read_result);
+                 ReceiveCompletionCallback success_callback,
+                 ReceiveErrorCompletionCallback error_callback);
+  void OnSocketReadComplete(ReceiveCompletionCallback success_callback,
+                            ReceiveErrorCompletionCallback error_callback,
+                            int read_result);
   void DoSend(scoped_refptr<net::IOBuffer> buffer,
               int buffer_size,
-              const SendCompletionCallback& success_callback,
-              const ErrorCompletionCallback& error_callback);
+              SendCompletionCallback success_callback,
+              ErrorCompletionCallback error_callback);
   void SendFrontWriteRequest();
-  void OnSocketWriteComplete(const SendCompletionCallback& success_callback,
-                             const ErrorCompletionCallback& error_callback,
+  void OnSocketWriteComplete(SendCompletionCallback success_callback,
+                             ErrorCompletionCallback error_callback,
                              int send_result);
 
-  void PostReceiveCompletion(const ReceiveCompletionCallback& callback,
+  void PostReceiveCompletion(ReceiveCompletionCallback callback,
                              int io_buffer_size,
                              scoped_refptr<net::IOBuffer> io_buffer);
-  void PostReceiveErrorCompletion(
-      const ReceiveErrorCompletionCallback& callback,
-      ErrorReason reason,
-      const std::string& error_message);
-  void PostSendCompletion(const SendCompletionCallback& callback,
-                          int bytes_written);
+  void PostReceiveErrorCompletion(ReceiveErrorCompletionCallback callback,
+                                  ErrorReason reason,
+                                  const std::string& error_message);
+  void PostSendCompletion(SendCompletionCallback callback, int bytes_written);
 
   scoped_refptr<base::SequencedTaskRunner> ui_task_runner_;
   scoped_refptr<BluetoothSocketThread> socket_thread_;
diff --git a/device/bluetooth/bluetooth_socket_win.cc b/device/bluetooth/bluetooth_socket_win.cc
index eed2b13..d9a7e537 100644
--- a/device/bluetooth/bluetooth_socket_win.cc
+++ b/device/bluetooth/bluetooth_socket_win.cc
@@ -11,6 +11,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/callback_helpers.h"
 #include "base/logging.h"
 #include "base/memory/ref_counted.h"
 #include "base/sequenced_task_runner.h"
@@ -101,7 +102,7 @@
 void BluetoothSocketWin::Connect(const BluetoothDeviceWin* device,
                                  const BluetoothUUID& uuid,
                                  base::OnceClosure success_callback,
-                                 ErrorCompletionOnceCallback error_callback) {
+                                 ErrorCompletionCallback error_callback) {
   DCHECK(ui_task_runner()->RunsTasksInCurrentSequence());
   DCHECK(device);
 
@@ -137,7 +138,7 @@
                                 const BluetoothUUID& uuid,
                                 const BluetoothAdapter::ServiceOptions& options,
                                 base::OnceClosure success_callback,
-                                ErrorCompletionOnceCallback error_callback) {
+                                ErrorCompletionCallback error_callback) {
   DCHECK(ui_task_runner()->RunsTasksInCurrentSequence());
 
   adapter_ = adapter;
@@ -160,18 +161,18 @@
   }
 }
 
-void BluetoothSocketWin::Accept(
-    const AcceptCompletionCallback& success_callback,
-    const ErrorCompletionCallback& error_callback) {
+void BluetoothSocketWin::Accept(AcceptCompletionCallback success_callback,
+                                ErrorCompletionCallback error_callback) {
   DCHECK(ui_task_runner()->RunsTasksInCurrentSequence());
 
   socket_thread()->task_runner()->PostTask(
-      FROM_HERE, base::BindOnce(&BluetoothSocketWin::DoAccept, this,
-                                success_callback, error_callback));
+      FROM_HERE,
+      base::BindOnce(&BluetoothSocketWin::DoAccept, this,
+                     std::move(success_callback), std::move(error_callback)));
 }
 
 void BluetoothSocketWin::DoConnect(base::OnceClosure success_callback,
-                                   ErrorCompletionOnceCallback error_callback) {
+                                   ErrorCompletionCallback error_callback) {
   DCHECK(socket_thread()->task_runner()->RunsTasksInCurrentSequence());
   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                 base::BlockingType::MAY_BLOCK);
@@ -229,7 +230,7 @@
 void BluetoothSocketWin::DoListen(const BluetoothUUID& uuid,
                                   int rfcomm_channel,
                                   base::OnceClosure success_callback,
-                                  ErrorCompletionOnceCallback error_callback) {
+                                  ErrorCompletionCallback error_callback) {
   DCHECK(socket_thread()->task_runner()->RunsTasksInCurrentSequence());
   DCHECK(!tcp_socket() && !service_reg_data_);
 
@@ -330,42 +331,44 @@
   PostSuccess(std::move(success_callback));
 }
 
-void BluetoothSocketWin::DoAccept(
-    const AcceptCompletionCallback& success_callback,
-    const ErrorCompletionCallback& error_callback) {
+void BluetoothSocketWin::DoAccept(AcceptCompletionCallback success_callback,
+                                  ErrorCompletionCallback error_callback) {
   DCHECK(socket_thread()->task_runner()->RunsTasksInCurrentSequence());
+  auto copyable_error_callback =
+      base::AdaptCallbackForRepeating(std::move(error_callback));
   int result = tcp_socket()->Accept(
       &accept_socket_, &accept_address_,
       base::BindOnce(&BluetoothSocketWin::OnAcceptOnSocketThread, this,
-                     success_callback, error_callback));
+                     std::move(success_callback), copyable_error_callback));
   if (result != net::OK && result != net::ERR_IO_PENDING) {
     LOG(WARNING) << "Failed to accept, net err=" << result;
-    PostErrorCompletion(error_callback, kFailedToAccept);
+    PostErrorCompletion(copyable_error_callback, kFailedToAccept);
   }
 }
 
 void BluetoothSocketWin::OnAcceptOnSocketThread(
-    const AcceptCompletionCallback& success_callback,
-    const ErrorCompletionCallback& error_callback,
+    AcceptCompletionCallback success_callback,
+    ErrorCompletionCallback error_callback,
     int accept_result) {
   DCHECK(socket_thread()->task_runner()->RunsTasksInCurrentSequence());
   if (accept_result != net::OK) {
     LOG(WARNING) << "OnAccept error, net err=" << accept_result;
-    PostErrorCompletion(error_callback, kFailedToAccept);
+    PostErrorCompletion(std::move(error_callback), kFailedToAccept);
     return;
   }
 
   ui_task_runner()->PostTask(
-      FROM_HERE, base::BindOnce(&BluetoothSocketWin::OnAcceptOnUI, this,
-                                std::move(accept_socket_), accept_address_,
-                                success_callback, error_callback));
+      FROM_HERE,
+      base::BindOnce(&BluetoothSocketWin::OnAcceptOnUI, this,
+                     std::move(accept_socket_), accept_address_,
+                     std::move(success_callback), std::move(error_callback)));
 }
 
 void BluetoothSocketWin::OnAcceptOnUI(
     std::unique_ptr<net::TCPSocket> accept_socket,
     const net::IPEndPoint& peer_address,
-    const AcceptCompletionCallback& success_callback,
-    const ErrorCompletionCallback& error_callback) {
+    AcceptCompletionCallback success_callback,
+    ErrorCompletionCallback error_callback) {
   DCHECK(ui_task_runner()->RunsTasksInCurrentSequence());
 
   const std::string peer_device_address =
@@ -374,14 +377,14 @@
   if (!peer_device) {
     LOG(WARNING) << "OnAccept failed with unknown device, addr="
                  << peer_device_address;
-    error_callback.Run(kFailedToAccept);
+    std::move(error_callback).Run(kFailedToAccept);
     return;
   }
 
   scoped_refptr<BluetoothSocketWin> peer_socket =
       CreateBluetoothSocket(ui_task_runner(), socket_thread());
   peer_socket->SetTCPSocket(std::move(accept_socket));
-  success_callback.Run(peer_device, peer_socket);
+  std::move(success_callback).Run(peer_device, peer_socket);
 }
 
 }  // namespace device
diff --git a/device/bluetooth/bluetooth_socket_win.h b/device/bluetooth/bluetooth_socket_win.h
index 8ab16c5..d7ab8fd 100644
--- a/device/bluetooth/bluetooth_socket_win.h
+++ b/device/bluetooth/bluetooth_socket_win.h
@@ -41,7 +41,7 @@
   void Connect(const BluetoothDeviceWin* device,
                const BluetoothUUID& uuid,
                base::OnceClosure success_callback,
-               ErrorCompletionOnceCallback error_callback);
+               ErrorCompletionCallback error_callback);
 
   // Listens using this socket using an RFCOMM service published as UUID |uuid|
   // with Channel |options.channel|, or an automatically allocated Channel if
@@ -52,14 +52,14 @@
               const BluetoothUUID& uuid,
               const BluetoothAdapter::ServiceOptions& options,
               base::OnceClosure success_callback,
-              ErrorCompletionOnceCallback error_callback);
+              ErrorCompletionCallback error_callback);
 
   // BluetoothSocketNet:
   void ResetData() override;
 
   // BluetoothSocket:
-  void Accept(const AcceptCompletionCallback& success_callback,
-              const ErrorCompletionCallback& error_callback) override;
+  void Accept(AcceptCompletionCallback success_callback,
+              ErrorCompletionCallback error_callback) override;
 
  protected:
   ~BluetoothSocketWin() override;
@@ -71,20 +71,20 @@
                      scoped_refptr<BluetoothSocketThread> socket_thread);
 
   void DoConnect(base::OnceClosure success_callback,
-                 ErrorCompletionOnceCallback error_callback);
+                 ErrorCompletionCallback error_callback);
   void DoListen(const BluetoothUUID& uuid,
                 int rfcomm_channel,
                 base::OnceClosure success_callback,
-                ErrorCompletionOnceCallback error_callback);
-  void DoAccept(const AcceptCompletionCallback& success_callback,
-                const ErrorCompletionCallback& error_callback);
-  void OnAcceptOnSocketThread(const AcceptCompletionCallback& success_callback,
-                              const ErrorCompletionCallback& error_callback,
+                ErrorCompletionCallback error_callback);
+  void DoAccept(AcceptCompletionCallback success_callback,
+                ErrorCompletionCallback error_callback);
+  void OnAcceptOnSocketThread(AcceptCompletionCallback success_callback,
+                              ErrorCompletionCallback error_callback,
                               int accept_result);
   void OnAcceptOnUI(std::unique_ptr<net::TCPSocket> accept_socket,
                     const net::IPEndPoint& peer_address,
-                    const AcceptCompletionCallback& success_callback,
-                    const ErrorCompletionCallback& error_callback);
+                    AcceptCompletionCallback success_callback,
+                    ErrorCompletionCallback error_callback);
 
   std::string device_address_;
   bool supports_rfcomm_;
diff --git a/device/bluetooth/bluetooth_task_manager_win_unittest.cc b/device/bluetooth/bluetooth_task_manager_win_unittest.cc
index 076500d..70e3fb0 100644
--- a/device/bluetooth/bluetooth_task_manager_win_unittest.cc
+++ b/device/bluetooth/bluetooth_task_manager_win_unittest.cc
@@ -112,8 +112,8 @@
     return;
 
   bluetooth_task_runner_->ClearPendingTasks();
-  base::Closure closure;
-  task_manager_->PostSetPoweredBluetoothTask(true, closure, closure);
+  task_manager_->PostSetPoweredBluetoothTask(true, base::OnceClosure(),
+                                             base::OnceClosure());
 
   EXPECT_EQ(1u, bluetooth_task_runner_->NumPendingTasks());
   bluetooth_task_runner_->RunPendingTasks();
diff --git a/device/bluetooth/bluez/bluetooth_adapter_bluez.cc b/device/bluetooth/bluez/bluetooth_adapter_bluez.cc
index 95ebd53..1f6030e 100644
--- a/device/bluetooth/bluez/bluetooth_adapter_bluez.cc
+++ b/device/bluetooth/bluez/bluetooth_adapter_bluez.cc
@@ -1309,8 +1309,8 @@
   released_profiles_[uuid] = iter->second;
   profiles_.erase(iter);
   profile->RemoveDelegate(device_path,
-                          base::Bind(&BluetoothAdapterBlueZ::RemoveProfile,
-                                     weak_ptr_factory_.GetWeakPtr(), uuid));
+                          base::BindOnce(&BluetoothAdapterBlueZ::RemoveProfile,
+                                         weak_ptr_factory_.GetWeakPtr(), uuid));
 }
 
 void BluetoothAdapterBlueZ::RemoveProfile(const BluetoothUUID& uuid) {
diff --git a/device/bluetooth/bluez/bluetooth_adapter_profile_bluez.cc b/device/bluetooth/bluez/bluetooth_adapter_profile_bluez.cc
index ce7aaf4b..2890e98 100644
--- a/device/bluetooth/bluez/bluetooth_adapter_profile_bluez.cc
+++ b/device/bluetooth/bluez/bluetooth_adapter_profile_bluez.cc
@@ -9,6 +9,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/callback_helpers.h"
 #include "base/logging.h"
 #include "base/strings/string_util.h"
 #include "dbus/bus.h"
@@ -72,7 +73,7 @@
 
 void BluetoothAdapterProfileBlueZ::RemoveDelegate(
     const dbus::ObjectPath& device_path,
-    const base::Closure& unregistered_callback) {
+    base::OnceClosure unregistered_callback) {
   DVLOG(1) << object_path_.value() << " dev " << device_path.value()
            << ": RemoveDelegate";
 
@@ -87,23 +88,26 @@
   DVLOG(1) << device_path.value() << " No delegates left, unregistering.";
 
   // No users left, release the profile.
+  auto copyable_callback =
+      base::AdaptCallbackForRepeating(std::move(unregistered_callback));
   bluez::BluezDBusManager::Get()
       ->GetBluetoothProfileManagerClient()
       ->UnregisterProfile(
-          object_path_, unregistered_callback,
-          base::Bind(&BluetoothAdapterProfileBlueZ::OnUnregisterProfileError,
-                     weak_ptr_factory_.GetWeakPtr(), unregistered_callback));
+          object_path_, copyable_callback,
+          base::BindOnce(
+              &BluetoothAdapterProfileBlueZ::OnUnregisterProfileError,
+              weak_ptr_factory_.GetWeakPtr(), copyable_callback));
 }
 
 void BluetoothAdapterProfileBlueZ::OnUnregisterProfileError(
-    const base::Closure& unregistered_callback,
+    base::OnceClosure unregistered_callback,
     const std::string& error_name,
     const std::string& error_message) {
   LOG(WARNING) << this->object_path().value()
                << ": Failed to unregister profile: " << error_name << ": "
                << error_message;
 
-  unregistered_callback.Run();
+  std::move(unregistered_callback).Run();
 }
 
 // bluez::BluetoothProfileServiceProvider::Delegate:
diff --git a/device/bluetooth/bluez/bluetooth_adapter_profile_bluez.h b/device/bluetooth/bluez/bluetooth_adapter_profile_bluez.h
index a4cc845..583ed4b 100644
--- a/device/bluetooth/bluez/bluetooth_adapter_profile_bluez.h
+++ b/device/bluetooth/bluez/bluetooth_adapter_profile_bluez.h
@@ -66,7 +66,7 @@
   // Remove the delegate for a device. |unregistered_callback| will be called
   // if this unregisters the profile.
   void RemoveDelegate(const dbus::ObjectPath& device_path,
-                      const base::Closure& unregistered_callback);
+                      base::OnceClosure unregistered_callback);
 
   // Returns the number of delegates for this profile.
   size_t DelegateCount() const { return delegates_.size(); }
@@ -86,7 +86,7 @@
   void Cancel() override;
 
   // Called by dbus:: on completion of the D-Bus method to unregister a profile.
-  void OnUnregisterProfileError(const base::Closure& unregistered_callback,
+  void OnUnregisterProfileError(base::OnceClosure unregistered_callback,
                                 const std::string& error_name,
                                 const std::string& error_message);
 
diff --git a/device/bluetooth/bluez/bluetooth_bluez_unittest.cc b/device/bluetooth/bluez/bluetooth_bluez_unittest.cc
index 245a86d..99a47b0c 100644
--- a/device/bluetooth/bluez/bluetooth_bluez_unittest.cc
+++ b/device/bluetooth/bluez/bluetooth_bluez_unittest.cc
@@ -191,10 +191,7 @@
     std::move(closure).Run();
   }
 
-  base::Closure GetCallback() {
-    return base::Bind(&BluetoothBlueZTest::Callback, base::Unretained(this));
-  }
-  base::OnceClosure GetOnceCallback() {
+  base::OnceClosure GetCallback() {
     return base::BindOnce(&BluetoothBlueZTest::Callback,
                           base::Unretained(this));
   }
@@ -229,15 +226,15 @@
     ErrorCallback();
   }
 
-  base::Closure GetErrorCallback() {
-    return base::Bind(&BluetoothBlueZTest::ErrorCallback,
-                      base::Unretained(this));
+  base::OnceClosure GetErrorCallback() {
+    return base::BindOnce(&BluetoothBlueZTest::ErrorCallback,
+                          base::Unretained(this));
   }
 
-  base::Callback<void(device::UMABluetoothDiscoverySessionOutcome)>
+  base::OnceCallback<void(device::UMABluetoothDiscoverySessionOutcome)>
   GetDiscoveryErrorCallback() {
-    return base::Bind(&BluetoothBlueZTest::DiscoveryErrorCallback,
-                      base::Unretained(this));
+    return base::BindOnce(&BluetoothBlueZTest::DiscoveryErrorCallback,
+                          base::Unretained(this));
   }
 
   void DBusErrorCallback(const std::string& error_name,
@@ -2323,7 +2320,7 @@
   ASSERT_FALSE(device->IsPaired());
 
   // Connect the device so it becomes trusted and remembered.
-  device->Connect(nullptr, GetOnceCallback(),
+  device->Connect(nullptr, GetCallback(),
                   base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                                  base::Unretained(this)));
 
@@ -2373,7 +2370,7 @@
 
   // Connect without a pairing delegate; since the device is already Paired
   // this should succeed and the device should become connected.
-  device->Connect(nullptr, GetOnceCallback(),
+  device->Connect(nullptr, GetCallback(),
                   base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                                  base::Unretained(this)));
 
@@ -2406,7 +2403,7 @@
 
   // Connect without a pairing delegate; since the device does not require
   // pairing, this should succeed and the device should become connected.
-  device->Connect(nullptr, GetOnceCallback(),
+  device->Connect(nullptr, GetCallback(),
                   base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                                  base::Unretained(this)));
 
@@ -2446,7 +2443,7 @@
   ASSERT_TRUE(device != nullptr);
   ASSERT_TRUE(device->IsPaired());
 
-  device->Connect(nullptr, GetOnceCallback(),
+  device->Connect(nullptr, GetCallback(),
                   base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                                  base::Unretained(this)));
 
@@ -2464,7 +2461,7 @@
   // anything to initiate the connection.
   TestBluetoothAdapterObserver observer(adapter_);
 
-  device->Connect(nullptr, GetOnceCallback(),
+  device->Connect(nullptr, GetCallback(),
                   base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                                  base::Unretained(this)));
 
@@ -2496,7 +2493,7 @@
 
   // Connect without a pairing delegate; since the device requires pairing,
   // this should fail with an error.
-  device->Connect(nullptr, GetOnceCallback(),
+  device->Connect(nullptr, GetCallback(),
                   base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                                  base::Unretained(this)));
 
@@ -2524,7 +2521,7 @@
   ASSERT_TRUE(device != nullptr);
 
   fake_bluetooth_device_client_->LeaveConnectionsPending();
-  device->Connect(nullptr, GetOnceCallback(),
+  device->Connect(nullptr, GetCallback(),
                   base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                                  base::Unretained(this)));
   // We pause discovery before connecting.
@@ -2559,7 +2556,7 @@
   ASSERT_TRUE(device != nullptr);
   ASSERT_TRUE(device->IsPaired());
 
-  device->Connect(nullptr, GetOnceCallback(),
+  device->Connect(nullptr, GetCallback(),
                   base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                                  base::Unretained(this)));
 
@@ -2639,7 +2636,7 @@
   TestPairingDelegate pairing_delegate;
   adapter_->AddPairingDelegate(
       &pairing_delegate, BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_HIGH);
-  device->Pair(&pairing_delegate, GetOnceCallback(),
+  device->Pair(&pairing_delegate, GetCallback(),
                base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                               base::Unretained(this)));
   EXPECT_EQ(1, pairing_delegate.call_count_);
@@ -2680,7 +2677,7 @@
   TestPairingDelegate pairing_delegate;
   adapter_->AddPairingDelegate(
       &pairing_delegate, BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_HIGH);
-  device->Pair(&pairing_delegate, GetOnceCallback(),
+  device->Pair(&pairing_delegate, GetCallback(),
                base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                               base::Unretained(this)));
 
@@ -2707,7 +2704,7 @@
   TestBluetoothAdapterObserver observer(adapter_);
 
   TestPairingDelegate pairing_delegate;
-  device->Connect(&pairing_delegate, GetOnceCallback(),
+  device->Connect(&pairing_delegate, GetCallback(),
                   base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                                  base::Unretained(this)));
 
@@ -2762,7 +2759,7 @@
   TestBluetoothAdapterObserver observer(adapter_);
 
   TestPairingDelegate pairing_delegate;
-  device->Connect(&pairing_delegate, GetOnceCallback(),
+  device->Connect(&pairing_delegate, GetCallback(),
                   base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                                  base::Unretained(this)));
 
@@ -2820,7 +2817,7 @@
   TestBluetoothAdapterObserver observer(adapter_);
 
   TestPairingDelegate pairing_delegate;
-  device->Connect(&pairing_delegate, GetOnceCallback(),
+  device->Connect(&pairing_delegate, GetCallback(),
                   base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                                  base::Unretained(this)));
 
@@ -2897,7 +2894,7 @@
   TestBluetoothAdapterObserver observer(adapter_);
 
   TestPairingDelegate pairing_delegate;
-  device->Connect(&pairing_delegate, GetOnceCallback(),
+  device->Connect(&pairing_delegate, GetCallback(),
                   base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                                  base::Unretained(this)));
 
@@ -2955,7 +2952,7 @@
   TestBluetoothAdapterObserver observer(adapter_);
 
   TestPairingDelegate pairing_delegate;
-  device->Connect(&pairing_delegate, GetOnceCallback(),
+  device->Connect(&pairing_delegate, GetCallback(),
                   base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                                  base::Unretained(this)));
 
@@ -3011,7 +3008,7 @@
   TestBluetoothAdapterObserver observer(adapter_);
 
   TestPairingDelegate pairing_delegate;
-  device->Connect(&pairing_delegate, GetOnceCallback(),
+  device->Connect(&pairing_delegate, GetCallback(),
                   base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                                  base::Unretained(this)));
 
@@ -3066,7 +3063,7 @@
   TestBluetoothAdapterObserver observer(adapter_);
 
   TestPairingDelegate pairing_delegate;
-  device->Connect(&pairing_delegate, GetOnceCallback(),
+  device->Connect(&pairing_delegate, GetCallback(),
                   base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                                  base::Unretained(this)));
 
@@ -3115,7 +3112,7 @@
   TestBluetoothAdapterObserver observer(adapter_);
 
   TestPairingDelegate pairing_delegate;
-  device->Connect(&pairing_delegate, GetOnceCallback(),
+  device->Connect(&pairing_delegate, GetCallback(),
                   base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                                  base::Unretained(this)));
 
@@ -3154,7 +3151,7 @@
   TestBluetoothAdapterObserver observer(adapter_);
 
   TestPairingDelegate pairing_delegate;
-  device->Connect(&pairing_delegate, GetOnceCallback(),
+  device->Connect(&pairing_delegate, GetCallback(),
                   base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                                  base::Unretained(this)));
 
@@ -3194,7 +3191,7 @@
   TestBluetoothAdapterObserver observer(adapter_);
 
   TestPairingDelegate pairing_delegate;
-  device->Connect(&pairing_delegate, GetOnceCallback(),
+  device->Connect(&pairing_delegate, GetCallback(),
                   base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                                  base::Unretained(this)));
 
@@ -3244,7 +3241,7 @@
   TestBluetoothAdapterObserver observer(adapter_);
 
   TestPairingDelegate pairing_delegate;
-  device->Connect(&pairing_delegate, GetOnceCallback(),
+  device->Connect(&pairing_delegate, GetCallback(),
                   base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                                  base::Unretained(this)));
 
@@ -3286,7 +3283,7 @@
   TestBluetoothAdapterObserver observer(adapter_);
 
   TestPairingDelegate pairing_delegate;
-  device->Connect(&pairing_delegate, GetOnceCallback(),
+  device->Connect(&pairing_delegate, GetCallback(),
                   base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                                  base::Unretained(this)));
 
@@ -3328,7 +3325,7 @@
   TestBluetoothAdapterObserver observer(adapter_);
 
   TestPairingDelegate pairing_delegate;
-  device->Connect(&pairing_delegate, GetOnceCallback(),
+  device->Connect(&pairing_delegate, GetCallback(),
                   base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                                  base::Unretained(this)));
 
@@ -3370,7 +3367,7 @@
   TestBluetoothAdapterObserver observer(adapter_);
 
   TestPairingDelegate pairing_delegate;
-  device->Connect(&pairing_delegate, GetOnceCallback(),
+  device->Connect(&pairing_delegate, GetCallback(),
                   base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                                  base::Unretained(this)));
 
@@ -3412,7 +3409,7 @@
   TestBluetoothAdapterObserver observer(adapter_);
 
   TestPairingDelegate pairing_delegate;
-  device->Connect(&pairing_delegate, GetOnceCallback(),
+  device->Connect(&pairing_delegate, GetCallback(),
                   base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                                  base::Unretained(this)));
 
@@ -3454,7 +3451,7 @@
   TestBluetoothAdapterObserver observer(adapter_);
 
   TestPairingDelegate pairing_delegate;
-  device->Connect(&pairing_delegate, GetOnceCallback(),
+  device->Connect(&pairing_delegate, GetCallback(),
                   base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                                  base::Unretained(this)));
 
@@ -3496,7 +3493,7 @@
   TestBluetoothAdapterObserver observer(adapter_);
 
   TestPairingDelegate pairing_delegate;
-  device->Connect(&pairing_delegate, GetOnceCallback(),
+  device->Connect(&pairing_delegate, GetCallback(),
                   base::BindOnce(&BluetoothBlueZTest::ConnectErrorCallback,
                                  base::Unretained(this)));
 
diff --git a/device/bluetooth/bluez/bluetooth_service_record_bluez_unittest.cc b/device/bluetooth/bluez/bluetooth_service_record_bluez_unittest.cc
index c2461454..12202c0f 100644
--- a/device/bluetooth/bluez/bluetooth_service_record_bluez_unittest.cc
+++ b/device/bluetooth/bluez/bluetooth_service_record_bluez_unittest.cc
@@ -184,7 +184,7 @@
       static_cast<BluetoothDeviceBlueZ*>(adapter_->GetDevice(
           bluez::FakeBluetoothDeviceClient::kPairedDeviceAddress));
   GetServiceRecords(device, false);
-  device->Connect(nullptr, GetOnceCallback(Call::EXPECTED),
+  device->Connect(nullptr, GetCallback(Call::EXPECTED),
                   GetConnectErrorCallback(Call::NOT_EXPECTED));
   GetServiceRecords(device, true);
   VerifyRecords();
diff --git a/device/bluetooth/bluez/bluetooth_socket_bluez.cc b/device/bluetooth/bluez/bluetooth_socket_bluez.cc
index 396b08c..3f07b29 100644
--- a/device/bluetooth/bluez/bluetooth_socket_bluez.cc
+++ b/device/bluetooth/bluez/bluetooth_socket_bluez.cc
@@ -92,7 +92,7 @@
                                    const BluetoothUUID& uuid,
                                    SecurityLevel security_level,
                                    base::OnceClosure success_callback,
-                                   ErrorCompletionOnceCallback error_callback) {
+                                   ErrorCompletionCallback error_callback) {
   DCHECK(ui_task_runner()->RunsTasksInCurrentSequence());
   DCHECK(!profile_);
 
@@ -120,7 +120,7 @@
     const BluetoothUUID& uuid,
     const BluetoothAdapter::ServiceOptions& service_options,
     base::OnceClosure success_callback,
-    ErrorCompletionOnceCallback error_callback) {
+    ErrorCompletionCallback error_callback) {
   DCHECK(ui_task_runner()->RunsTasksInCurrentSequence());
   DCHECK(!profile_);
 
@@ -176,39 +176,38 @@
   }
 }
 
-void BluetoothSocketBlueZ::Disconnect(const base::Closure& callback) {
+void BluetoothSocketBlueZ::Disconnect(base::OnceClosure callback) {
   DCHECK(ui_task_runner()->RunsTasksInCurrentSequence());
 
   if (profile_)
     UnregisterProfile();
 
   if (!device_path_.value().empty()) {
-    BluetoothSocketNet::Disconnect(callback);
+    BluetoothSocketNet::Disconnect(std::move(callback));
   } else {
     DoCloseListening();
-    callback.Run();
+    std::move(callback).Run();
   }
 }
 
-void BluetoothSocketBlueZ::Accept(
-    const AcceptCompletionCallback& success_callback,
-    const ErrorCompletionCallback& error_callback) {
+void BluetoothSocketBlueZ::Accept(AcceptCompletionCallback success_callback,
+                                  ErrorCompletionCallback error_callback) {
   DCHECK(ui_task_runner()->RunsTasksInCurrentSequence());
 
   if (!device_path_.value().empty()) {
-    error_callback.Run(kSocketNotListening);
+    std::move(error_callback).Run(kSocketNotListening);
     return;
   }
 
   // Only one pending accept at a time
   if (accept_request_.get()) {
-    error_callback.Run(net::ErrorToString(net::ERR_IO_PENDING));
+    std::move(error_callback).Run(net::ErrorToString(net::ERR_IO_PENDING));
     return;
   }
 
-  accept_request_.reset(new AcceptRequest);
-  accept_request_->success_callback = success_callback;
-  accept_request_->error_callback = error_callback;
+  accept_request_ = std::make_unique<AcceptRequest>();
+  accept_request_->success_callback = std::move(success_callback);
+  accept_request_->error_callback = std::move(error_callback);
 
   if (connection_request_queue_.size() >= 1) {
     AcceptConnectionRequest();
@@ -218,7 +217,7 @@
 void BluetoothSocketBlueZ::RegisterProfile(
     BluetoothAdapterBlueZ* adapter,
     base::OnceClosure success_callback,
-    ErrorCompletionOnceCallback error_callback) {
+    ErrorCompletionCallback error_callback) {
   DCHECK(ui_task_runner()->RunsTasksInCurrentSequence());
   DCHECK(!profile_);
   DCHECK(adapter);
@@ -249,7 +248,7 @@
 
 void BluetoothSocketBlueZ::OnRegisterProfile(
     base::OnceClosure success_callback,
-    ErrorCompletionOnceCallback error_callback,
+    ErrorCompletionCallback error_callback,
     BluetoothAdapterProfileBlueZ* profile) {
   DCHECK(ui_task_runner()->RunsTasksInCurrentSequence());
   DCHECK(!profile_);
@@ -274,7 +273,7 @@
 }
 
 void BluetoothSocketBlueZ::OnRegisterProfileError(
-    ErrorCompletionOnceCallback error_callback,
+    ErrorCompletionCallback error_callback,
     const std::string& error_message) {
   DCHECK(ui_task_runner()->RunsTasksInCurrentSequence());
 
@@ -294,7 +293,7 @@
 }
 
 void BluetoothSocketBlueZ::OnConnectProfileError(
-    ErrorCompletionOnceCallback error_callback,
+    ErrorCompletionCallback error_callback,
     const std::string& error_name,
     const std::string& error_message) {
   DCHECK(ui_task_runner()->RunsTasksInCurrentSequence());
@@ -504,9 +503,9 @@
             ->GetDeviceWithPath(request->device_path);
     DCHECK(device);
 
-    accept_request_->success_callback.Run(device, socket);
+    std::move(accept_request_->success_callback).Run(device, socket);
   } else {
-    accept_request_->error_callback.Run(kAcceptFailed);
+    std::move(accept_request_->error_callback).Run(kAcceptFailed);
   }
 
   accept_request_.reset(nullptr);
@@ -519,8 +518,8 @@
   DCHECK(ui_task_runner()->RunsTasksInCurrentSequence());
 
   if (accept_request_) {
-    accept_request_->error_callback.Run(
-        net::ErrorToString(net::ERR_CONNECTION_CLOSED));
+    std::move(accept_request_->error_callback)
+        .Run(net::ErrorToString(net::ERR_CONNECTION_CLOSED));
     accept_request_.reset(nullptr);
   }
 
diff --git a/device/bluetooth/bluez/bluetooth_socket_bluez.h b/device/bluetooth/bluez/bluetooth_socket_bluez.h
index 0536790..f8165556 100644
--- a/device/bluetooth/bluez/bluetooth_socket_bluez.h
+++ b/device/bluetooth/bluez/bluetooth_socket_bluez.h
@@ -50,7 +50,7 @@
                        const device::BluetoothUUID& uuid,
                        SecurityLevel security_level,
                        base::OnceClosure success_callback,
-                       ErrorCompletionOnceCallback error_callback);
+                       ErrorCompletionCallback error_callback);
 
   // Listens using this socket using a service published on |adapter|. The
   // service is either RFCOMM or L2CAP depending on |socket_type| and published
@@ -65,13 +65,13 @@
       const device::BluetoothUUID& uuid,
       const device::BluetoothAdapter::ServiceOptions& service_options,
       base::OnceClosure success_callback,
-      ErrorCompletionOnceCallback error_callback);
+      ErrorCompletionCallback error_callback);
 
   // BluetoothSocket:
   void Close() override;
-  void Disconnect(const base::Closure& callback) override;
-  void Accept(const AcceptCompletionCallback& success_callback,
-              const ErrorCompletionCallback& error_callback) override;
+  void Disconnect(base::OnceClosure callback) override;
+  void Accept(AcceptCompletionCallback success_callback,
+              ErrorCompletionCallback error_callback) override;
 
  protected:
   ~BluetoothSocketBlueZ() override;
@@ -84,16 +84,16 @@
   // Register the underlying profile client object with the Bluetooth Daemon.
   void RegisterProfile(BluetoothAdapterBlueZ* adapter,
                        base::OnceClosure success_callback,
-                       ErrorCompletionOnceCallback error_callback);
+                       ErrorCompletionCallback error_callback);
   void OnRegisterProfile(base::OnceClosure success_callback,
-                         ErrorCompletionOnceCallback error_callback,
+                         ErrorCompletionCallback error_callback,
                          BluetoothAdapterProfileBlueZ* profile);
-  void OnRegisterProfileError(ErrorCompletionOnceCallback error_callback,
+  void OnRegisterProfileError(ErrorCompletionCallback error_callback,
                               const std::string& error_message);
 
   // Called by dbus:: on completion of the ConnectProfile() method.
   void OnConnectProfile(base::OnceClosure success_callback);
-  void OnConnectProfileError(ErrorCompletionOnceCallback error_callback,
+  void OnConnectProfileError(ErrorCompletionCallback error_callback,
                              const std::string& error_name,
                              const std::string& error_message);
 
diff --git a/device/bluetooth/dbus/bluetooth_agent_service_provider.cc b/device/bluetooth/dbus/bluetooth_agent_service_provider.cc
index fefc74da..b94eccdc 100644
--- a/device/bluetooth/dbus/bluetooth_agent_service_provider.cc
+++ b/device/bluetooth/dbus/bluetooth_agent_service_provider.cc
@@ -454,7 +454,12 @@
   if (!bluez::BluezDBusManager::Get()->IsUsingFakes()) {
     return new BluetoothAgentServiceProviderImpl(bus, object_path, delegate);
   }
+#if defined(USE_REAL_DBUS_CLIENTS)
+  LOG(FATAL) << "Fake is unavailable if USE_REAL_DBUS_CLIENTS is defined.";
+  return nullptr;
+#else
   return new FakeBluetoothAgentServiceProvider(object_path, delegate);
+#endif  // defined(USE_REAL_DBUS_CLIENTS)
 }
 
 }  // namespace bluez
diff --git a/device/bluetooth/dbus/bluetooth_dbus_client_bundle.cc b/device/bluetooth/dbus/bluetooth_dbus_client_bundle.cc
index c0e077b..7abf123e 100644
--- a/device/bluetooth/dbus/bluetooth_dbus_client_bundle.cc
+++ b/device/bluetooth/dbus/bluetooth_dbus_client_bundle.cc
@@ -7,6 +7,7 @@
 #include <vector>
 
 #include "base/command_line.h"
+#include "base/logging.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
 #include "device/bluetooth/dbus/bluetooth_adapter_client.h"
@@ -58,6 +59,9 @@
     alternate_bluetooth_adapter_client_.reset(BluetoothAdapterClient::Create());
     alternate_bluetooth_device_client_.reset(BluetoothDeviceClient::Create());
   } else {
+#if defined(USE_REAL_DBUS_CLIENTS)
+    LOG(FATAL) << "Fakes are unavailable if USE_REAL_DBUS_CLIENTS is defined.";
+#else
     bluetooth_adapter_client_.reset(new FakeBluetoothAdapterClient);
     bluetooth_le_advertising_manager_client_.reset(
         new FakeBluetoothLEAdvertisingManagerClient);
@@ -76,6 +80,7 @@
 
     alternate_bluetooth_adapter_client_.reset(new FakeBluetoothAdapterClient);
     alternate_bluetooth_device_client_.reset(new FakeBluetoothDeviceClient);
+#endif  // defined(USE_REAL_DBUS_CLIENTS)
   }
 }
 
diff --git a/device/bluetooth/dbus/bluetooth_gatt_application_service_provider.cc b/device/bluetooth/dbus/bluetooth_gatt_application_service_provider.cc
index 611e810f..53bb65d 100644
--- a/device/bluetooth/dbus/bluetooth_gatt_application_service_provider.cc
+++ b/device/bluetooth/dbus/bluetooth_gatt_application_service_provider.cc
@@ -183,8 +183,13 @@
     return base::WrapUnique(new BluetoothGattApplicationServiceProviderImpl(
         bus, object_path, services));
   }
+#if defined(USE_REAL_DBUS_CLIENTS)
+  LOG(FATAL) << "Fake is unavailable if USE_REAL_DBUS_CLIENTS is defined.";
+  return nullptr;
+#else
   return std::make_unique<FakeBluetoothGattApplicationServiceProvider>(
       object_path, services);
+#endif  // defined(USE_REAL_DBUS_CLIENTS)
 }
 
 }  // namespace bluez
diff --git a/device/bluetooth/dbus/bluetooth_gatt_characteristic_service_provider.cc b/device/bluetooth/dbus/bluetooth_gatt_characteristic_service_provider.cc
index bf66c34..b799423 100644
--- a/device/bluetooth/dbus/bluetooth_gatt_characteristic_service_provider.cc
+++ b/device/bluetooth/dbus/bluetooth_gatt_characteristic_service_provider.cc
@@ -4,6 +4,7 @@
 
 #include "device/bluetooth/dbus/bluetooth_gatt_characteristic_service_provider.h"
 
+#include "base/logging.h"
 #include "device/bluetooth/dbus/bluetooth_gatt_characteristic_service_provider_impl.h"
 #include "device/bluetooth/dbus/bluez_dbus_manager.h"
 #include "device/bluetooth/dbus/fake_bluetooth_gatt_characteristic_service_provider.h"
@@ -29,8 +30,13 @@
     return new BluetoothGattCharacteristicServiceProviderImpl(
         bus, object_path, std::move(delegate), uuid, flags, service_path);
   }
+#if defined(USE_REAL_DBUS_CLIENTS)
+  LOG(FATAL) << "Fake is unavailable if USE_REAL_DBUS_CLIENTS is defined.";
+  return nullptr;
+#else
   return new FakeBluetoothGattCharacteristicServiceProvider(
       object_path, std::move(delegate), uuid, flags, service_path);
+#endif  // defined(USE_REAL_DBUS_CLIENTS)
 }
 
 }  // namespace bluez
diff --git a/device/bluetooth/dbus/bluetooth_gatt_descriptor_service_provider.cc b/device/bluetooth/dbus/bluetooth_gatt_descriptor_service_provider.cc
index 9a84761..63d9748 100644
--- a/device/bluetooth/dbus/bluetooth_gatt_descriptor_service_provider.cc
+++ b/device/bluetooth/dbus/bluetooth_gatt_descriptor_service_provider.cc
@@ -4,6 +4,7 @@
 
 #include "device/bluetooth/dbus/bluetooth_gatt_descriptor_service_provider.h"
 
+#include "base/logging.h"
 #include "device/bluetooth/dbus/bluetooth_gatt_descriptor_service_provider_impl.h"
 #include "device/bluetooth/dbus/bluez_dbus_manager.h"
 #include "device/bluetooth/dbus/fake_bluetooth_gatt_descriptor_service_provider.h"
@@ -30,8 +31,13 @@
         bus, object_path, std::move(delegate), uuid, flags,
         characteristic_path);
   }
+#if defined(USE_REAL_DBUS_CLIENTS)
+  LOG(FATAL) << "Fake is unavailable if USE_REAL_DBUS_CLIENTS is defined.";
+  return nullptr;
+#else
   return new FakeBluetoothGattDescriptorServiceProvider(
       object_path, std::move(delegate), uuid, flags, characteristic_path);
+#endif  // defined(USE_REAL_DBUS_CLIENTS)
 }
 
 }  // namespace bluez
diff --git a/device/bluetooth/dbus/bluetooth_gatt_service_service_provider.cc b/device/bluetooth/dbus/bluetooth_gatt_service_service_provider.cc
index e15a587..1e06dde 100644
--- a/device/bluetooth/dbus/bluetooth_gatt_service_service_provider.cc
+++ b/device/bluetooth/dbus/bluetooth_gatt_service_service_provider.cc
@@ -4,6 +4,7 @@
 
 #include "device/bluetooth/dbus/bluetooth_gatt_service_service_provider.h"
 
+#include "base/logging.h"
 #include "device/bluetooth/dbus/bluetooth_gatt_service_service_provider_impl.h"
 #include "device/bluetooth/dbus/bluez_dbus_manager.h"
 #include "device/bluetooth/dbus/fake_bluetooth_gatt_service_service_provider.h"
@@ -28,8 +29,13 @@
     return new BluetoothGattServiceServiceProviderImpl(bus, object_path, uuid,
                                                        is_primary, includes);
   }
+#if defined(USE_REAL_DBUS_CLIENTS)
+  LOG(FATAL) << "Fake is unavailable if USE_REAL_DBUS_CLIENTS is defined.";
+  return nullptr;
+#else
   return new FakeBluetoothGattServiceServiceProvider(object_path, uuid,
                                                      includes);
+#endif  // defined(USE_REAL_DBUS_CLIENTS)
 }
 
 }  // namespace bluez
diff --git a/device/bluetooth/dbus/bluetooth_le_advertisement_service_provider.cc b/device/bluetooth/dbus/bluetooth_le_advertisement_service_provider.cc
index 7f719886f..183dde9 100644
--- a/device/bluetooth/dbus/bluetooth_le_advertisement_service_provider.cc
+++ b/device/bluetooth/dbus/bluetooth_le_advertisement_service_provider.cc
@@ -426,8 +426,13 @@
         std::move(manufacturer_data), std::move(solicit_uuids),
         std::move(service_data));
   }
+#if defined(USE_REAL_DBUS_CLIENTS)
+  LOG(FATAL) << "Fake is unavailable if USE_REAL_DBUS_CLIENTS is defined.";
+  return nullptr;
+#else
   return std::make_unique<FakeBluetoothLEAdvertisementServiceProvider>(
       object_path, delegate);
+#endif  // defined(USE_REAL_DBUS_CLIENTS)
 }
 
 }  // namespace bluez
diff --git a/device/bluetooth/dbus/bluetooth_profile_service_provider.cc b/device/bluetooth/dbus/bluetooth_profile_service_provider.cc
index c4d7eee..cbfc5e6b2 100644
--- a/device/bluetooth/dbus/bluetooth_profile_service_provider.cc
+++ b/device/bluetooth/dbus/bluetooth_profile_service_provider.cc
@@ -250,7 +250,12 @@
   if (!bluez::BluezDBusManager::Get()->IsUsingFakes()) {
     return new BluetoothProfileServiceProviderImpl(bus, object_path, delegate);
   }
+#if defined(USE_REAL_DBUS_CLIENTS)
+  LOG(FATAL) << "Fake is unavailable if USE_REAL_DBUS_CLIENTS is defined.";
+  return nullptr;
+#else
   return new FakeBluetoothProfileServiceProvider(object_path, delegate);
+#endif  // defined(USE_REAL_DBUS_CLIENTS)
 }
 
 }  // namespace bluez
diff --git a/device/bluetooth/device_unittest.cc b/device/bluetooth/device_unittest.cc
index c76794c..0c62528 100644
--- a/device/bluetooth/device_unittest.cc
+++ b/device/bluetooth/device_unittest.cc
@@ -78,32 +78,32 @@
 
     auto service1 = std::make_unique<NiceMockBluetoothGattService>(
         &device_, kTestServiceId0, device::BluetoothUUID(kTestServiceUuid0),
-        true /* is_primary */, false /* is_local */);
+        /*is_primary=*/true);
 
     auto characteristic1 =
         std::make_unique<NiceMockBluetoothGattCharacteristic>(
             service1.get(), kTestCharacteristicId0,
             device::BluetoothUUID(kTestCharacteristicUuid0),
-            false /* is_local */, kReadWriteProperties, 0 /* permissions */);
+            kReadWriteProperties, 0 /* permissions */);
 
     auto characteristic2 =
         std::make_unique<NiceMockBluetoothGattCharacteristic>(
             service1.get(), kTestCharacteristicId1,
             device::BluetoothUUID(kTestCharacteristicUuid1),
-            false /* is_local */, kReadWriteProperties, 0 /* permissions */);
+            kReadWriteProperties, 0 /* permissions */);
 
     service1->AddMockCharacteristic(std::move(characteristic1));
     service1->AddMockCharacteristic(std::move(characteristic2));
 
     auto service2 = std::make_unique<NiceMockBluetoothGattService>(
         &device_, kTestServiceId1, device::BluetoothUUID(kTestServiceUuid1),
-        true /* is_primary */, false /* is_local */);
+        /*is_primary=*/true);
 
     auto characteristic3 =
         std::make_unique<NiceMockBluetoothGattCharacteristic>(
             service2.get(), kTestCharacteristicId2,
-            device::BluetoothUUID(kTestCharacteristicUuid2),
-            false /* is_local */, kAllProperties, 0 /* permissions */);
+            device::BluetoothUUID(kTestCharacteristicUuid2), kAllProperties,
+            0 /* permissions */);
 
     service2->AddMockCharacteristic(std::move(characteristic3));
 
diff --git a/device/bluetooth/test/bluetooth_test.cc b/device/bluetooth/test/bluetooth_test.cc
index 2bb4a6b6..5433822 100644
--- a/device/bluetooth/test/bluetooth_test.cc
+++ b/device/bluetooth/test/bluetooth_test.cc
@@ -407,14 +407,7 @@
   }
 }
 
-base::Closure BluetoothTestBase::GetCallback(Call expected) {
-  if (expected == Call::EXPECTED)
-    ++expected_success_callback_calls_;
-  return base::Bind(&BluetoothTestBase::Callback, weak_factory_.GetWeakPtr(),
-                    expected);
-}
-
-base::OnceClosure BluetoothTestBase::GetOnceCallback(Call expected) {
+base::OnceClosure BluetoothTestBase::GetCallback(Call expected) {
   if (expected == Call::EXPECTED)
     ++expected_success_callback_calls_;
   return base::BindOnce(&BluetoothTestBase::Callback,
@@ -460,18 +453,18 @@
                         weak_factory_.GetWeakPtr(), num_of_preceding_calls);
 }
 
-base::Closure BluetoothTestBase::GetStopNotifyCallback(Call expected) {
+base::OnceClosure BluetoothTestBase::GetStopNotifyCallback(Call expected) {
   if (expected == Call::EXPECTED)
     ++expected_success_callback_calls_;
-  return base::Bind(&BluetoothTestBase::StopNotifyCallback,
-                    weak_factory_.GetWeakPtr(), expected);
+  return base::BindOnce(&BluetoothTestBase::StopNotifyCallback,
+                        weak_factory_.GetWeakPtr(), expected);
 }
 
-base::Closure BluetoothTestBase::GetStopNotifyCheckForPrecedingCalls(
+base::OnceClosure BluetoothTestBase::GetStopNotifyCheckForPrecedingCalls(
     int num_of_preceding_calls) {
   ++expected_success_callback_calls_;
-  return base::Bind(&BluetoothTestBase::StopNotifyCheckForPrecedingCalls,
-                    weak_factory_.GetWeakPtr(), num_of_preceding_calls);
+  return base::BindOnce(&BluetoothTestBase::StopNotifyCheckForPrecedingCalls,
+                        weak_factory_.GetWeakPtr(), num_of_preceding_calls);
 }
 
 BluetoothRemoteGattCharacteristic::ValueCallback
@@ -506,12 +499,12 @@
                         weak_factory_.GetWeakPtr(), expected);
 }
 
-base::Callback<void(BluetoothRemoteGattService::GattErrorCode)>
+base::OnceCallback<void(BluetoothRemoteGattService::GattErrorCode)>
 BluetoothTestBase::GetGattErrorCallback(Call expected) {
   if (expected == Call::EXPECTED)
     ++expected_error_callback_calls_;
-  return base::Bind(&BluetoothTestBase::GattErrorCallback,
-                    weak_factory_.GetWeakPtr(), expected);
+  return base::BindOnce(&BluetoothTestBase::GattErrorCallback,
+                        weak_factory_.GetWeakPtr(), expected);
 }
 
 BluetoothRemoteGattCharacteristic::NotifySessionCallback
@@ -525,14 +518,14 @@
       weak_factory_.GetWeakPtr(), expected, characteristic);
 }
 
-base::Callback<void(BluetoothGattService::GattErrorCode)>
+base::OnceCallback<void(BluetoothGattService::GattErrorCode)>
 BluetoothTestBase::GetReentrantStartNotifySessionErrorCallback(
     Call expected,
     BluetoothRemoteGattCharacteristic* characteristic,
     bool error_in_reentrant) {
   if (expected == Call::EXPECTED)
     ++expected_error_callback_calls_;
-  return base::Bind(
+  return base::BindOnce(
       &BluetoothTestBase::ReentrantStartNotifySessionErrorCallback,
       weak_factory_.GetWeakPtr(), expected, characteristic, error_in_reentrant);
 }
diff --git a/device/bluetooth/test/bluetooth_test.h b/device/bluetooth/test/bluetooth_test.h
index a9e7aee..06fac412 100644
--- a/device/bluetooth/test/bluetooth_test.h
+++ b/device/bluetooth/test/bluetooth_test.h
@@ -632,8 +632,7 @@
       BluetoothGattService::GattErrorCode error_code);
 
   // Accessors to get callbacks bound to this fixture:
-  base::Closure GetCallback(Call expected);
-  base::OnceClosure GetOnceCallback(Call expected);
+  base::OnceClosure GetCallback(Call expected);
   BluetoothAdapter::CreateAdvertisementCallback GetCreateAdvertisementCallback(
       Call expected);
   BluetoothAdapter::DiscoverySessionCallback GetDiscoverySessionCallback(
@@ -644,21 +643,22 @@
       Call expected);
   BluetoothRemoteGattCharacteristic::NotifySessionCallback
   GetNotifyCheckForPrecedingCalls(int num_of_preceding_calls);
-  base::Closure GetStopNotifyCallback(Call expected);
-  base::Closure GetStopNotifyCheckForPrecedingCalls(int num_of_preceding_calls);
+  base::OnceClosure GetStopNotifyCallback(Call expected);
+  base::OnceClosure GetStopNotifyCheckForPrecedingCalls(
+      int num_of_preceding_calls);
   BluetoothRemoteGattCharacteristic::ValueCallback GetReadValueCallback(
       Call expected);
   BluetoothAdapter::ErrorCallback GetErrorCallback(Call expected);
   BluetoothAdapter::AdvertisementErrorCallback GetAdvertisementErrorCallback(
       Call expected);
   BluetoothDevice::ConnectErrorCallback GetConnectErrorCallback(Call expected);
-  base::Callback<void(BluetoothRemoteGattService::GattErrorCode)>
+  base::OnceCallback<void(BluetoothRemoteGattService::GattErrorCode)>
   GetGattErrorCallback(Call expected);
   BluetoothRemoteGattCharacteristic::NotifySessionCallback
   GetReentrantStartNotifySessionSuccessCallback(
       Call expected,
       BluetoothRemoteGattCharacteristic* characteristic);
-  base::Callback<void(BluetoothGattService::GattErrorCode)>
+  base::OnceCallback<void(BluetoothGattService::GattErrorCode)>
   GetReentrantStartNotifySessionErrorCallback(
       Call expected,
       BluetoothRemoteGattCharacteristic* characteristic,
diff --git a/device/bluetooth/test/mock_bluetooth_gatt_characteristic.cc b/device/bluetooth/test/mock_bluetooth_gatt_characteristic.cc
index 6367639..b0ed02b 100644
--- a/device/bluetooth/test/mock_bluetooth_gatt_characteristic.cc
+++ b/device/bluetooth/test/mock_bluetooth_gatt_characteristic.cc
@@ -20,12 +20,10 @@
     MockBluetoothGattService* service,
     const std::string& identifier,
     const BluetoothUUID& uuid,
-    bool is_local,
     Properties properties,
     Permissions permissions) {
   ON_CALL(*this, GetIdentifier()).WillByDefault(Return(identifier));
   ON_CALL(*this, GetUUID()).WillByDefault(Return(uuid));
-  ON_CALL(*this, IsLocal()).WillByDefault(Return(is_local));
   ON_CALL(*this, GetValue())
       .WillByDefault(ReturnRefOfCopy(std::vector<uint8_t>()));
   ON_CALL(*this, GetService()).WillByDefault(Return(service));
diff --git a/device/bluetooth/test/mock_bluetooth_gatt_characteristic.h b/device/bluetooth/test/mock_bluetooth_gatt_characteristic.h
index 1152c36..2924a6af 100644
--- a/device/bluetooth/test/mock_bluetooth_gatt_characteristic.h
+++ b/device/bluetooth/test/mock_bluetooth_gatt_characteristic.h
@@ -32,14 +32,12 @@
   MockBluetoothGattCharacteristic(MockBluetoothGattService* service,
                                   const std::string& identifier,
                                   const BluetoothUUID& uuid,
-                                  bool is_local,
                                   Properties properties,
                                   Permissions permissions);
   ~MockBluetoothGattCharacteristic() override;
 
   MOCK_CONST_METHOD0(GetIdentifier, std::string());
   MOCK_CONST_METHOD0(GetUUID, BluetoothUUID());
-  MOCK_CONST_METHOD0(IsLocal, bool());
   MOCK_CONST_METHOD0(GetValue, const std::vector<uint8_t>&());
   MOCK_CONST_METHOD0(GetService, BluetoothRemoteGattService*());
   MOCK_CONST_METHOD0(GetProperties, Properties());
@@ -49,7 +47,6 @@
                      std::vector<BluetoothRemoteGattDescriptor*>());
   MOCK_CONST_METHOD1(GetDescriptor,
                      BluetoothRemoteGattDescriptor*(const std::string&));
-  MOCK_METHOD1(UpdateValue, bool(const std::vector<uint8_t>&));
 #if defined(OS_CHROMEOS)
   void StartNotifySession(NotificationType t,
                           NotifySessionCallback c,
diff --git a/device/bluetooth/test/mock_bluetooth_gatt_descriptor.cc b/device/bluetooth/test/mock_bluetooth_gatt_descriptor.cc
index b7f2798..b60e5d2 100644
--- a/device/bluetooth/test/mock_bluetooth_gatt_descriptor.cc
+++ b/device/bluetooth/test/mock_bluetooth_gatt_descriptor.cc
@@ -15,11 +15,9 @@
     MockBluetoothGattCharacteristic* characteristic,
     const std::string& identifier,
     const BluetoothUUID& uuid,
-    bool is_local,
     BluetoothRemoteGattCharacteristic::Permissions permissions) {
   ON_CALL(*this, GetIdentifier()).WillByDefault(Return(identifier));
   ON_CALL(*this, GetUUID()).WillByDefault(Return(uuid));
-  ON_CALL(*this, IsLocal()).WillByDefault(Return(is_local));
   ON_CALL(*this, GetValue())
       .WillByDefault(ReturnRefOfCopy(std::vector<uint8_t>()));
   ON_CALL(*this, GetCharacteristic()).WillByDefault(Return(characteristic));
diff --git a/device/bluetooth/test/mock_bluetooth_gatt_descriptor.h b/device/bluetooth/test/mock_bluetooth_gatt_descriptor.h
index b4216f9..e1de291 100644
--- a/device/bluetooth/test/mock_bluetooth_gatt_descriptor.h
+++ b/device/bluetooth/test/mock_bluetooth_gatt_descriptor.h
@@ -27,13 +27,11 @@
       MockBluetoothGattCharacteristic* characteristic,
       const std::string& identifier,
       const BluetoothUUID& uuid,
-      bool is_local,
       BluetoothRemoteGattCharacteristic::Permissions permissions);
   ~MockBluetoothGattDescriptor() override;
 
   MOCK_CONST_METHOD0(GetIdentifier, std::string());
   MOCK_CONST_METHOD0(GetUUID, BluetoothUUID());
-  MOCK_CONST_METHOD0(IsLocal, bool());
   MOCK_CONST_METHOD0(GetValue, const std::vector<uint8_t>&());
   MOCK_CONST_METHOD0(GetCharacteristic, BluetoothRemoteGattCharacteristic*());
   MOCK_CONST_METHOD0(GetPermissions,
diff --git a/device/bluetooth/test/mock_bluetooth_gatt_service.cc b/device/bluetooth/test/mock_bluetooth_gatt_service.cc
index 2539fb6..935266d 100644
--- a/device/bluetooth/test/mock_bluetooth_gatt_service.cc
+++ b/device/bluetooth/test/mock_bluetooth_gatt_service.cc
@@ -19,11 +19,9 @@
     MockBluetoothDevice* device,
     const std::string& identifier,
     const BluetoothUUID& uuid,
-    bool is_primary,
-    bool is_local) {
+    bool is_primary) {
   ON_CALL(*this, GetIdentifier()).WillByDefault(Return(identifier));
   ON_CALL(*this, GetUUID()).WillByDefault(Return(uuid));
-  ON_CALL(*this, IsLocal()).WillByDefault(Return(is_local));
   ON_CALL(*this, IsPrimary()).WillByDefault(Return(is_primary));
   ON_CALL(*this, GetDevice()).WillByDefault(Return(device));
   ON_CALL(*this, GetCharacteristics()).WillByDefault(Invoke([this] {
diff --git a/device/bluetooth/test/mock_bluetooth_gatt_service.h b/device/bluetooth/test/mock_bluetooth_gatt_service.h
index f5c5921c..60c2d96 100644
--- a/device/bluetooth/test/mock_bluetooth_gatt_service.h
+++ b/device/bluetooth/test/mock_bluetooth_gatt_service.h
@@ -25,13 +25,11 @@
   MockBluetoothGattService(MockBluetoothDevice* device,
                            const std::string& identifier,
                            const BluetoothUUID& uuid,
-                           bool is_primary,
-                           bool is_local);
+                           bool is_primary);
   ~MockBluetoothGattService() override;
 
   MOCK_CONST_METHOD0(GetIdentifier, std::string());
   MOCK_CONST_METHOD0(GetUUID, BluetoothUUID());
-  MOCK_CONST_METHOD0(IsLocal, bool());
   MOCK_CONST_METHOD0(IsPrimary, bool());
   MOCK_CONST_METHOD0(GetDevice, BluetoothDevice*());
   MOCK_CONST_METHOD0(GetCharacteristics,
@@ -43,9 +41,6 @@
   MOCK_CONST_METHOD1(
       GetCharacteristicsByUUID,
       std::vector<BluetoothRemoteGattCharacteristic*>(const BluetoothUUID&));
-  MOCK_METHOD1(AddIncludedService, bool(BluetoothRemoteGattService*));
-  MOCK_METHOD2(Register, void(const base::Closure&, const ErrorCallback&));
-  MOCK_METHOD2(Unregister, void(const base::Closure&, const ErrorCallback&));
 
   void AddMockCharacteristic(
       std::unique_ptr<MockBluetoothGattCharacteristic> mock_characteristic);
diff --git a/device/bluetooth/test/mock_bluetooth_socket.h b/device/bluetooth/test/mock_bluetooth_socket.h
index 3e6c57f..ff5cf32 100644
--- a/device/bluetooth/test/mock_bluetooth_socket.h
+++ b/device/bluetooth/test/mock_bluetooth_socket.h
@@ -18,19 +18,19 @@
  public:
   MockBluetoothSocket();
   MOCK_METHOD0(Close, void());
-  MOCK_METHOD1(Disconnect, void(const base::Closure& success_callback));
+  MOCK_METHOD1(Disconnect, void(base::OnceClosure success_callback));
   MOCK_METHOD3(Receive,
                void(int count,
-                    const ReceiveCompletionCallback& success_callback,
-                    const ReceiveErrorCompletionCallback& error_callback));
+                    ReceiveCompletionCallback success_callback,
+                    ReceiveErrorCompletionCallback error_callback));
   MOCK_METHOD4(Send,
                void(scoped_refptr<net::IOBuffer> buffer,
                     int buffer_size,
-                    const SendCompletionCallback& success_callback,
-                    const ErrorCompletionCallback& error_callback));
+                    SendCompletionCallback success_callback,
+                    ErrorCompletionCallback error_callback));
   MOCK_METHOD2(Accept,
-               void(const AcceptCompletionCallback& success_callback,
-                    const ErrorCompletionCallback& error_callback));
+               void(AcceptCompletionCallback success_callback,
+                    ErrorCompletionCallback error_callback));
 
  protected:
   ~MockBluetoothSocket() override;
diff --git a/device/bluetooth/test/test_bluetooth_adapter_observer.h b/device/bluetooth/test/test_bluetooth_adapter_observer.h
index e65e0bb4..b6022785 100644
--- a/device/bluetooth/test/test_bluetooth_adapter_observer.h
+++ b/device/bluetooth/test/test_bluetooth_adapter_observer.h
@@ -250,8 +250,8 @@
   device::BluetoothDevice::ServiceDataMap last_service_data_map_;
   device::BluetoothDevice::ManufacturerDataMap last_manufacturer_data_map_;
 
-  base::Closure discovering_changed_callback_;
-  base::Closure discovery_change_completed_callback_;
+  base::RepeatingClosure discovering_changed_callback_;
+  base::RepeatingClosure discovery_change_completed_callback_;
 
 #if defined(OS_CHROMEOS) || defined(OS_LINUX)
   int device_paired_changed_count_;
diff --git a/device/fido/cable/fido_ble_connection_unittest.cc b/device/fido/cable/fido_ble_connection_unittest.cc
index 49f25b9..e964194 100644
--- a/device/fido/cable/fido_ble_connection_unittest.cc
+++ b/device/fido/cable/fido_ble_connection_unittest.cc
@@ -329,16 +329,15 @@
   void AddFidoService() {
     auto fido_service = std::make_unique<NiceMockBluetoothGattService>(
         fido_device_, "fido_service", BluetoothUUID(kFidoServiceUUID),
-        /* is_primary */ true, /* is_local */ false);
+        /*is_primary=*/true);
     fido_service_ = fido_service.get();
     fido_device_->AddMockService(std::move(fido_service));
 
-    static constexpr bool kIsLocal = false;
     {
       auto fido_control_point =
           std::make_unique<NiceMockBluetoothGattCharacteristic>(
               fido_service_, "fido_control_point",
-              BluetoothUUID(kFidoControlPointUUID), kIsLocal,
+              BluetoothUUID(kFidoControlPointUUID),
               BluetoothGattCharacteristic::PROPERTY_WRITE_WITHOUT_RESPONSE,
               BluetoothGattCharacteristic::PERMISSION_NONE);
       fido_control_point_ = fido_control_point.get();
@@ -348,7 +347,7 @@
     {
       auto fido_status = std::make_unique<NiceMockBluetoothGattCharacteristic>(
           fido_service_, "fido_status", BluetoothUUID(kFidoStatusUUID),
-          kIsLocal, BluetoothGattCharacteristic::PROPERTY_NOTIFY,
+          BluetoothGattCharacteristic::PROPERTY_NOTIFY,
           BluetoothGattCharacteristic::PERMISSION_NONE);
       fido_status_ = fido_status.get();
       fido_service_->AddMockCharacteristic(std::move(fido_status));
@@ -358,7 +357,7 @@
       auto fido_control_point_length =
           std::make_unique<NiceMockBluetoothGattCharacteristic>(
               fido_service_, "fido_control_point_length",
-              BluetoothUUID(kFidoControlPointLengthUUID), kIsLocal,
+              BluetoothUUID(kFidoControlPointLengthUUID),
               BluetoothGattCharacteristic::PROPERTY_READ,
               BluetoothGattCharacteristic::PERMISSION_NONE);
       fido_control_point_length_ = fido_control_point_length.get();
@@ -370,7 +369,7 @@
       auto fido_service_revision =
           std::make_unique<NiceMockBluetoothGattCharacteristic>(
               fido_service_, "fido_service_revision",
-              BluetoothUUID(kFidoServiceRevisionUUID), kIsLocal,
+              BluetoothUUID(kFidoServiceRevisionUUID),
               BluetoothGattCharacteristic::PROPERTY_READ,
               BluetoothGattCharacteristic::PERMISSION_NONE);
       fido_service_revision_ = fido_service_revision.get();
@@ -381,7 +380,7 @@
       auto fido_service_revision_bitfield =
           std::make_unique<NiceMockBluetoothGattCharacteristic>(
               fido_service_, "fido_service_revision_bitfield",
-              BluetoothUUID(kFidoServiceRevisionBitfieldUUID), kIsLocal,
+              BluetoothUUID(kFidoServiceRevisionBitfieldUUID),
               BluetoothGattCharacteristic::PROPERTY_READ |
                   BluetoothGattCharacteristic::PROPERTY_WRITE,
               BluetoothGattCharacteristic::PERMISSION_NONE);
diff --git a/device/vr/android/java/src/org/chromium/device/vr/NonPresentingGvrContext.java b/device/vr/android/java/src/org/chromium/device/vr/NonPresentingGvrContext.java
index 3eb8e8e..bb31aec 100644
--- a/device/vr/android/java/src/org/chromium/device/vr/NonPresentingGvrContext.java
+++ b/device/vr/android/java/src/org/chromium/device/vr/NonPresentingGvrContext.java
@@ -5,13 +5,13 @@
 package org.chromium.device.vr;
 
 import android.content.Context;
-import android.os.StrictMode;
 import android.view.Display;
 
 import com.google.vr.cardboard.DisplaySynchronizer;
 import com.google.vr.ndk.base.GvrApi;
 
 import org.chromium.base.ContextUtils;
+import org.chromium.base.StrictModeContext;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.base.annotations.NativeMethods;
@@ -34,20 +34,20 @@
         mNativeGvrDevice = nativeGvrDevice;
         Context context = ContextUtils.getApplicationContext();
         Display display = DisplayAndroidManager.getDefaultDisplayForContext(context);
-        mDisplaySynchronizer = new DisplaySynchronizer(context, display) {
-            @Override
-            public void onConfigurationChanged() {
-                super.onConfigurationChanged();
-                onDisplayConfigurationChanged();
-            }
-        };
+
+        try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
+            mDisplaySynchronizer = new DisplaySynchronizer(context, display) {
+                @Override
+                public void onConfigurationChanged() {
+                    super.onConfigurationChanged();
+                    onDisplayConfigurationChanged();
+                }
+            };
+        }
 
         // Creating the GvrApi can sometimes create the Daydream config file.
-        StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
-        try {
+        try (StrictModeContext ignored = StrictModeContext.allowDiskWrites()) {
             mGvrApi = new GvrApi(context, mDisplaySynchronizer);
-        } finally {
-            StrictMode.setThreadPolicy(oldPolicy);
         }
         resume();
     }
diff --git a/docs/optional.md b/docs/optional.md
index fee3a1a4..42f89609 100644
--- a/docs/optional.md
+++ b/docs/optional.md
@@ -27,7 +27,7 @@
 ```C++
 base::Optional<int> opt;
 opt == true; // false
-opt.value(); // illegal, will DCHECK
+opt.value(); // illegal, will CHECK
 opt == base::nullopt; // true
 ```
 
diff --git a/extensions/browser/api/automation_internal/automation_internal_api.cc b/extensions/browser/api/automation_internal/automation_internal_api.cc
index d8d5499..e0ae655 100644
--- a/extensions/browser/api/automation_internal/automation_internal_api.cc
+++ b/extensions/browser/api/automation_internal/automation_internal_api.cc
@@ -36,7 +36,7 @@
 #include "extensions/common/manifest_handlers/automation.h"
 #include "extensions/common/permissions/permissions_data.h"
 #include "ui/accessibility/ax_action_data.h"
-#include "ui/accessibility/ax_action_handler.h"
+#include "ui/accessibility/ax_action_handler_base.h"
 #include "ui/accessibility/ax_enum_util.h"
 #include "ui/accessibility/ax_tree_id_registry.h"
 
@@ -299,7 +299,8 @@
 
   ui::AXTreeID ax_tree_id = ui::AXTreeID::FromString(params->tree_id);
   ui::AXTreeIDRegistry* registry = ui::AXTreeIDRegistry::GetInstance();
-  ui::AXActionHandler* action_handler = registry->GetActionHandler(ax_tree_id);
+  ui::AXActionHandlerBase* action_handler =
+      registry->GetActionHandler(ax_tree_id);
   if (action_handler) {
     // Explicitly invalidate the pre-existing source tree first. This ensures
     // the source tree sends a complete tree when the next event occurs. This
@@ -535,7 +536,7 @@
   std::unique_ptr<Params> params(Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params.get());
   ui::AXTreeIDRegistry* registry = ui::AXTreeIDRegistry::GetInstance();
-  ui::AXActionHandler* action_handler = registry->GetActionHandler(
+  ui::AXActionHandlerBase* action_handler = registry->GetActionHandler(
       ui::AXTreeID::FromString(params->args.tree_id));
   if (action_handler) {
     // Handle an AXActionHandler with a rfh first. Some actions require a rfh ->
diff --git a/extensions/browser/api/networking_private/networking_private_api.cc b/extensions/browser/api/networking_private/networking_private_api.cc
index 3a70339..ef2ad1c 100644
--- a/extensions/browser/api/networking_private/networking_private_api.cc
+++ b/extensions/browser/api/networking_private/networking_private_api.cc
@@ -780,40 +780,7 @@
 
 ExtensionFunction::ResponseAction
 NetworkingPrivateSetWifiTDLSEnabledStateFunction::Run() {
-  // This method is private - as such, it should not be exposed through public
-  // networking.onc API.
-  // TODO(tbarzic): Consider exposing this via separate API.
-  // http://crbug.com/678737
-  if (!HasPrivateNetworkingAccess(extension(), source_context_type(),
-                                  source_url())) {
-    return RespondNow(Error(kPrivateOnlyError));
-  }
-  std::unique_ptr<private_api::SetWifiTDLSEnabledState::Params> params =
-      private_api::SetWifiTDLSEnabledState::Params::Create(*args_);
-  EXTENSION_FUNCTION_VALIDATE(params);
-
-  GetDelegate(browser_context())
-      ->SetWifiTDLSEnabledState(
-          params->ip_or_mac_address, params->enabled,
-          base::Bind(&NetworkingPrivateSetWifiTDLSEnabledStateFunction::Success,
-                     this),
-          base::Bind(&NetworkingPrivateSetWifiTDLSEnabledStateFunction::Failure,
-                     this));
-  // Success() or Failure() might have been called synchronously at this point.
-  // In that case this function has already called Respond(). Return
-  // AlreadyResponded() in that case.
-  return did_respond() ? AlreadyResponded() : RespondLater();
-}
-
-void NetworkingPrivateSetWifiTDLSEnabledStateFunction::Success(
-    const std::string& result) {
-  Respond(ArgumentList(
-      private_api::SetWifiTDLSEnabledState::Results::Create(result)));
-}
-
-void NetworkingPrivateSetWifiTDLSEnabledStateFunction::Failure(
-    const std::string& error) {
-  Respond(Error(error));
+  return RespondNow(Error("Not supported"));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -825,40 +792,7 @@
 
 ExtensionFunction::ResponseAction
 NetworkingPrivateGetWifiTDLSStatusFunction::Run() {
-  // This method is private - as such, it should not be exposed through public
-  // networking.onc API.
-  // TODO(tbarzic): Consider exposing this via separate API.
-  // http://crbug.com/678737
-  if (!HasPrivateNetworkingAccess(extension(), source_context_type(),
-                                  source_url())) {
-    return RespondNow(Error(kPrivateOnlyError));
-  }
-  std::unique_ptr<private_api::GetWifiTDLSStatus::Params> params =
-      private_api::GetWifiTDLSStatus::Params::Create(*args_);
-  EXTENSION_FUNCTION_VALIDATE(params);
-
-  GetDelegate(browser_context())
-      ->GetWifiTDLSStatus(
-          params->ip_or_mac_address,
-          base::Bind(&NetworkingPrivateGetWifiTDLSStatusFunction::Success,
-                     this),
-          base::Bind(&NetworkingPrivateGetWifiTDLSStatusFunction::Failure,
-                     this));
-  // Success() or Failure() might have been called synchronously at this point.
-  // In that case this function has already called Respond(). Return
-  // AlreadyResponded() in that case.
-  return did_respond() ? AlreadyResponded() : RespondLater();
-}
-
-void NetworkingPrivateGetWifiTDLSStatusFunction::Success(
-    const std::string& result) {
-  Respond(
-      ArgumentList(private_api::GetWifiTDLSStatus::Results::Create(result)));
-}
-
-void NetworkingPrivateGetWifiTDLSStatusFunction::Failure(
-    const std::string& error) {
-  Respond(Error(error));
+  return RespondNow(Error("Not supported"));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/extensions/browser/api/networking_private/networking_private_api.h b/extensions/browser/api/networking_private/networking_private_api.h
index 591c8ec..7a8891e 100644
--- a/extensions/browser/api/networking_private/networking_private_api.h
+++ b/extensions/browser/api/networking_private/networking_private_api.h
@@ -375,7 +375,7 @@
   DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateVerifyAndEncryptDataFunction);
 };
 
-// Implements the chrome.networkingPrivate.setWifiTDLSEnabledState method.
+// Deprecated chrome.networkingPrivate.setWifiTDLSEnabledState method.
 class NetworkingPrivateSetWifiTDLSEnabledStateFunction
     : public ExtensionFunction {
  public:
@@ -389,14 +389,11 @@
   // ExtensionFunction:
   ResponseAction Run() override;
 
-  void Success(const std::string& result);
-  void Failure(const std::string& error);
-
  private:
   DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateSetWifiTDLSEnabledStateFunction);
 };
 
-// Implements the chrome.networkingPrivate.getWifiTDLSStatus method.
+// Deprecated chrome.networkingPrivate.getWifiTDLSStatus method.
 class NetworkingPrivateGetWifiTDLSStatusFunction : public ExtensionFunction {
  public:
   NetworkingPrivateGetWifiTDLSStatusFunction() {}
@@ -409,9 +406,6 @@
   // ExtensionFunction:
   ResponseAction Run() override;
 
-  void Success(const std::string& result);
-  void Failure(const std::string& error);
-
  private:
   DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateGetWifiTDLSStatusFunction);
 };
diff --git a/extensions/browser/api/networking_private/networking_private_chromeos.cc b/extensions/browser/api/networking_private/networking_private_chromeos.cc
index 33f990e..2ca1b83 100644
--- a/extensions/browser/api/networking_private/networking_private_chromeos.cc
+++ b/extensions/browser/api/networking_private/networking_private_chromeos.cc
@@ -523,25 +523,6 @@
   success_callback.Run();
 }
 
-void NetworkingPrivateChromeOS::SetWifiTDLSEnabledState(
-    const std::string& ip_or_mac_address,
-    bool enabled,
-    const StringCallback& success_callback,
-    const FailureCallback& failure_callback) {
-  NetworkHandler::Get()->network_device_handler()->SetWifiTDLSEnabled(
-      ip_or_mac_address, enabled, success_callback,
-      base::Bind(&NetworkHandlerFailureCallback, failure_callback));
-}
-
-void NetworkingPrivateChromeOS::GetWifiTDLSStatus(
-    const std::string& ip_or_mac_address,
-    const StringCallback& success_callback,
-    const FailureCallback& failure_callback) {
-  NetworkHandler::Get()->network_device_handler()->GetWifiTDLSStatus(
-      ip_or_mac_address, success_callback,
-      base::Bind(&NetworkHandlerFailureCallback, failure_callback));
-}
-
 void NetworkingPrivateChromeOS::GetCaptivePortalStatus(
     const std::string& guid,
     const StringCallback& success_callback,
diff --git a/extensions/browser/api/networking_private/networking_private_chromeos.h b/extensions/browser/api/networking_private/networking_private_chromeos.h
index 6942d8d..fc8be6f 100644
--- a/extensions/browser/api/networking_private/networking_private_chromeos.h
+++ b/extensions/browser/api/networking_private/networking_private_chromeos.h
@@ -69,14 +69,6 @@
                      const std::string& carrier,
                      const VoidCallback& success_callback,
                      const FailureCallback& failure_callback) override;
-  void SetWifiTDLSEnabledState(
-      const std::string& ip_or_mac_address,
-      bool enabled,
-      const StringCallback& success_callback,
-      const FailureCallback& failure_callback) override;
-  void GetWifiTDLSStatus(const std::string& ip_or_mac_address,
-                         const StringCallback& success_callback,
-                         const FailureCallback& failure_callback) override;
   void GetCaptivePortalStatus(const std::string& guid,
                               const StringCallback& success_callback,
                               const FailureCallback& failure_callback) override;
diff --git a/extensions/browser/api/networking_private/networking_private_delegate.h b/extensions/browser/api/networking_private/networking_private_delegate.h
index 35b4bf2e..59f2da4 100644
--- a/extensions/browser/api/networking_private/networking_private_delegate.h
+++ b/extensions/browser/api/networking_private/networking_private_delegate.h
@@ -98,14 +98,6 @@
                              const std::string& carrier,
                              const VoidCallback& success_callback,
                              const FailureCallback& failure_callback);
-  virtual void SetWifiTDLSEnabledState(
-      const std::string& ip_or_mac_address,
-      bool enabled,
-      const StringCallback& success_callback,
-      const FailureCallback& failure_callback) = 0;
-  virtual void GetWifiTDLSStatus(const std::string& ip_or_mac_address,
-                                 const StringCallback& success_callback,
-                                 const FailureCallback& failure_callback) = 0;
   virtual void GetCaptivePortalStatus(
       const std::string& guid,
       const StringCallback& success_callback,
diff --git a/extensions/browser/api/networking_private/networking_private_linux.cc b/extensions/browser/api/networking_private/networking_private_linux.cc
index 0a0bcd94..a356a32 100644
--- a/extensions/browser/api/networking_private/networking_private_linux.cc
+++ b/extensions/browser/api/networking_private/networking_private_linux.cc
@@ -542,21 +542,6 @@
                      success_callback, failure_callback));
 }
 
-void NetworkingPrivateLinux::SetWifiTDLSEnabledState(
-    const std::string& ip_or_mac_address,
-    bool enabled,
-    const StringCallback& success_callback,
-    const FailureCallback& failure_callback) {
-  ReportNotSupported("SetWifiTDLSEnabledState", failure_callback);
-}
-
-void NetworkingPrivateLinux::GetWifiTDLSStatus(
-    const std::string& ip_or_mac_address,
-    const StringCallback& success_callback,
-    const FailureCallback& failure_callback) {
-  ReportNotSupported("GetWifiTDLSStatus", failure_callback);
-}
-
 void NetworkingPrivateLinux::GetCaptivePortalStatus(
     const std::string& guid,
     const StringCallback& success_callback,
diff --git a/extensions/browser/api/networking_private/networking_private_linux.h b/extensions/browser/api/networking_private/networking_private_linux.h
index e5f9939..75528d5 100644
--- a/extensions/browser/api/networking_private/networking_private_linux.h
+++ b/extensions/browser/api/networking_private/networking_private_linux.h
@@ -70,14 +70,6 @@
   void StartDisconnect(const std::string& guid,
                        const VoidCallback& success_callback,
                        const FailureCallback& failure_callback) override;
-  void SetWifiTDLSEnabledState(
-      const std::string& ip_or_mac_address,
-      bool enabled,
-      const StringCallback& success_callback,
-      const FailureCallback& failure_callback) override;
-  void GetWifiTDLSStatus(const std::string& ip_or_mac_address,
-                         const StringCallback& success_callback,
-                         const FailureCallback& failure_callback) override;
   void GetCaptivePortalStatus(const std::string& guid,
                               const StringCallback& success_callback,
                               const FailureCallback& failure_callback) override;
diff --git a/extensions/browser/api/networking_private/networking_private_service_client.cc b/extensions/browser/api/networking_private/networking_private_service_client.cc
index 862f874..4a168849 100644
--- a/extensions/browser/api/networking_private/networking_private_service_client.cc
+++ b/extensions/browser/api/networking_private/networking_private_service_client.cc
@@ -306,21 +306,6 @@
                      base::Owned(error)));
 }
 
-void NetworkingPrivateServiceClient::SetWifiTDLSEnabledState(
-    const std::string& ip_or_mac_address,
-    bool enabled,
-    const StringCallback& success_callback,
-    const FailureCallback& failure_callback) {
-  failure_callback.Run(networking_private::kErrorNotSupported);
-}
-
-void NetworkingPrivateServiceClient::GetWifiTDLSStatus(
-    const std::string& ip_or_mac_address,
-    const StringCallback& success_callback,
-    const FailureCallback& failure_callback) {
-  failure_callback.Run(networking_private::kErrorNotSupported);
-}
-
 void NetworkingPrivateServiceClient::GetCaptivePortalStatus(
     const std::string& guid,
     const StringCallback& success_callback,
diff --git a/extensions/browser/api/networking_private/networking_private_service_client.h b/extensions/browser/api/networking_private/networking_private_service_client.h
index 3c918f391..324f1f1b 100644
--- a/extensions/browser/api/networking_private/networking_private_service_client.h
+++ b/extensions/browser/api/networking_private/networking_private_service_client.h
@@ -80,14 +80,6 @@
   void StartDisconnect(const std::string& guid,
                        const VoidCallback& success_callback,
                        const FailureCallback& failure_callback) override;
-  void SetWifiTDLSEnabledState(
-      const std::string& ip_or_mac_address,
-      bool enabled,
-      const StringCallback& success_callback,
-      const FailureCallback& failure_callback) override;
-  void GetWifiTDLSStatus(const std::string& ip_or_mac_address,
-                         const StringCallback& success_callback,
-                         const FailureCallback& failure_callback) override;
   void GetCaptivePortalStatus(const std::string& guid,
                               const StringCallback& success_callback,
                               const FailureCallback& failure_callback) override;
diff --git a/extensions/browser/content_verify_job_unittest.cc b/extensions/browser/content_verify_job_unittest.cc
index 78078dd..bbf6a57 100644
--- a/extensions/browser/content_verify_job_unittest.cc
+++ b/extensions/browser/content_verify_job_unittest.cc
@@ -796,8 +796,8 @@
     verified_contents_ = GetVerifiedContents(extension);
 
     // Delete verified_contents.json.
-    EXPECT_TRUE(base::DeleteFile(
-        file_util::GetVerifiedContentsPath(extension.path()), true));
+    EXPECT_TRUE(base::DeletePathRecursively(
+        file_util::GetVerifiedContentsPath(extension.path())));
 
     // Clear cache so that next extension resource load will fetch hashes as
     // we've already deleted verified_contents.json.
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index 0c20639a6..2aa46a75 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1546,6 +1546,7 @@
   INPUTMETHODPRIVATE_SETAUTOCORRECTRANGE = 1483,
   PLATFORMKEYSINTERNAL_GETPUBLICKEYBYSPKI = 1484,
   CERTIFICATEPROVIDER_SETCERTIFICATES = 1485,
+  AUTOTESTPRIVATE_DISABLEAUTOMATION = 1486,
   // Last entry: Add new entries above, then run:
   // python tools/metrics/histograms/update_extension_histograms.py
   ENUM_BOUNDARY
diff --git a/extensions/browser/service_worker_manager.cc b/extensions/browser/service_worker_manager.cc
index b066e54f..24e13e2 100644
--- a/extensions/browser/service_worker_manager.cc
+++ b/extensions/browser/service_worker_manager.cc
@@ -40,7 +40,8 @@
   // c) Check for any orphaned workers.
   util::GetStoragePartitionForExtensionId(extension->id(), browser_context_)
       ->GetServiceWorkerContext()
-      ->DeleteForOrigin(extension->url(), base::DoNothing());
+      ->DeleteForOrigin(url::Origin::Create(extension->url()),
+                        base::DoNothing());
 }
 
 }  // namespace extensions
diff --git a/extensions/common/api/networking_private.idl b/extensions/common/api/networking_private.idl
index e78030c..48088b6 100644
--- a/extensions/common/api/networking_private.idl
+++ b/extensions/common/api/networking_private.idl
@@ -1064,8 +1064,9 @@
                                      DOMString data,
                                      StringCallback callback);
 
-    // Enables TDLS for WiFi traffic with a specified peer if available.
-    // |ip_or_mac_address|: The IP or MAC address of the peer with which to
+    // Deprecated. Enables TDLS for WiFi traffic with a specified peer if
+    // available. |ip_or_mac_address|: The IP or MAC address of the peer with
+    // which to
     //     enable a TDLS connection.
     // |enabled| If true, enable TDLS, otherwise disable TDLS.
     // |callback|: A callback function that receives a string with an error or
@@ -1073,17 +1074,17 @@
     //     (e.g. MAC address lookup failed). 'Timeout' indicates that the lookup
     //     timed out. Otherwise a valid status is returned (see
     //     $(ref:getWifiTDLSStatus)).
-    [nodoc, deprecated = "Use networking.castPrivate API."]
+    [nodoc, deprecated]
     static void setWifiTDLSEnabledState(DOMString ip_or_mac_address,
                                         boolean enabled,
                                         optional StringCallback callback);
 
-    // Returns the current TDLS status for the specified peer.
+    // Deprecated. Returns the current TDLS status for the specified peer.
     // |ip_or_mac_address|: The IP or MAC address of the peer.
     // |callback|: A callback function that receives a string with the current
     //     TDLS status which can be 'Connected', 'Disabled', 'Disconnected',
     //     'Nonexistent', or 'Unknown'.
-    [nodoc, deprecated = "Use networking.castPrivate API."]
+    [nodoc, deprecated]
     static void getWifiTDLSStatus(DOMString ip_or_mac_address,
                                   StringCallback callback);
 
diff --git a/fuchsia/engine/browser/cookie_manager_impl.cc b/fuchsia/engine/browser/cookie_manager_impl.cc
index d5c05f4..c28b9e0 100644
--- a/fuchsia/engine/browser/cookie_manager_impl.cc
+++ b/fuchsia/engine/browser/cookie_manager_impl.cc
@@ -65,7 +65,8 @@
           ConvertCanonicalCookie(cookie, net::CookieChangeCause::INSERTED);
     }
   }
-  // Same as above except it takes CookieStatusList instead of just CookieList.
+  // Same as above except it takes CookieAccessResultList instead of just
+  // CookieList.
   CookiesIteratorImpl(
       const std::vector<net::CookieWithAccessResult>&
           cookies_with_access_results,
diff --git a/fuchsia/engine/renderer/cast_streaming_demuxer.cc b/fuchsia/engine/renderer/cast_streaming_demuxer.cc
index 92937f3..dc8413ca 100644
--- a/fuchsia/engine/renderer/cast_streaming_demuxer.cc
+++ b/fuchsia/engine/renderer/cast_streaming_demuxer.cc
@@ -121,7 +121,6 @@
     pending_read_cb_ = std::move(read_cb);
     CompletePendingRead();
   }
-  bool IsReadPending() const final { return !pending_read_cb_.is_null(); }
   Liveness liveness() const final { return Liveness::LIVENESS_LIVE; }
   bool SupportsConfigChanges() final { return false; }
 
diff --git a/fuchsia/engine/web_engine_integration_test.cc b/fuchsia/engine/web_engine_integration_test.cc
index 3b250cf..fedcf8c 100644
--- a/fuchsia/engine/web_engine_integration_test.cc
+++ b/fuchsia/engine/web_engine_integration_test.cc
@@ -524,8 +524,7 @@
   EXPECT_FALSE(fake_audio_consumer_service_->instance(0)->is_muted());
 }
 
-// TODO(crbug.com/1090159): Reenable when cadence estimator DCHECK is fixed.
-TEST_F(WebEngineIntegrationTest, DISABLED_PlayVideo) {
+TEST_F(WebEngineIntegrationTest, PlayVideo) {
   StartWebEngine();
   CreateContextAndFrame(ContextParamsWithAudioAndTestData());
 
@@ -588,8 +587,7 @@
   navigation_listener_->RunUntilTitleEquals("ended");
 }
 
-// TODO(crbug.com/1090159): Reenable when cadence estimator DCHECK is fixed.
-TEST_F(WebEngineIntegrationTest, DISABLED_SetBlockMediaLoading_Blocked) {
+TEST_F(WebEngineIntegrationTest, SetBlockMediaLoading_Blocked) {
   StartWebEngine();
   CreateContextAndFrame(ContextParamsWithAudioAndTestData());
 
@@ -607,8 +605,7 @@
 
 // Initially, set media blocking to be true. When media is unblocked, check that
 // it begins playing, since autoplay=true.
-// TODO(crbug.com/1090159): Reenable when cadence estimator DCHECK is fixed.
-TEST_F(WebEngineIntegrationTest, DISABLED_SetBlockMediaLoading_AfterUnblock) {
+TEST_F(WebEngineIntegrationTest, SetBlockMediaLoading_AfterUnblock) {
   StartWebEngine();
   CreateContextAndFrame(ContextParamsWithAudioAndTestData());
 
@@ -628,9 +625,7 @@
 
 // Check that when autoplay=false and media loading was blocked after the
 // element has started loading that media will play when play() is called.
-// TODO(crbug.com/1090159): Reenable when cadence estimator DCHECK is fixed.
-TEST_F(WebEngineIntegrationTest,
-       DISABLED_SetBlockMediaLoading_SetBlockedAfterLoading) {
+TEST_F(WebEngineIntegrationTest, SetBlockMediaLoading_SetBlockedAfterLoading) {
   StartWebEngine();
   CreateContextAndFrame(ContextParamsWithAudioAndTestData());
 
diff --git a/fuchsia/runners/cast/cast_runner.cc b/fuchsia/runners/cast/cast_runner.cc
index fda72d7..7e19858 100644
--- a/fuchsia/runners/cast/cast_runner.cc
+++ b/fuchsia/runners/cast/cast_runner.cc
@@ -213,7 +213,7 @@
   const char kCastPlayreadyKeySystem[] = "com.chromecast.playready";
   params.set_playready_key_system(kCastPlayreadyKeySystem);
 
-  // TODO(b/141956135): Use CrKey version provided by the Agent.
+  // See http://b/141956135.
   params.set_user_agent_product("CrKey");
   params.set_user_agent_version("1.43.000000");
 
diff --git a/gpu/command_buffer/service/external_vk_image_backing.cc b/gpu/command_buffer/service/external_vk_image_backing.cc
index d98d875..d58823c0 100644
--- a/gpu/command_buffer/service/external_vk_image_backing.cc
+++ b/gpu/command_buffer/service/external_vk_image_backing.cc
@@ -119,6 +119,33 @@
   GLuint id_;
 };
 
+bool UseSeparateGLTexture(SharedContextState* context_state,
+                          viz::ResourceFormat format) {
+  if (!context_state->support_vulkan_external_object())
+    return true;
+
+  if (format != viz::ResourceFormat::BGRA_8888)
+    return false;
+
+  const auto* version_info = context_state->real_context()->GetVersionInfo();
+  const auto& ext = gl::g_current_gl_driver->ext;
+  if (!ext.b_GL_EXT_texture_format_BGRA8888)
+    return true;
+
+  if (!version_info->is_angle)
+    return false;
+
+  // If ANGLE is using vulkan, there is no problem for importing BGRA8888
+  // textures.
+  if (version_info->is_angle_vulkan)
+    return false;
+
+  // ANGLE claims GL_EXT_texture_format_BGRA8888, but glTexStorageMem2DEXT
+  // doesn't work correctly.
+  // TODO(crbug.com/angleproject/4831): fix ANGLE and return false.
+  return true;
+}
+
 }  // namespace
 
 // static
@@ -138,7 +165,6 @@
 
   auto* device_queue = context_state->vk_context_provider()->GetDeviceQueue();
   VkFormat vk_format = ToVkFormat(format);
-
   constexpr auto kUsageNeedsColorAttachment =
       SHARED_IMAGE_USAGE_GLES2 | SHARED_IMAGE_USAGE_RASTER |
       SHARED_IMAGE_USAGE_OOP_RASTERIZATION | SHARED_IMAGE_USAGE_WEBGPU;
@@ -169,11 +195,12 @@
   VkImageCreateFlags vk_flags = 0;
 
   // In protected mode mark the image as protected, except when the image needs
-  // GLES2, but not Raster usage. ANGLE currenctly doesn't support protected
+  // GLES2, but not Raster usage. ANGLE currently doesn't support protected
   // images. Some clients request GLES2 and Raster usage (e.g. see
   // GpuMemoryBufferVideoFramePool). In that case still allocate protected
   // image, which ensures that image can still usable, but it may not work in
   // some scenarios (e.g. when the video frame is used in WebGL).
+  // TODO(https://crbug.com/angleproject/4833)
   if (vulkan_implementation->enforce_protected_memory() &&
       (!(usage & SHARED_IMAGE_USAGE_GLES2) ||
        (usage & SHARED_IMAGE_USAGE_RASTER))) {
@@ -192,9 +219,11 @@
   if (!image)
     return nullptr;
 
+  bool use_separate_gl_texture = UseSeparateGLTexture(context_state, format);
   auto backing = std::make_unique<ExternalVkImageBacking>(
       util::PassKey<ExternalVkImageBacking>(), mailbox, format, size,
-      color_space, usage, context_state, std::move(image), command_pool);
+      color_space, usage, context_state, std::move(image), command_pool,
+      use_separate_gl_texture);
 
   if (!pixel_data.empty()) {
     size_t stride = BitsPerPixel(format) / 8 * size.width();
@@ -233,9 +262,12 @@
       return nullptr;
     }
 
+    bool use_separate_gl_texture =
+        UseSeparateGLTexture(context_state, resource_format);
     auto backing = std::make_unique<ExternalVkImageBacking>(
         util::PassKey<ExternalVkImageBacking>(), mailbox, resource_format, size,
-        color_space, usage, context_state, std::move(image), command_pool);
+        color_space, usage, context_state, std::move(image), command_pool,
+        use_separate_gl_texture);
     backing->SetCleared();
     return backing;
   }
@@ -270,7 +302,8 @@
     uint32_t usage,
     SharedContextState* context_state,
     std::unique_ptr<VulkanImage> image,
-    VulkanCommandPool* command_pool)
+    VulkanCommandPool* command_pool,
+    bool use_separate_gl_texture)
     : ClearTrackingSharedImageBacking(mailbox,
                                       format,
                                       size,
@@ -283,7 +316,8 @@
       backend_texture_(size.width(),
                        size.height(),
                        CreateGrVkImageInfo(image_.get())),
-      command_pool_(command_pool) {}
+      command_pool_(command_pool),
+      use_separate_gl_texture_(use_separate_gl_texture) {}
 
 ExternalVkImageBacking::~ExternalVkImageBacking() {
   GrVkImageInfo image_info;
@@ -477,22 +511,6 @@
   }
 
   GLuint internal_format = viz::TextureStorageFormat(format());
-  bool is_bgra8 = (internal_format == GL_BGRA8_EXT);
-  if (is_bgra8) {
-    const auto& ext = gl::g_current_gl_driver->ext;
-    if (!ext.b_GL_EXT_texture_format_BGRA8888) {
-      bool support_swizzle = ext.b_GL_ARB_texture_swizzle ||
-                             ext.b_GL_EXT_texture_swizzle ||
-                             gl::g_current_gl_version->IsAtLeastGL(3, 3) ||
-                             gl::g_current_gl_version->IsAtLeastGLES(3, 0);
-      if (!support_swizzle) {
-        LOG(ERROR) << "BGRA_88888 is not supported.";
-        return 0;
-      }
-      internal_format = GL_RGBA8;
-    }
-  }
-
   GLuint texture_service_id = 0;
   api->glGenTexturesFn(1, &texture_service_id);
   gl::ScopedTextureBinder scoped_texture_binder(GL_TEXTURE_2D,
@@ -512,11 +530,6 @@
                                 memory_object->id(), 0);
   }
 
-  if (is_bgra8 && internal_format == GL_RGBA8) {
-    api->glTexParameteriFn(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
-    api->glTexParameteriFn(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
-  }
-
   return texture_service_id;
 }
 
diff --git a/gpu/command_buffer/service/external_vk_image_backing.h b/gpu/command_buffer/service/external_vk_image_backing.h
index 7afb0b5..442bdaf 100644
--- a/gpu/command_buffer/service/external_vk_image_backing.h
+++ b/gpu/command_buffer/service/external_vk_image_backing.h
@@ -65,7 +65,8 @@
                          uint32_t usage,
                          SharedContextState* context_state,
                          std::unique_ptr<VulkanImage> image,
-                         VulkanCommandPool* command_pool);
+                         VulkanCommandPool* command_pool,
+                         bool use_separate_gl_texture);
 
   ~ExternalVkImageBacking() override;
 
@@ -91,6 +92,7 @@
         ->GetDeviceQueue()
         ->GetVulkanDevice();
   }
+  bool use_separate_gl_texture() const { return use_separate_gl_texture_; }
   bool need_synchronization() const {
     if (usage() & SHARED_IMAGE_USAGE_WEBGPU) {
       return true;
@@ -101,10 +103,6 @@
     }
     return false;
   }
-  bool use_separate_gl_texture() const {
-    return !context_state()->support_vulkan_external_object();
-  }
-
   uint32_t reads_in_progress() const { return reads_in_progress_; }
   uint32_t gl_reads_in_progress() const { return gl_reads_in_progress_; }
 
@@ -185,6 +183,7 @@
   std::unique_ptr<VulkanImage> image_;
   GrBackendTexture backend_texture_;
   VulkanCommandPool* const command_pool_;
+  const bool use_separate_gl_texture_;
 
   SemaphoreHandle write_semaphore_handle_;
   std::vector<SemaphoreHandle> read_semaphore_handles_;
diff --git a/infra/config/generated/commit-queue.cfg b/infra/config/generated/commit-queue.cfg
index 0e764727..9ccc4232 100644
--- a/infra/config/generated/commit-queue.cfg
+++ b/infra/config/generated/commit-queue.cfg
@@ -473,6 +473,10 @@
         experiment_percentage: 50
       }
       builders {
+        name: "chromium/try/fuchsia-fyi-arm64-dbg"
+        includable_only: true
+      }
+      builders {
         name: "chromium/try/fuchsia-fyi-arm64-rel"
         includable_only: true
       }
diff --git a/infra/config/generated/cr-buildbucket.cfg b/infra/config/generated/cr-buildbucket.cfg
index 426ba15..3c7cc63 100644
--- a/infra/config/generated/cr-buildbucket.cfg
+++ b/infra/config/generated/cr-buildbucket.cfg
@@ -6254,12 +6254,11 @@
       name: "ToTLinuxASanLibfuzzer"
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "builderless:1"
+      dimensions: "builder:ToTLinuxASanLibfuzzer"
       dimensions: "cores:32"
       dimensions: "cpu:x86-64"
       dimensions: "os:Ubuntu-16.04"
       dimensions: "pool:luci.chromium.ci"
-      dimensions: "ssd:0"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
         cipd_version: "refs/heads/master"
@@ -7826,7 +7825,7 @@
         cipd_version: "refs/heads/master"
         cmd: "recipes"
       }
-      properties: "{\"$build/chromium_tests\":{\"bucketed_triggers\":true},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.fyi\",\"recipe\":\"chromium\"}"
+      properties: "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.fyi\",\"recipe\":\"chromium\"}"
       execution_timeout_secs: 36000
       build_numbers: YES
       service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
@@ -9458,6 +9457,35 @@
       }
     }
     builders {
+      name: "fuchsia-fyi-arm64-dbg"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "pool:luci.chromium.ci"
+      dimensions: "ssd:0"
+      exe {
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        cmd: "recipes"
+      }
+      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.fyi\",\"recipe\":\"chromium\"}"
+      execution_timeout_secs: 36000
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "luci-resultdb"
+          dataset: "chromium"
+          table: "ci_test_results"
+          test_results {}
+        }
+      }
+    }
+    builders {
       name: "fuchsia-fyi-arm64-rel"
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
@@ -10692,7 +10720,7 @@
         cipd_version: "refs/heads/master"
         cmd: "recipes"
       }
-      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.swangle\",\"recipe\":\"chromium\"}"
+      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.swangle\",\"recipe\":\"angle_chromium\"}"
       execution_timeout_secs: 10800
       build_numbers: YES
       service_account: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
@@ -10721,7 +10749,7 @@
         cipd_version: "refs/heads/master"
         cmd: "recipes"
       }
-      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.swangle\",\"recipe\":\"chromium\"}"
+      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.swangle\",\"recipe\":\"angle_chromium\"}"
       execution_timeout_secs: 10800
       build_numbers: YES
       service_account: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
@@ -10750,7 +10778,7 @@
         cipd_version: "refs/heads/master"
         cmd: "recipes"
       }
-      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.swangle\",\"recipe\":\"chromium\"}"
+      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.swangle\",\"recipe\":\"angle_chromium\"}"
       execution_timeout_secs: 10800
       build_numbers: YES
       service_account: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
@@ -10779,7 +10807,7 @@
         cipd_version: "refs/heads/master"
         cmd: "recipes"
       }
-      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.swangle\",\"recipe\":\"chromium\"}"
+      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.swangle\",\"recipe\":\"angle_chromium\"}"
       execution_timeout_secs: 10800
       build_numbers: YES
       service_account: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
@@ -11180,7 +11208,7 @@
         cipd_version: "refs/heads/master"
         cmd: "recipes"
       }
-      properties: "{\"$build/chromium_tests\":{\"bucketed_triggers\":true},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.fyi\",\"recipe\":\"chromium\"}"
+      properties: "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.fyi\",\"recipe\":\"chromium\"}"
       execution_timeout_secs: 36000
       build_numbers: YES
       service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
@@ -11231,7 +11259,6 @@
       dimensions: "cpu:x86-64"
       dimensions: "os:Mac-10.13"
       dimensions: "pool:luci.chromium.ci"
-      dimensions: "ssd:0"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
         cipd_version: "refs/heads/master"
@@ -11548,7 +11575,7 @@
         cipd_version: "refs/heads/master"
         cmd: "recipes"
       }
-      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.swangle\",\"recipe\":\"chromium\"}"
+      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.swangle\",\"recipe\":\"angle_chromium\"}"
       execution_timeout_secs: 10800
       build_numbers: YES
       service_account: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
@@ -11577,7 +11604,7 @@
         cipd_version: "refs/heads/master"
         cmd: "recipes"
       }
-      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.swangle\",\"recipe\":\"chromium\"}"
+      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.swangle\",\"recipe\":\"angle_chromium\"}"
       execution_timeout_secs: 10800
       build_numbers: YES
       service_account: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
@@ -11606,7 +11633,7 @@
         cipd_version: "refs/heads/master"
         cmd: "recipes"
       }
-      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.swangle\",\"recipe\":\"chromium\"}"
+      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.swangle\",\"recipe\":\"angle_chromium\"}"
       execution_timeout_secs: 10800
       build_numbers: YES
       service_account: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
@@ -11635,7 +11662,7 @@
         cipd_version: "refs/heads/master"
         cmd: "recipes"
       }
-      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.swangle\",\"recipe\":\"chromium\"}"
+      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.swangle\",\"recipe\":\"angle_chromium\"}"
       execution_timeout_secs: 10800
       build_numbers: YES
       service_account: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
@@ -13651,34 +13678,6 @@
       }
     }
     builders {
-      name: "Win10 Tests x64 1803"
-      swarming_host: "chromium-swarm.appspot.com"
-      swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "builder:Win10 Tests x64 1803"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Windows-10"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/master"
-        cmd: "recipes"
-      }
-      properties: "{\"$build/chromium_tests\":{\"bucketed_triggers\":true},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.fyi\",\"recipe\":\"chromium\"}"
-      execution_timeout_secs: 36000
-      build_numbers: YES
-      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      resultdb {
-        enable: true
-        bq_exports {
-          project: "luci-resultdb"
-          dataset: "chromium"
-          table: "ci_test_results"
-          test_results {}
-        }
-      }
-    }
-    builders {
       name: "Win10 x64 Release (NVIDIA)"
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
@@ -14292,35 +14291,6 @@
         }
       }
     }
-    builders {
-      name: "mac-osxbeta-rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "builderless:1"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "pool:luci.chromium.ci"
-      dimensions: "ssd:0"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/master"
-        cmd: "recipes"
-      }
-      properties: "{\"$build/chromium_tests\":{\"bucketed_triggers\":true},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.fyi\",\"recipe\":\"chromium\"}"
-      execution_timeout_secs: 36000
-      build_numbers: YES
-      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      resultdb {
-        enable: true
-        bq_exports {
-          project: "luci-resultdb"
-          dataset: "chromium"
-          table: "ci_test_results"
-          test_results {}
-        }
-      }
-    }
   }
 }
 buckets {
@@ -16095,34 +16065,6 @@
       }
     }
     builders {
-      name: "Win10 Tests x64 1803"
-      swarming_host: "chromium-swarm.appspot.com"
-      swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "builder:Win10 Tests x64 1803"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Windows-10"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/master"
-        cmd: "recipes"
-      }
-      properties: "{\"$build/chromium_tests\":{\"bucketed_triggers\":true},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.fyi\",\"recipe\":\"chromium\"}"
-      execution_timeout_secs: 36000
-      build_numbers: YES
-      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      resultdb {
-        enable: true
-        bq_exports {
-          project: "luci-resultdb"
-          dataset: "chromium"
-          table: "ci_test_results"
-          test_results {}
-        }
-      }
-    }
-    builders {
       name: "Win10 x64 Release (NVIDIA)"
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
@@ -16881,62 +16823,6 @@
         }
       }
     }
-    builders {
-      name: "mac-arm64"
-      swarming_host: "chromium-swarm.appspot.com"
-      swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "builder:mac-arm64"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Mac"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/master"
-        cmd: "recipes"
-      }
-      properties: "{\"$build/chromium_tests\":{\"bucketed_triggers\":true},\"$build/goma\":{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.fyi\",\"recipe\":\"chromium\"}"
-      execution_timeout_secs: 36000
-      build_numbers: YES
-      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      resultdb {
-        enable: true
-        bq_exports {
-          project: "luci-resultdb"
-          dataset: "chromium"
-          table: "ci_test_results"
-          test_results {}
-        }
-      }
-    }
-    builders {
-      name: "mac-osxbeta-rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "builderless:1"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "pool:luci.chromium.ci"
-      dimensions: "ssd:0"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/master"
-        cmd: "recipes"
-      }
-      properties: "{\"$build/chromium_tests\":{\"bucketed_triggers\":true},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"chromium.fyi\",\"recipe\":\"chromium\"}"
-      execution_timeout_secs: 36000
-      build_numbers: YES
-      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      resultdb {
-        enable: true
-        bq_exports {
-          project: "luci-resultdb"
-          dataset: "chromium"
-          table: "ci_test_results"
-          test_results {}
-        }
-      }
-    }
   }
 }
 buckets {
@@ -21212,6 +21098,43 @@
       }
     }
     builders {
+      name: "fuchsia-fyi-arm64-dbg"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "pool:luci.chromium.try"
+      dimensions: "ssd:0"
+      exe {
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        cmd: "recipes"
+      }
+      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"tryserver.chromium.linux\",\"recipe\":\"chromium_trybot\"}"
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches {
+        name: "win_toolchain"
+        path: "win_toolchain"
+      }
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage {
+        value: 5
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "luci-resultdb"
+          dataset: "chromium"
+          table: "try_test_results"
+          test_results {}
+        }
+      }
+    }
+    builders {
       name: "fuchsia-fyi-arm64-rel"
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
@@ -25240,7 +25163,7 @@
         cipd_version: "refs/heads/master"
         cmd: "recipes"
       }
-      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"tryserver.chromium.swangle\",\"recipe\":\"chromium_trybot\"}"
+      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"tryserver.chromium.swangle\",\"recipe\":\"angle_chromium_trybot\"}"
       execution_timeout_secs: 7200
       expiration_secs: 7200
       caches {
@@ -25277,7 +25200,7 @@
         cipd_version: "refs/heads/master"
         cmd: "recipes"
       }
-      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"tryserver.chromium.swangle\",\"recipe\":\"chromium_trybot\"}"
+      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"tryserver.chromium.swangle\",\"recipe\":\"angle_chromium_trybot\"}"
       execution_timeout_secs: 7200
       expiration_secs: 7200
       caches {
@@ -25314,7 +25237,7 @@
         cipd_version: "refs/heads/master"
         cmd: "recipes"
       }
-      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"tryserver.chromium.swangle\",\"recipe\":\"chromium_trybot\"}"
+      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"tryserver.chromium.swangle\",\"recipe\":\"angle_chromium_trybot\"}"
       execution_timeout_secs: 7200
       expiration_secs: 7200
       caches {
@@ -25351,7 +25274,7 @@
         cipd_version: "refs/heads/master"
         cmd: "recipes"
       }
-      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"tryserver.chromium.swangle\",\"recipe\":\"chromium_trybot\"}"
+      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"tryserver.chromium.swangle\",\"recipe\":\"angle_chromium_trybot\"}"
       execution_timeout_secs: 7200
       expiration_secs: 7200
       caches {
@@ -27951,7 +27874,7 @@
         cipd_version: "refs/heads/master"
         cmd: "recipes"
       }
-      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"tryserver.chromium.swangle\",\"recipe\":\"chromium_trybot\"}"
+      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"tryserver.chromium.swangle\",\"recipe\":\"angle_chromium_trybot\"}"
       execution_timeout_secs: 7200
       expiration_secs: 7200
       caches {
@@ -27988,7 +27911,7 @@
         cipd_version: "refs/heads/master"
         cmd: "recipes"
       }
-      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"tryserver.chromium.swangle\",\"recipe\":\"chromium_trybot\"}"
+      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"tryserver.chromium.swangle\",\"recipe\":\"angle_chromium_trybot\"}"
       execution_timeout_secs: 7200
       expiration_secs: 7200
       caches {
@@ -28025,7 +27948,7 @@
         cipd_version: "refs/heads/master"
         cmd: "recipes"
       }
-      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"tryserver.chromium.swangle\",\"recipe\":\"chromium_trybot\"}"
+      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"tryserver.chromium.swangle\",\"recipe\":\"angle_chromium_trybot\"}"
       execution_timeout_secs: 7200
       expiration_secs: 7200
       caches {
@@ -28062,7 +27985,7 @@
         cipd_version: "refs/heads/master"
         cmd: "recipes"
       }
-      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"tryserver.chromium.swangle\",\"recipe\":\"chromium_trybot\"}"
+      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"mastername\":\"tryserver.chromium.swangle\",\"recipe\":\"angle_chromium_trybot\"}"
       execution_timeout_secs: 7200
       expiration_secs: 7200
       caches {
diff --git a/infra/config/generated/luci-milo.cfg b/infra/config/generated/luci-milo.cfg
index 94d5b3f..5403510 100644
--- a/infra/config/generated/luci-milo.cfg
+++ b/infra/config/generated/luci-milo.cfg
@@ -61,6 +61,16 @@
     short_name: "64"
   }
   builders {
+    name: "buildbucket/luci.chromium.ci/win32-official"
+    category: "chromium|win|off"
+    short_name: "32"
+  }
+  builders {
+    name: "buildbucket/luci.chromium.ci/win-official"
+    category: "chromium|win|off"
+    short_name: "64"
+  }
+  builders {
     name: "buildbucket/luci.chromium.ci/win32-archive-rel"
     category: "chromium|win|rel"
     short_name: "32"
@@ -1121,11 +1131,6 @@
     category: "chromium.fyi|cronet"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci-m84/mac-osxbeta-rel"
-    category: "chromium.fyi|mac"
-    short_name: "beta"
-  }
-  builders {
     name: "buildbucket/luci.chromium.ci-m84/VR Linux"
     category: "chromium.fyi|linux"
   }
@@ -1144,10 +1149,6 @@
     category: "chromium.fyi|linux"
     short_name: "lox"
   }
-  builders {
-    name: "buildbucket/luci.chromium.ci-m84/Win10 Tests x64 1803"
-    category: "chromium.fyi|win10|1803"
-  }
   header {
     oncalls {
       name: "Chromium"
@@ -1809,16 +1810,6 @@
     category: "chromium.fyi|cronet"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci-m85/mac-arm64"
-    category: "chromium.fyi|mac"
-    short_name: "arm64"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.ci-m85/mac-osxbeta-rel"
-    category: "chromium.fyi|mac"
-    short_name: "beta"
-  }
-  builders {
     name: "buildbucket/luci.chromium.ci-m85/fuchsia-official"
     category: "chromium.fyi|fuchsia"
     short_name: "off"
@@ -1848,10 +1839,6 @@
     short_name: "off"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci-m85/Win10 Tests x64 1803"
-    category: "chromium.fyi|win10|1803"
-  }
-  builders {
     name: "buildbucket/luci.chromium.ci-m85/android-official"
     category: "chromium.fyi|android"
     short_name: "off"
@@ -2136,6 +2123,16 @@
     short_name: "off"
   }
   builders {
+    name: "buildbucket/luci.chromium.ci/win32-official"
+    category: "chromium|win|off"
+    short_name: "32"
+  }
+  builders {
+    name: "buildbucket/luci.chromium.ci/win-official"
+    category: "chromium|win|off"
+    short_name: "64"
+  }
+  builders {
     name: "buildbucket/luci.chromium.ci/Win x64 Builder"
     category: "chromium.win|release|builder"
     short_name: "64"
@@ -2532,11 +2529,6 @@
     short_name: "arm64"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci/mac-osxbeta-rel"
-    category: "chromium.fyi|mac"
-    short_name: "beta"
-  }
-  builders {
     name: "buildbucket/luci.chromium.ci/VR Linux"
     category: "chromium.fyi|linux"
   }
@@ -2560,20 +2552,6 @@
     category: "chromium.fyi|linux"
     short_name: "off"
   }
-  builders {
-    name: "buildbucket/luci.chromium.ci/Win10 Tests x64 1803"
-    category: "chromium.fyi|win10|1803"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.ci/win32-official"
-    category: "chromium.fyi|win|off"
-    short_name: "32"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.ci/win-official"
-    category: "chromium.fyi|win|off"
-    short_name: "64"
-  }
   header {
     oncalls {
       name: "Chromium"
@@ -3225,11 +3203,6 @@
     category: "chromium.fyi|cronet"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci-m84/mac-osxbeta-rel"
-    category: "chromium.fyi|mac"
-    short_name: "beta"
-  }
-  builders {
     name: "buildbucket/luci.chromium.ci-m84/VR Linux"
     category: "chromium.fyi|linux"
   }
@@ -3248,10 +3221,6 @@
     category: "chromium.fyi|linux"
     short_name: "lox"
   }
-  builders {
-    name: "buildbucket/luci.chromium.ci-m84/Win10 Tests x64 1803"
-    category: "chromium.fyi|win10|1803"
-  }
   header {
     oncalls {
       name: "Chromium"
@@ -3913,16 +3882,6 @@
     category: "chromium.fyi|cronet"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci-m85/mac-arm64"
-    category: "chromium.fyi|mac"
-    short_name: "arm64"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.ci-m85/mac-osxbeta-rel"
-    category: "chromium.fyi|mac"
-    short_name: "beta"
-  }
-  builders {
     name: "buildbucket/luci.chromium.ci-m85/fuchsia-official"
     category: "chromium.fyi|fuchsia"
     short_name: "off"
@@ -3952,10 +3911,6 @@
     short_name: "off"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci-m85/Win10 Tests x64 1803"
-    category: "chromium.fyi|win10|1803"
-  }
-  builders {
     name: "buildbucket/luci.chromium.ci-m85/android-official"
     category: "chromium.fyi|android"
     short_name: "off"
@@ -5368,6 +5323,16 @@
     short_name: "64"
   }
   builders {
+    name: "buildbucket/luci.chromium.ci/win32-official"
+    category: "win|off"
+    short_name: "32"
+  }
+  builders {
+    name: "buildbucket/luci.chromium.ci/win-official"
+    category: "win|off"
+    short_name: "64"
+  }
+  builders {
     name: "buildbucket/luci.chromium.ci/win32-archive-rel"
     category: "win|rel"
     short_name: "32"
@@ -8115,6 +8080,11 @@
     short_name: "dbg"
   }
   builders {
+    name: "buildbucket/luci.chromium.ci/fuchsia-fyi-arm64-dbg"
+    category: "fuchsia|a64"
+    short_name: "dbg"
+  }
+  builders {
     name: "buildbucket/luci.chromium.ci/fuchsia-fyi-arm64-rel"
     category: "fuchsia|a64"
     short_name: "rel"
@@ -8347,16 +8317,6 @@
     category: "perfetto"
     short_name: "win"
   }
-  builders {
-    name: "buildbucket/luci.chromium.ci/win32-official"
-    category: "win|off"
-    short_name: "32"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.ci/win-official"
-    category: "win|off"
-    short_name: "64"
-  }
   header {
     oncalls {
       name: "Chromium"
@@ -15892,6 +15852,9 @@
     name: "buildbucket/luci.chromium.try/fuchsia-compile-x64-dbg"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/fuchsia-fyi-arm64-dbg"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/fuchsia-fyi-arm64-rel"
   }
   builders {
diff --git a/infra/config/generated/luci-notify.cfg b/infra/config/generated/luci-notify.cfg
index 3377dc7..e092ec7 100644
--- a/infra/config/generated/luci-notify.cfg
+++ b/infra/config/generated/luci-notify.cfg
@@ -100,14 +100,15 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+gpu-test-tree-closing-notifier@chromium.org"
+      recipients: "chrome-gpu-build-failures@google.com"
+      rotation_urls: "https://rota-ng.appspot.com/legacy/sheriff_gpu.json"
     }
   }
   builders {
@@ -151,7 +152,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -176,7 +177,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -259,7 +260,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -310,7 +311,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -335,7 +336,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -366,7 +367,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -397,14 +398,15 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+gpu-test-tree-closing-notifier@chromium.org"
+      recipients: "chrome-gpu-build-failures@google.com"
+      rotation_urls: "https://rota-ng.appspot.com/legacy/sheriff_gpu.json"
     }
   }
   builders {
@@ -422,14 +424,15 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+gpu-test-tree-closing-notifier@chromium.org"
+      recipients: "chrome-gpu-build-failures@google.com"
+      rotation_urls: "https://rota-ng.appspot.com/legacy/sheriff_gpu.json"
     }
   }
   builders {
@@ -447,14 +450,15 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+gpu-test-tree-closing-notifier@chromium.org"
+      recipients: "chrome-gpu-build-failures@google.com"
+      rotation_urls: "https://rota-ng.appspot.com/legacy/sheriff_gpu.json"
     }
   }
   builders {
@@ -641,7 +645,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -666,7 +670,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -690,7 +694,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -714,7 +718,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -739,7 +743,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -764,7 +768,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -789,7 +793,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -814,7 +818,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -839,7 +843,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -863,7 +867,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -881,7 +885,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -906,7 +910,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -930,7 +934,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -955,7 +959,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -979,7 +983,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -1003,7 +1007,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -1027,7 +1031,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -1051,14 +1055,15 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+gpu-test-tree-closing-notifier@chromium.org"
+      recipients: "chrome-gpu-build-failures@google.com"
+      rotation_urls: "https://rota-ng.appspot.com/legacy/sheriff_gpu.json"
     }
   }
   builders {
@@ -1075,7 +1080,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -1100,7 +1105,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -1124,7 +1129,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -1148,7 +1153,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -1224,7 +1229,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1242,7 +1247,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1259,7 +1264,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1277,7 +1282,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1295,14 +1300,15 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+gpu-test-tree-closing-notifier@chromium.org"
+      recipients: "chrome-gpu-build-failures@google.com"
+      rotation_urls: "https://rota-ng.appspot.com/legacy/sheriff_gpu.json"
     }
   }
   builders {
@@ -1319,14 +1325,15 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+gpu-test-tree-closing-notifier@chromium.org"
+      recipients: "chrome-gpu-build-failures@google.com"
+      rotation_urls: "https://rota-ng.appspot.com/legacy/sheriff_gpu.json"
     }
   }
   builders {
@@ -1343,7 +1350,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1360,7 +1367,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1377,7 +1384,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1394,7 +1401,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1411,7 +1418,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1428,7 +1435,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1445,7 +1452,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1462,7 +1469,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -1672,7 +1679,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1690,7 +1697,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1708,7 +1715,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1726,7 +1733,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1743,7 +1750,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1773,7 +1780,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1816,7 +1823,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1834,7 +1841,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1852,7 +1859,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1870,7 +1877,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1888,7 +1895,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1905,14 +1912,15 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+gpu-test-tree-closing-notifier@chromium.org"
+      recipients: "chrome-gpu-build-failures@google.com"
+      rotation_urls: "https://rota-ng.appspot.com/legacy/sheriff_gpu.json"
     }
   }
   builders {
@@ -1929,7 +1937,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1946,7 +1954,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1963,7 +1971,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1980,7 +1988,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -1998,7 +2006,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -2016,7 +2024,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -2174,7 +2182,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -2192,7 +2200,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -2210,7 +2218,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -2228,7 +2236,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -2246,7 +2254,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -2264,7 +2272,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -2282,7 +2290,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -2327,6 +2335,19 @@
   }
   builders {
     bucket: "ci"
+    name: "fuchsia-fyi-arm64-dbg"
+    repository: "https://chromium.googlesource.com/chromium/src"
+  }
+}
+notifiers {
+  notifications {
+    on_change: true
+    email {
+      recipients: "cr-fuchsia+bot@chromium.org"
+    }
+  }
+  builders {
+    bucket: "ci"
     name: "fuchsia-fyi-arm64-rel"
     repository: "https://chromium.googlesource.com/chromium/src"
   }
@@ -2385,7 +2406,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -2416,7 +2437,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -2434,7 +2455,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -2465,7 +2486,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -2483,7 +2504,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -2515,7 +2536,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -2533,7 +2554,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -2558,7 +2579,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -2596,7 +2617,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -2614,7 +2635,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -2632,7 +2653,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -2686,7 +2707,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -2711,7 +2732,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -2741,7 +2762,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -2759,7 +2780,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -2791,7 +2812,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -2809,7 +2830,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -2827,7 +2848,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -2845,14 +2866,15 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+gpu-test-tree-closing-notifier@chromium.org"
+      recipients: "chrome-gpu-build-failures@google.com"
+      rotation_urls: "https://rota-ng.appspot.com/legacy/sheriff_gpu.json"
     }
   }
   builders {
@@ -2870,7 +2892,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -2895,7 +2917,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -2926,7 +2948,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -2957,14 +2979,15 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+gpu-test-tree-closing-notifier@chromium.org"
+      recipients: "chrome-gpu-build-failures@google.com"
+      rotation_urls: "https://rota-ng.appspot.com/legacy/sheriff_gpu.json"
     }
   }
   builders {
@@ -2982,14 +3005,15 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+gpu-test-tree-closing-notifier@chromium.org"
+      recipients: "chrome-gpu-build-failures@google.com"
+      rotation_urls: "https://rota-ng.appspot.com/legacy/sheriff_gpu.json"
     }
   }
   builders {
@@ -3007,14 +3031,15 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+gpu-test-tree-closing-notifier@chromium.org"
+      recipients: "chrome-gpu-build-failures@google.com"
+      rotation_urls: "https://rota-ng.appspot.com/legacy/sheriff_gpu.json"
     }
   }
   builders {
@@ -3032,7 +3057,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -3057,7 +3082,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -3081,7 +3106,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -3105,7 +3130,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -3130,7 +3155,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -3155,7 +3180,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -3179,7 +3204,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -3203,7 +3228,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -3227,14 +3252,15 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+gpu-test-tree-closing-notifier@chromium.org"
+      recipients: "chrome-gpu-build-failures@google.com"
+      rotation_urls: "https://rota-ng.appspot.com/legacy/sheriff_gpu.json"
     }
   }
   builders {
@@ -3251,7 +3277,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -3276,7 +3302,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -3300,7 +3326,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -3324,7 +3350,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -3348,7 +3374,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -3366,7 +3392,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -3384,14 +3410,15 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+gpu-test-tree-closing-notifier@chromium.org"
+      recipients: "chrome-gpu-build-failures@google.com"
+      rotation_urls: "https://rota-ng.appspot.com/legacy/sheriff_gpu.json"
     }
   }
   builders {
@@ -3408,14 +3435,15 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+gpu-test-tree-closing-notifier@chromium.org"
+      recipients: "chrome-gpu-build-failures@google.com"
+      rotation_urls: "https://rota-ng.appspot.com/legacy/sheriff_gpu.json"
     }
   }
   builders {
@@ -3432,7 +3460,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -3449,7 +3477,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -3466,7 +3494,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -3483,7 +3511,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -3500,7 +3528,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -3517,7 +3545,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -3534,7 +3562,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -3551,7 +3579,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -3568,7 +3596,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -3585,7 +3613,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -3603,7 +3631,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -3621,7 +3649,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -3638,14 +3666,15 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+gpu-test-tree-closing-notifier@chromium.org"
+      recipients: "chrome-gpu-build-failures@google.com"
+      rotation_urls: "https://rota-ng.appspot.com/legacy/sheriff_gpu.json"
     }
   }
   builders {
@@ -3662,7 +3691,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -3729,7 +3758,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -3747,7 +3776,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -3765,7 +3794,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -3783,7 +3812,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -3814,7 +3843,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -3845,7 +3874,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -3876,7 +3905,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -3894,7 +3923,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -3912,7 +3941,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -3930,7 +3959,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -3955,14 +3984,15 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+gpu-test-tree-closing-notifier@chromium.org"
+      recipients: "chrome-gpu-build-failures@google.com"
+      rotation_urls: "https://rota-ng.appspot.com/legacy/sheriff_gpu.json"
     }
   }
   builders {
@@ -3980,7 +4010,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -4005,7 +4035,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -4036,7 +4066,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -4067,14 +4097,15 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+gpu-test-tree-closing-notifier@chromium.org"
+      recipients: "chrome-gpu-build-failures@google.com"
+      rotation_urls: "https://rota-ng.appspot.com/legacy/sheriff_gpu.json"
     }
   }
   builders {
@@ -4092,14 +4123,15 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+gpu-test-tree-closing-notifier@chromium.org"
+      recipients: "chrome-gpu-build-failures@google.com"
+      rotation_urls: "https://rota-ng.appspot.com/legacy/sheriff_gpu.json"
     }
   }
   builders {
@@ -4117,14 +4149,15 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+gpu-test-tree-closing-notifier@chromium.org"
+      recipients: "chrome-gpu-build-failures@google.com"
+      rotation_urls: "https://rota-ng.appspot.com/legacy/sheriff_gpu.json"
     }
   }
   builders {
@@ -4142,7 +4175,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -4167,7 +4200,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -4191,7 +4224,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -4215,7 +4248,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -4240,7 +4273,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -4265,7 +4298,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -4289,7 +4322,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -4313,7 +4346,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -4337,14 +4370,15 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+gpu-test-tree-closing-notifier@chromium.org"
+      recipients: "chrome-gpu-build-failures@google.com"
+      rotation_urls: "https://rota-ng.appspot.com/legacy/sheriff_gpu.json"
     }
   }
   builders {
@@ -4361,7 +4395,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -4386,7 +4420,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -4410,7 +4444,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -4434,7 +4468,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -4458,7 +4492,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -4476,7 +4510,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -4494,14 +4528,15 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+gpu-test-tree-closing-notifier@chromium.org"
+      recipients: "chrome-gpu-build-failures@google.com"
+      rotation_urls: "https://rota-ng.appspot.com/legacy/sheriff_gpu.json"
     }
   }
   builders {
@@ -4518,14 +4553,15 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+gpu-test-tree-closing-notifier@chromium.org"
+      recipients: "chrome-gpu-build-failures@google.com"
+      rotation_urls: "https://rota-ng.appspot.com/legacy/sheriff_gpu.json"
     }
   }
   builders {
@@ -4542,7 +4578,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -4559,7 +4595,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -4576,7 +4612,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -4593,7 +4629,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -4610,7 +4646,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -4627,7 +4663,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -4644,7 +4680,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -4661,7 +4697,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -4678,7 +4714,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -4695,7 +4731,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -4713,7 +4749,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -4731,7 +4767,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -4748,14 +4784,15 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+gpu-test-tree-closing-notifier@chromium.org"
+      recipients: "chrome-gpu-build-failures@google.com"
+      rotation_urls: "https://rota-ng.appspot.com/legacy/sheriff_gpu.json"
     }
   }
   builders {
@@ -4772,7 +4809,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -4839,7 +4876,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -4857,7 +4894,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -4875,7 +4912,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -4893,7 +4930,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -4924,7 +4961,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
@@ -4955,7 +4992,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -4986,7 +5023,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -5004,7 +5041,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -5022,7 +5059,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   builders {
@@ -5040,7 +5077,7 @@
     on_occurrence: FAILURE
     failed_step_regexp: "bot_update|compile|gclient runhooks|runhooks|update"
     email {
-      recipients: "orodley+test-tree-closing-notifier@chromium.org"
+      rotation_urls: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
     }
   }
   notifications {
diff --git a/infra/config/generated/luci-scheduler.cfg b/infra/config/generated/luci-scheduler.cfg
index 5e7a046..661cf7f 100644
--- a/infra/config/generated/luci-scheduler.cfg
+++ b/infra/config/generated/luci-scheduler.cfg
@@ -7802,6 +7802,15 @@
   }
 }
 job {
+  id: "ci-fuchsia-fyi-arm64-dbg"
+  acl_sets: "ci"
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci"
+    builder: "fuchsia-fyi-arm64-dbg"
+  }
+}
+job {
   id: "ci-fuchsia-fyi-arm64-rel"
   acl_sets: "ci"
   buildbucket {
@@ -8923,19 +8932,6 @@
   }
 }
 job {
-  id: "ci-m84-Win10 Tests x64 1803"
-  acls {
-    role: TRIGGERER
-    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-  }
-  acl_sets: "ci-m84"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.chromium.ci-m84"
-    builder: "Win10 Tests x64 1803"
-  }
-}
-job {
   id: "ci-m84-Win10 x64 Release (NVIDIA)"
   acls {
     role: TRIGGERER
@@ -9145,19 +9141,6 @@
   }
 }
 job {
-  id: "ci-m84-mac-osxbeta-rel"
-  acls {
-    role: TRIGGERER
-    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-  }
-  acl_sets: "ci-m84"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.chromium.ci-m84"
-    builder: "mac-osxbeta-rel"
-  }
-}
-job {
   id: "ci-m85-Android Release (Nexus 5X)"
   acl_sets: "ci-m85"
   buildbucket {
@@ -9851,19 +9834,6 @@
   }
 }
 job {
-  id: "ci-m85-Win10 Tests x64 1803"
-  acls {
-    role: TRIGGERER
-    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-  }
-  acl_sets: "ci-m85"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.chromium.ci-m85"
-    builder: "Win10 Tests x64 1803"
-  }
-}
-job {
   id: "ci-m85-Win10 x64 Release (NVIDIA)"
   acls {
     role: TRIGGERER
@@ -10118,28 +10088,6 @@
   }
 }
 job {
-  id: "ci-m85-mac-arm64"
-  acl_sets: "ci-m85"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.chromium.ci-m85"
-    builder: "mac-arm64"
-  }
-}
-job {
-  id: "ci-m85-mac-osxbeta-rel"
-  acls {
-    role: TRIGGERER
-    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-  }
-  acl_sets: "ci-m85"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.chromium.ci-m85"
-    builder: "mac-osxbeta-rel"
-  }
-}
-job {
   id: "ci-mac-archive-dbg"
   acl_sets: "ci"
   buildbucket {
@@ -10413,6 +10361,15 @@
   }
 }
 job {
+  id: "fuchsia-fyi-arm64-dbg"
+  acl_sets: "ci"
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci"
+    builder: "fuchsia-fyi-arm64-dbg"
+  }
+}
+job {
   id: "fuchsia-fyi-arm64-rel"
   acl_sets: "ci"
   buildbucket {
@@ -11365,6 +11322,26 @@
   }
 }
 job {
+  id: "ci-m84-Win10 Tests x64 1803"
+  schedule: "triggered"
+  acls {
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  }
+  acl_sets: "ci-m84"
+  noop {}
+}
+job {
+  id: "ci-m85-Win10 Tests x64 1803"
+  schedule: "triggered"
+  acls {
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  }
+  acl_sets: "ci-m85"
+  noop {}
+}
+job {
   id: "ci-m84-Android WebView L (dbg)"
   schedule: "triggered"
   acls {
@@ -11542,7 +11519,6 @@
   triggers: "ci-m85-linux-chromeos-rel"
   triggers: "ci-m85-linux-official"
   triggers: "ci-m85-linux-ozone-rel"
-  triggers: "ci-m85-mac-arm64"
   gitiles {
     repo: "https://chromium.googlesource.com/chromium/src"
     refs: "regexp:refs/branch-heads/4183"
@@ -11761,6 +11737,7 @@
   triggers: "ci-chromeos-arm-generic-rel"
   triggers: "chromeos-kevin-rel"
   triggers: "ci-fuchsia-arm64-cast"
+  triggers: "fuchsia-fyi-arm64-dbg"
   triggers: "fuchsia-fyi-arm64-rel"
   triggers: "fuchsia-fyi-x64-dbg"
   triggers: "fuchsia-fyi-x64-rel"
@@ -11808,7 +11785,7 @@
   triggers: "linux-wpt-fyi-rel"
   triggers: "mac-archive-dbg"
   triggers: "mac-archive-rel"
-  triggers: "ci-mac-arm64"
+  triggers: "mac-arm64"
   triggers: "mac-code-coverage"
   triggers: "mac-hermetic-upgrade-rel"
   triggers: "mac-mojo-rel"
diff --git a/infra/config/generators/scheduler-noop-jobs.star b/infra/config/generators/scheduler-noop-jobs.star
index f5f754d..72d1f3c 100644
--- a/infra/config/generators/scheduler-noop-jobs.star
+++ b/infra/config/generators/scheduler-noop-jobs.star
@@ -2,11 +2,29 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+"""Generator for creating no-op scheduler jobs.
+
+The triggering relationship is actually described in the configuration defined
+in the recipes, which is shared between the versions of a builder for different
+milestones. We don't always want to trigger the same set of builders on all of
+the branches, so we create no-op jobs for the milestones where the builder is
+not defined so that the recipe can issue a trigger for the non-existent builder
+without error.
+"""
+
 # Don't make a habit of this - it isn't public API
 load('@stdlib//internal/luci/proto.star', 'scheduler_pb')
 load('//project.star', 'settings')
 
 
+# For the chromium project (settings.is_master is True), we have bucket-based
+# milestones for <=M85. We create a no-op job that prefixes the ci bucket name
+# for those milestones. Combined with setting the bucketed_triggers property,
+# this makes it safe to issue triggers for the builders that don't exist for the
+# milestone.
+# For the chromium milestone projects (settings.is_master is False), the
+# milestone project will use the same bucket names, so we create a no-op job for
+# the 'ci' bucket.
 _BRANCH_NOOP_CONFIG = struct(
     buckets = ['ci-m84', 'ci-m85'],
     fmt = '{bucket}-{builder}',
@@ -16,9 +34,15 @@
 )
 
 
-# Android testers which are triggered by Android arm Builder (dbg)
-# on master, but not on branches.
-_ANDROID_NON_BRANCHED_TESTERS = (
+_NON_BRANCHED_TESTERS = (
+    # This tester is triggered by 'Win x64 Builder', but it is an FYI builder
+    # and not mirrored by any branched try builders, so we do not need to run it
+    # on the branches
+    'Win10 Tests x64 1803',
+
+    # These Android testers are triggered by 'Android arm Builder (dbg)', but we
+    # don't have sufficient capacity of devices with older Android versions, so
+    # we do not run them on the branches
     'Android WebView L (dbg)',
     'Lollipop Phone Tester',
     'Lollipop Tablet Tester',
@@ -26,7 +50,7 @@
 )
 
 
-_ANDROID_TEST_NOOP_JOBS = [scheduler_pb.Job(
+_TESTER_NOOP_JOBS = [scheduler_pb.Job(
     id = _BRANCH_NOOP_CONFIG.fmt.format(bucket=bucket, builder=builder),
     schedule = 'triggered',
     acl_sets = [bucket],
@@ -35,12 +59,12 @@
         granted_to = 'chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com',
     )],
     noop = scheduler_pb.NoopTask(),
-) for builder in _ANDROID_NON_BRANCHED_TESTERS for bucket in _BRANCH_NOOP_CONFIG.buckets]
+) for builder in _NON_BRANCHED_TESTERS for bucket in _BRANCH_NOOP_CONFIG.buckets]
 
 
 def _add_noop_jobs(ctx):
   cfg = ctx.output['luci-scheduler.cfg']
-  for j in _ANDROID_TEST_NOOP_JOBS:
+  for j in _TESTER_NOOP_JOBS:
     cfg.job.append(j)
 
 lucicfg.generator(_add_noop_jobs)
diff --git a/infra/config/lib/ci.star b/infra/config/lib/ci.star
index ce16d28..75d220e 100644
--- a/infra/config/lib/ci.star
+++ b/infra/config/lib/ci.star
@@ -555,14 +555,14 @@
   )
 
 
-def clang_builder(*, name, cores=32, properties=None, **kwargs):
+def clang_builder(*, name, builderless=True, cores=32, properties=None, **kwargs):
   properties = properties or {}
   properties.update({
     'perf_dashboard_machine_group': 'ChromiumClang',
   })
   return ci_builder(
       name = name,
-      builderless = True,
+      builderless = builderless,
       cores = cores,
       # Because these run ToT Clang, goma is not used.
       # Naturally the runtime will be ~4-8h on average, depending on config.
@@ -885,15 +885,18 @@
   )
 
 
-def swangle_builder(*, name, builderless=True, **kwargs):
-  return ci.builder(
-      name = name,
-      builderless = builderless,
-      mastername = 'chromium.swangle',
-      service_account =
-      'chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com',
-      **kwargs
-  )
+def swangle_builder(*, name, builderless = True, pinned = True, **kwargs):
+    builder_args = dict(kwargs)
+    builder_args.update(
+        name = name,
+        builderless = builderless,
+        mastername = "chromium.swangle",
+        service_account =
+            "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com",
+    )
+    if pinned:
+        builder_args.update(executable = "recipe:angle_chromium")
+    return ci.builder(**builder_args)
 
 
 def swangle_linux_builder(
diff --git a/infra/config/lib/try.star b/infra/config/lib/try.star
index 8d6b047..8997fa0 100644
--- a/infra/config/lib/try.star
+++ b/infra/config/lib/try.star
@@ -414,14 +414,17 @@
   )
 
 
-def chromium_swangle_builder(*, name, **kwargs):
-  return try_builder(
-      name = name,
-      builderless = True,
-      mastername = 'tryserver.chromium.swangle',
-      service_account = 'chromium-try-gpu-builder@chops-service-accounts.iam.gserviceaccount.com',
-      **kwargs
-  )
+def chromium_swangle_builder(*, name, pinned = True, **kwargs):
+    builder_args = dict(kwargs)
+    builder_args.update(
+        name = name,
+        builderless = True,
+        mastername = "tryserver.chromium.swangle",
+        service_account = "chromium-try-gpu-builder@chops-service-accounts.iam.gserviceaccount.com",
+    )
+    if pinned:
+        builder_args.update(executable = "recipe:angle_chromium_trybot")
+    return try_builder(**builder_args)
 
 
 def chromium_swangle_linux_builder(*, name, **kwargs):
diff --git a/infra/config/notifiers.star b/infra/config/notifiers.star
index 7f7ccf7..d9f9f7a 100644
--- a/infra/config/notifiers.star
+++ b/infra/config/notifiers.star
@@ -59,25 +59,26 @@
     tree_status_host = 'chromium-status.appspot.com',
 )
 
-def tree_closure_notifier(name, notify_emails):
+def tree_closure_notifier(**kwargs):
   return luci.notifier(
-      name = name,
-      notify_emails = notify_emails,
       on_occurrence = ['FAILURE'],
       failed_step_regexp = TREE_CLOSING_STEPS,
+      **kwargs,
   )
 
 tree_closure_notifier(
     name = 'chromium-tree-closer-email',
-    # Stand-in for the chromium build sheriff, while testing.
-    notify_emails = ['orodley+test-tree-closing-notifier@chromium.org'],
+    notify_rotation_urls = [
+        "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff",
+    ],
 )
 
 tree_closure_notifier(
     name = 'gpu-tree-closer-email',
-    # Stand-in for chrome-gpu-build-failures@google.com and the GPU build
-    # sheriff, while testing.
-    notify_emails = ['orodley+gpu-test-tree-closing-notifier@chromium.org'],
+    notify_emails = ['chrome-gpu-build-failures@google.com'],
+    notify_rotation_urls = [
+        "https://rota-ng.appspot.com/legacy/sheriff_gpu.json",
+    ],
 )
 
 tree_closure_notifier(
diff --git a/infra/config/recipes.star b/infra/config/recipes.star
index cb5efe9..3fc5310 100644
--- a/infra/config/recipes.star
+++ b/infra/config/recipes.star
@@ -35,6 +35,14 @@
 )
 
 build_recipe(
+    name = 'recipe:angle_chromium',
+)
+
+build_recipe(
+    name = 'recipe:angle_chromium_trybot',
+)
+
+build_recipe(
     name = 'recipe:binary_size_generator_tot',
 )
 
diff --git a/infra/config/subprojects/chromium/ci.star b/infra/config/subprojects/chromium/ci.star
index 2d8c25e4..1acc18d 100644
--- a/infra/config/subprojects/chromium/ci.star
+++ b/infra/config/subprojects/chromium/ci.star
@@ -511,9 +511,7 @@
 
 ci.chromium_builder(
     name = 'win-official',
-    # TODO(https://crbug.com/1072012) Use the default console view and add
-    # main_console_view = settings.main_console_name once the build is green
-    console_view = 'chromium.fyi',
+    main_console_view = settings.main_console_name,
     console_view_entry = ci.console_view_entry(
         category = 'win|off',
         short_name = '64',
@@ -526,9 +524,7 @@
 
 ci.chromium_builder(
     name = 'win32-official',
-    # TODO(https://crbug.com/1072012) Use the default console view and add
-    # main_console_view = settings.main_console_name once the build is green
-    console_view = 'chromium.fyi',
+    main_console_view = settings.main_console_name,
     console_view_entry = ci.console_view_entry(
         category = 'win|off',
         short_name = '32',
@@ -733,19 +729,6 @@
     main_console_view = None,
 )
 
-# This is launching & collecting entirely isolated tests.
-# OS shouldn't matter.
-ci.fyi_builder(
-    name = 'mac-osxbeta-rel',
-    console_view_entry = ci.console_view_entry(
-        category = 'mac',
-        short_name = 'beta',
-    ),
-    goma_backend = None,
-    main_console_view = None,
-    triggered_by = [builder_name('Mac Builder')],
-)
-
 ci.fyi_builder(
     name = 'mac-arm64',
     console_view_entry = ci.console_view_entry(
@@ -771,18 +754,6 @@
 )
 
 
-ci.fyi_windows_builder(
-    name = 'Win10 Tests x64 1803',
-    console_view_entry = ci.console_view_entry(
-        category = 'win10|1803',
-    ),
-    goma_backend = None,
-    main_console_view = None,
-    os = os.WINDOWS_10,
-    triggered_by = [builder_name('Win x64 Builder')],
-)
-
-
 ci.gpu_builder(
     name = 'Android Release (Nexus 5X)',
     console_view_entry = ci.console_view_entry(
diff --git a/infra/config/subprojects/chromium/master-only/ci.star b/infra/config/subprojects/chromium/master-only/ci.star
index 4bab9bcd..cfd77ce 100644
--- a/infra/config/subprojects/chromium/master-only/ci.star
+++ b/infra/config/subprojects/chromium/master-only/ci.star
@@ -690,6 +690,8 @@
 
 clang_tot_linux_builder(
     name = 'ToTLinuxASanLibfuzzer',
+    # Requires a large disk, so has a machine specifically devoted to it
+    builderless = False,
     short_name = 'fuz',
 )
 
@@ -1460,6 +1462,15 @@
 )
 
 ci.fyi_builder(
+    name = 'fuchsia-fyi-arm64-dbg',
+    console_view_entry = ci.console_view_entry(
+        category = 'fuchsia|a64',
+        short_name = 'dbg',
+    ),
+    notifies = ['cr-fuchsia'],
+)
+
+ci.fyi_builder(
     name = 'fuchsia-fyi-arm64-rel',
     console_view_entry = ci.console_view_entry(
         category = 'fuchsia|a64',
@@ -1567,6 +1578,19 @@
     goma_backend = None,
 )
 
+# This is launching & collecting entirely isolated tests.
+# OS shouldn't matter.
+ci.fyi_builder(
+    name = 'mac-osxbeta-rel',
+    console_view_entry = ci.console_view_entry(
+        category = 'mac',
+        short_name = 'beta',
+    ),
+    goma_backend = None,
+    main_console_view = None,
+    triggered_by = ['ci/Mac Builder'],
+)
+
 ci.fyi_builder(
     name = 'win-pixel-builder-rel',
     console_view_entry = ci.console_view_entry(
@@ -1603,6 +1627,7 @@
     os = os.MAC_DEFAULT,
     schedule = 'with 3h interval',
     triggered_by = [],
+    ssd = None,
 )
 
 ci.fyi_builder(
@@ -1895,6 +1920,17 @@
 
 
 ci.fyi_windows_builder(
+    name = 'Win10 Tests x64 1803',
+    console_view_entry = ci.console_view_entry(
+        category = 'win10|1803',
+    ),
+    goma_backend = None,
+    main_console_view = None,
+    os = os.WINDOWS_10,
+    triggered_by = ['ci/Win x64 Builder'],
+)
+
+ci.fyi_windows_builder(
     name = 'Win 10 Fast Ring',
     console_view_entry = ci.console_view_entry(
         category = 'win10',
@@ -3053,6 +3089,7 @@
         category = 'Chromium|Linux',
         short_name = 'x64',
     ),
+    pinned = False,
 )
 
 ci.swangle_linux_builder(
@@ -3093,6 +3130,7 @@
         category = 'DEPS|Linux',
         short_name = 'x64',
     ),
+    pinned = False,
 )
 
 ci.swangle_linux_builder(
@@ -3101,6 +3139,7 @@
         category = 'DEPS|Linux',
         short_name = 'x86',
     ),
+    pinned = False,
 )
 
 
@@ -3110,6 +3149,7 @@
         category = 'Chromium|Mac',
         short_name = 'x64',
     ),
+    pinned = False,
 )
 
 
@@ -3119,6 +3159,7 @@
         category = 'Chromium|Windows',
         short_name = 'x86',
     ),
+    pinned = False,
 )
 
 ci.swangle_windows_builder(
@@ -3159,6 +3200,7 @@
         category = 'DEPS|Windows',
         short_name = 'x64',
     ),
+    pinned = False,
 )
 
 ci.swangle_windows_builder(
@@ -3167,6 +3209,7 @@
         category = 'DEPS|Windows',
         short_name = 'x86',
     ),
+    pinned = False,
 )
 
 
diff --git a/infra/config/subprojects/chromium/master-only/swangle.try.star b/infra/config/subprojects/chromium/master-only/swangle.try.star
index f00185d..f2bfe2d5 100644
--- a/infra/config/subprojects/chromium/master-only/swangle.try.star
+++ b/infra/config/subprojects/chromium/master-only/swangle.try.star
@@ -18,6 +18,7 @@
     name = 'linux-swangle-chromium-try-x64',
     pool = 'luci.chromium.swangle.chromium.linux.x64.try',
     execution_timeout = 6 * time.hour,
+    pinned = False,
 )
 
 try_.chromium_swangle_linux_builder(
@@ -43,11 +44,13 @@
 try_.chromium_swangle_linux_builder(
     name = 'linux-swangle-try-x64',
     pool = 'luci.chromium.swangle.deps.linux.x64.try',
+    pinned = False,
 )
 
 try_.chromium_swangle_linux_builder(
     name = 'linux-swangle-try-x86',
     pool = 'luci.chromium.swangle.linux.x86.try',
+    pinned = False,
 )
 
 
@@ -55,6 +58,7 @@
     name = 'mac-swangle-chromium-try-x64',
     pool = 'luci.chromium.swangle.chromium.mac.x64.try',
     execution_timeout = 6 * time.hour,
+    pinned = False,
 )
 
 
@@ -62,6 +66,7 @@
     name = 'win-swangle-chromium-try-x86',
     pool = 'luci.chromium.swangle.chromium.win.x86.try',
     execution_timeout = 6 * time.hour,
+    pinned = False,
 )
 
 try_.chromium_swangle_windows_builder(
@@ -87,9 +92,11 @@
 try_.chromium_swangle_windows_builder(
     name = 'win-swangle-try-x64',
     pool = 'luci.chromium.swangle.win.x64.try',
+    pinned = False,
 )
 
 try_.chromium_swangle_windows_builder(
     name = 'win-swangle-try-x86',
     pool = 'luci.chromium.swangle.deps.win.x86.try',
+    pinned = False,
 )
diff --git a/infra/config/subprojects/chromium/master-only/try.star b/infra/config/subprojects/chromium/master-only/try.star
index e574107..20fdb4a 100644
--- a/infra/config/subprojects/chromium/master-only/try.star
+++ b/infra/config/subprojects/chromium/master-only/try.star
@@ -342,6 +342,10 @@
 )
 
 try_.chromium_linux_builder(
+    name = 'fuchsia-fyi-arm64-dbg',
+)
+
+try_.chromium_linux_builder(
     name = 'fuchsia-fyi-arm64-rel',
 )
 
diff --git a/infra/config/subprojects/chromium/versioned/m84/buckets/ci.star b/infra/config/subprojects/chromium/versioned/m84/buckets/ci.star
index 267686b3..7436e01 100644
--- a/infra/config/subprojects/chromium/versioned/m84/buckets/ci.star
+++ b/infra/config/subprojects/chromium/versioned/m84/buckets/ci.star
@@ -378,18 +378,6 @@
     ),
 )
 
-# This is launching & collecting entirely isolated tests.
-# OS shouldn't matter.
-ci.fyi_builder(
-    name = 'mac-osxbeta-rel',
-    console_view_entry = ci.console_view_entry(
-        category = 'mac',
-        short_name = 'beta',
-    ),
-    goma_backend = None,
-    triggered_by = [builder_name('Mac Builder')],
-)
-
 
 ci.fyi_ios_builder(
     name = 'ios-simulator-cronet',
@@ -404,17 +392,6 @@
 )
 
 
-ci.fyi_windows_builder(
-    name = 'Win10 Tests x64 1803',
-    console_view_entry = ci.console_view_entry(
-        category = 'win10|1803',
-    ),
-    goma_backend = None,
-    os = os.WINDOWS_10,
-    triggered_by = [builder_name('Win x64 Builder')],
-)
-
-
 ci.gpu_builder(
     name = 'Android Release (Nexus 5X)',
     console_view_entry = ci.console_view_entry(
diff --git a/infra/config/subprojects/chromium/versioned/m85/buckets/ci.star b/infra/config/subprojects/chromium/versioned/m85/buckets/ci.star
index cb95181..6258864 100644
--- a/infra/config/subprojects/chromium/versioned/m85/buckets/ci.star
+++ b/infra/config/subprojects/chromium/versioned/m85/buckets/ci.star
@@ -441,27 +441,6 @@
     ),
 )
 
-# This is launching & collecting entirely isolated tests.
-# OS shouldn't matter.
-ci.fyi_builder(
-    name = 'mac-osxbeta-rel',
-    console_view_entry = ci.console_view_entry(
-        category = 'mac',
-        short_name = 'beta',
-    ),
-    goma_backend = None,
-    triggered_by = [builder_name('Mac Builder')],
-)
-
-ci.fyi_builder(
-    name = 'mac-arm64',
-    console_view_entry = ci.console_view_entry(
-        category = 'mac',
-        short_name = 'arm64',
-    ),
-    cores = None,
-    os = os.MAC_ANY,
-)
 
 ci.fyi_ios_builder(
     name = 'ios-simulator-cronet',
@@ -476,17 +455,6 @@
 )
 
 
-ci.fyi_windows_builder(
-    name = 'Win10 Tests x64 1803',
-    console_view_entry = ci.console_view_entry(
-        category = 'win10|1803',
-    ),
-    goma_backend = None,
-    os = os.WINDOWS_10,
-    triggered_by = [builder_name('Win x64 Builder')],
-)
-
-
 ci.gpu_builder(
     name = 'Android Release (Nexus 5X)',
     console_view_entry = ci.console_view_entry(
diff --git a/ios/build/bots/scripts/xcode_log_parser.py b/ios/build/bots/scripts/xcode_log_parser.py
index 4b7ed15..33a45368 100644
--- a/ios/build/bots/scripts/xcode_log_parser.py
+++ b/ios/build/bots/scripts/xcode_log_parser.py
@@ -184,10 +184,11 @@
                     xcresult, test['summaryRef']['id']['_value']))
             failure_message = []
             for failure in rootFailure['failureSummaries']['_values']:
+              failure_location = '<unknown>'
               if 'lineNumber' in failure:
                 failure_location = '%s:%s' % (failure['fileName'].get(
                     '_value', ''), failure['lineNumber'].get('_value', ''))
-              else:
+              elif 'fileName' in failure:
                 failure_location = failure['fileName'].get('_value', '')
               failure_message += [failure_location
                                  ] + failure['message']['_value'].splitlines()
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd
index 2215fc1..07bfc135 100644
--- a/ios/chrome/app/strings/ios_strings.grd
+++ b/ios/chrome/app/strings/ios_strings.grd
@@ -1638,12 +1638,15 @@
       <message name="IDS_IOS_SEARCH_COPIED_TEXT" desc="If a user has some text on their clipboard, this string will appear as an option when the user long-presses on Chrome's address bar. The user can select this option to search the default search engine for the text. This string is a complete sentence. If necessary for your language, you can translate as 'Search for Text That You Copied.' [iOS only]" meaning="Maximum characters: 32">
           Search for Copied Text
       </message>
-      <message name="IDS_IOS_SEARCH_ENGINE_SETTING_TITLE" desc="The title for Search Engine selection setting [iOS only]">
-        Search Engine
-      </message>
       <message name="IDS_IOS_SEARCH_ENGINE_SETTING_CUSTOM_SECTION_HEADER" desc="The header for custom search engines section in Search Engine selection setting [iOS only]">
         Recently Visited
       </message>
+      <message name="IDS_IOS_SEARCH_ENGINE_SETTING_DISABLED_STATUS" desc="The status for Search Engine selection setting when the default search engine is disabled by policy [iOS only]">
+        None
+      </message>
+      <message name="IDS_IOS_SEARCH_ENGINE_SETTING_TITLE" desc="The title for Search Engine selection setting [iOS only]">
+        Search Engine
+      </message>
       <message name="IDS_IOS_SETTINGS_SAFETY_CHECK_PASSWORDS_TITLE" desc="Title for the passwords element of safety check" meaning="Row title to access the passowrd check subpage if a probelm is found with user's saved passwords [CHAR_LIMIT=20]">
         Passwords
       </message>
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SEARCH_ENGINE_SETTING_DISABLED_STATUS.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SEARCH_ENGINE_SETTING_DISABLED_STATUS.png.sha1
new file mode 100644
index 0000000..213ef66
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SEARCH_ENGINE_SETTING_DISABLED_STATUS.png.sha1
@@ -0,0 +1 @@
+28c6f13fb1421950ab191293d51ef6f954254585
\ No newline at end of file
diff --git a/ios/chrome/browser/passwords/BUILD.gn b/ios/chrome/browser/passwords/BUILD.gn
index cb0b174..db6599f 100644
--- a/ios/chrome/browser/passwords/BUILD.gn
+++ b/ios/chrome/browser/passwords/BUILD.gn
@@ -35,7 +35,6 @@
     "password_check_observer_bridge.mm",
     "password_controller.h",
     "password_controller.mm",
-    "password_form_filler.h",
     "password_manager_log_router_factory.cc",
     "password_manager_log_router_factory.h",
     "password_store_observer_bridge.h",
diff --git a/ios/chrome/browser/passwords/password_controller.h b/ios/chrome/browser/passwords/password_controller.h
index 7c74600f..e84e17c8b 100644
--- a/ios/chrome/browser/passwords/password_controller.h
+++ b/ios/chrome/browser/passwords/password_controller.h
@@ -20,7 +20,6 @@
 class Browser;
 @class NotifyUserAutoSigninViewController;
 @protocol PasswordBreachCommands;
-@protocol PasswordFormFiller;
 @protocol PasswordsUiDelegate;
 @class UIViewController;
 
@@ -59,9 +58,6 @@
 @property(nonatomic, readonly)
     password_manager::PasswordManagerDriver* passwordManagerDriver;
 
-// The PasswordFormFiller owned by this PasswordController.
-@property(nonatomic, readonly) id<PasswordFormFiller> passwordFormFiller;
-
 // The base view controller from which to present UI.
 @property(nonatomic, readwrite, weak) UIViewController* baseViewController;
 
diff --git a/ios/chrome/browser/passwords/password_controller.mm b/ios/chrome/browser/passwords/password_controller.mm
index f329528..91046e2 100644
--- a/ios/chrome/browser/passwords/password_controller.mm
+++ b/ios/chrome/browser/passwords/password_controller.mm
@@ -55,7 +55,6 @@
 #import "ios/chrome/browser/passwords/ios_chrome_update_password_infobar_delegate.h"
 #import "ios/chrome/browser/passwords/ios_password_infobar_controller.h"
 #import "ios/chrome/browser/passwords/notify_auto_signin_view_controller.h"
-#import "ios/chrome/browser/passwords/password_form_filler.h"
 #include "ios/chrome/browser/passwords/password_manager_features.h"
 #include "ios/chrome/browser/sync/profile_sync_service_factory.h"
 #import "ios/chrome/browser/ui/alert_coordinator/action_sheet_coordinator.h"
@@ -147,7 +146,6 @@
 @end
 
 @interface PasswordController () <FormSuggestionProvider,
-                                  PasswordFormFiller,
                                   FormActivityObserver>
 
 // Informs the |_passwordManager| of the password forms (if any were present)
@@ -257,10 +255,6 @@
 
 #pragma mark - Properties
 
-- (id<PasswordFormFiller>)passwordFormFiller {
-  return self;
-}
-
 - (ukm::SourceId)ukmSourceId {
   return _webState ? ukm::GetSourceIdForWebStateDocument(_webState)
                    : ukm::kInvalidSourceId;
@@ -274,16 +268,6 @@
   return _passwordManagerDriver.get();
 }
 
-#pragma mark - PasswordFormFiller
-
-- (void)findAndFillPasswordForms:(NSString*)username
-                        password:(NSString*)password
-               completionHandler:(void (^)(BOOL))completionHandler {
-  [self.formHelper findAndFillPasswordFormsWithUserName:username
-                                               password:password
-                                      completionHandler:completionHandler];
-}
-
 #pragma mark - CRWWebStateObserver
 
 // If Tab was shown, and there is a pending PasswordForm, display autosign-in
@@ -439,6 +423,10 @@
     _lastTypedfieldIdentifier = formQuery.uniqueFieldID;
     _lastTypedValue = formQuery.typedValue;
 
+    if ([formQuery.type isEqual:@"text"]) {
+      [self.formHelper updateFieldDataOnUserInput:formQuery.uniqueFieldID
+                                       inputValue:formQuery.typedValue];
+    }
     self.passwordManager->UpdateStateOnUserInput(
         self.passwordManagerDriver, formQuery.uniqueFormID,
         formQuery.uniqueFieldID, SysNSStringToUTF16(formQuery.typedValue));
diff --git a/ios/chrome/browser/passwords/password_controller_unittest.mm b/ios/chrome/browser/passwords/password_controller_unittest.mm
index 3707cfab..adecfbc 100644
--- a/ios/chrome/browser/passwords/password_controller_unittest.mm
+++ b/ios/chrome/browser/passwords/password_controller_unittest.mm
@@ -36,7 +36,6 @@
 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
 #import "ios/chrome/browser/autofill/form_suggestion_controller.h"
 #include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
-#import "ios/chrome/browser/passwords/password_form_filler.h"
 #include "ios/chrome/browser/passwords/password_manager_features.h"
 #import "ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_mediator.h"
 #include "ios/chrome/browser/web/chrome_web_client.h"
@@ -831,67 +830,6 @@
   }
 }
 
-// Tests that a form is found and the found form is filled in with the given
-// username and password.
-TEST_F(PasswordControllerTest, FindAndFillOnePasswordForm) {
-  LoadHtml(@"<form><input id='un' type='text' name='u'>"
-            "<input id='pw' type='password' name='p'></form>");
-  __block int call_counter = 0;
-  __block int success_counter = 0;
-  [passwordController_.passwordFormFiller
-      findAndFillPasswordForms:@"john.doe@gmail.com"
-                      password:@"super!secret"
-             completionHandler:^(BOOL complete) {
-               ++call_counter;
-               if (complete)
-                 ++success_counter;
-             }];
-  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
-    return call_counter == 1;
-  }));
-  EXPECT_EQ(1, success_counter);
-  id result = ExecuteJavaScript(kInputFieldValueVerificationScript);
-  EXPECT_NSEQ(@"un=john.doe@gmail.com;pw=super!secret;", result);
-}
-
-// Tests that multiple forms on the same page are found and filled.
-// This test includes an mock injected failure on form filling to verify
-// that completion handler is called with the proper values.
-TEST_F(PasswordControllerTest, FindAndFillMultiplePasswordForms) {
-  // Fails the first call to fill password form.
-  passwordController_.formHelper.jsPasswordManager =
-      [[FakeJsPasswordManager alloc] init];
-
-  LoadHtml(@"<form><input id='u1' type='text' name='un1'>"
-            "<input id='p1' type='password' name='pw1'></form>"
-            "<form><input id='u2' type='text' name='un2'>"
-            "<input id='p2' type='password' name='pw2'></form>"
-            "<form><input id='u3' type='text' name='un3'>"
-            "<input id='p3' type='password' name='pw3'></form>");
-
-  __block int call_counter = 0;
-  __block int success_counter = 0;
-  [passwordController_.passwordFormFiller
-      findAndFillPasswordForms:@"john.doe@gmail.com"
-                      password:@"super!secret"
-             completionHandler:^(BOOL complete) {
-               ++call_counter;
-               if (complete)
-                 ++success_counter;
-               LOG(INFO) << "HANDLER call " << call_counter << " success "
-                         << success_counter;
-             }];
-  // There should be 3 password forms and only 2 successfully filled forms.
-  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
-    return call_counter == 3;
-  }));
-  EXPECT_EQ(2, success_counter);
-  id result = ExecuteJavaScript(kInputFieldValueVerificationScript);
-  EXPECT_NSEQ(@"u2=john.doe@gmail.com;p2=super!secret;"
-               "u3=john.doe@gmail.com;p3=super!secret;",
-              result);
-}
-
 BOOL PasswordControllerTest::BasicFormFill(NSString* html) {
   LoadHtml(html);
   const std::string base_url = BaseUrl();
diff --git a/ios/chrome/browser/passwords/password_form_filler.h b/ios/chrome/browser/passwords/password_form_filler.h
deleted file mode 100644
index e2bd542b..0000000
--- a/ios/chrome/browser/passwords/password_form_filler.h
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_PASSWORDS_PASSWORD_FORM_FILLER_H_
-#define IOS_CHROME_BROWSER_PASSWORDS_PASSWORD_FORM_FILLER_H_
-
-#import <Foundation/Foundation.h>
-
-@protocol PasswordFormFiller<NSObject>
-
-// Finds password forms in the page and fills them with the |username| and
-// |password|. If not nil, |completionHandler| is called once per form filled.
-- (void)findAndFillPasswordForms:(NSString*)username
-                        password:(NSString*)password
-               completionHandler:(void (^)(BOOL))completionHandler;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_PASSWORDS_PASSWORD_FORM_FILLER_H_
diff --git a/ios/chrome/browser/passwords/password_tab_helper.h b/ios/chrome/browser/passwords/password_tab_helper.h
index d78821177..5df5d34 100644
--- a/ios/chrome/browser/passwords/password_tab_helper.h
+++ b/ios/chrome/browser/passwords/password_tab_helper.h
@@ -13,7 +13,6 @@
 @protocol FormSuggestionProvider;
 @class PasswordController;
 @protocol PasswordControllerDelegate;
-@protocol PasswordFormFiller;
 @protocol PasswordsUiDelegate;
 @class UIViewController;
 
@@ -44,9 +43,6 @@
   // May return nil.
   id<FormSuggestionProvider> GetSuggestionProvider();
 
-  // Returns the PasswordFormFiller from the PasswordController.
-  id<PasswordFormFiller> GetPasswordFormFiller();
-
   // Returns the PasswordGenerationFrameHelper owned by the PasswordController.
   password_manager::PasswordGenerationFrameHelper* GetGenerationHelper();
 
diff --git a/ios/chrome/browser/passwords/password_tab_helper.mm b/ios/chrome/browser/passwords/password_tab_helper.mm
index 1f761f0..c428fbc 100644
--- a/ios/chrome/browser/passwords/password_tab_helper.mm
+++ b/ios/chrome/browser/passwords/password_tab_helper.mm
@@ -42,10 +42,6 @@
   return controller_.suggestionProvider;
 }
 
-id<PasswordFormFiller> PasswordTabHelper::GetPasswordFormFiller() {
-  return controller_.passwordFormFiller;
-}
-
 password_manager::PasswordGenerationFrameHelper*
 PasswordTabHelper::GetGenerationHelper() {
   return controller_.passwordGenerationHelper;
diff --git a/ios/chrome/browser/ui/bookmarks/BUILD.gn b/ios/chrome/browser/ui/bookmarks/BUILD.gn
index cdca642..beaf58e 100644
--- a/ios/chrome/browser/ui/bookmarks/BUILD.gn
+++ b/ios/chrome/browser/ui/bookmarks/BUILD.gn
@@ -53,6 +53,7 @@
     "resources:bookmark_blue_check",
     "resources:bookmark_blue_folder",
     "resources:bookmark_blue_new_folder",
+    "resources:bookmark_empty",
     "resources:bookmark_empty_star",
     "//base",
     "//base:i18n",
diff --git a/ios/chrome/browser/ui/bookmarks/managed_bookmarks_egtest.mm b/ios/chrome/browser/ui/bookmarks/managed_bookmarks_egtest.mm
index 7a82950..240d157 100644
--- a/ios/chrome/browser/ui/bookmarks/managed_bookmarks_egtest.mm
+++ b/ios/chrome/browser/ui/bookmarks/managed_bookmarks_egtest.mm
@@ -334,6 +334,14 @@
   SwipeBookmarkNodeWithLabel(@"First_Managed_URL");
   VerifyDeleteSwipeButtonNil();
 
+  // TODO(crbug.com/1105526) On iOS14 the swipe above will trigger a tap
+  // instead, and dismiss the bookmarks UI.  Since the test is still effectively
+  // testing for swipeButton nil, simply return here.  This test should be
+  // refactored to account for swipe-on-disabled-rows-trigger-a-tap.
+  if (@available(iOS 14, *)) {
+    return;
+  }
+
   SwipeBookmarkNodeWithLabel(@"Managed_Sub_Folder");
   VerifyDeleteSwipeButtonNil();
 
diff --git a/ios/chrome/browser/ui/bookmarks/resources/BUILD.gn b/ios/chrome/browser/ui/bookmarks/resources/BUILD.gn
index ebac1550..701492b2 100644
--- a/ios/chrome/browser/ui/bookmarks/resources/BUILD.gn
+++ b/ios/chrome/browser/ui/bookmarks/resources/BUILD.gn
@@ -28,6 +28,14 @@
   ]
 }
 
+imageset("bookmark_empty") {
+  sources = [
+    "bookmark_empty.imageset/Contents.json",
+    "bookmark_empty.imageset/bookmark_empty.pdf",
+    "bookmark_empty.imageset/bookmark_empty_dark.pdf",
+  ]
+}
+
 imageset("bookmark_empty_star") {
   sources = [
     "bookmark_empty_star.imageset/Contents.json",
diff --git a/ios/chrome/browser/ui/bookmarks/resources/bookmark_empty.imageset/Contents.json b/ios/chrome/browser/ui/bookmarks/resources/bookmark_empty.imageset/Contents.json
new file mode 100644
index 0000000..10e2ada
--- /dev/null
+++ b/ios/chrome/browser/ui/bookmarks/resources/bookmark_empty.imageset/Contents.json
@@ -0,0 +1,25 @@
+{
+  "images" : [
+    {
+      "filename" : "bookmark_empty.pdf",
+      "idiom" : "universal"
+    },
+    {
+      "appearances" : [
+        {
+          "appearance" : "luminosity",
+          "value" : "dark"
+        }
+      ],
+      "filename" : "bookmark_empty_dark.pdf",
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  },
+  "properties" : {
+    "preserves-vector-representation" : true
+  }
+}
diff --git a/ios/chrome/browser/ui/bookmarks/resources/bookmark_empty.imageset/bookmark_empty.pdf b/ios/chrome/browser/ui/bookmarks/resources/bookmark_empty.imageset/bookmark_empty.pdf
new file mode 100644
index 0000000..396a9fb
--- /dev/null
+++ b/ios/chrome/browser/ui/bookmarks/resources/bookmark_empty.imageset/bookmark_empty.pdf
Binary files differ
diff --git a/ios/chrome/browser/ui/bookmarks/resources/bookmark_empty.imageset/bookmark_empty_dark.pdf b/ios/chrome/browser/ui/bookmarks/resources/bookmark_empty.imageset/bookmark_empty_dark.pdf
new file mode 100644
index 0000000..45959f4
--- /dev/null
+++ b/ios/chrome/browser/ui/bookmarks/resources/bookmark_empty.imageset/bookmark_empty_dark.pdf
Binary files differ
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
index 490bb81..9135959b 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
@@ -10,6 +10,7 @@
 #import <MaterialComponents/MaterialSnackbar.h>
 
 #include "base/base64.h"
+#include "base/ios/ios_util.h"
 #include "base/mac/bundle_locations.h"
 #include "base/mac/foundation_util.h"
 #include "base/metrics/histogram_macros.h"
@@ -3400,15 +3401,19 @@
 
   // If there is no first responder, try to make the webview or the NTP first
   // responder to have it answer keyboard commands (e.g. space bar to scroll).
-  if (!GetFirstResponder() && self.currentWebState) {
-    NewTabPageTabHelper* NTPHelper =
-        NewTabPageTabHelper::FromWebState(webState);
-    if (NTPHelper && NTPHelper->IsActive()) {
-      UIViewController* viewController =
-          _ntpCoordinatorsForWebStates[webState].viewController;
-      [viewController becomeFirstResponder];
-    } else {
-      [self.currentWebState->GetWebViewProxy() becomeFirstResponder];
+  // TODO(crbug.com/1103822): Investigate why this is causing EG2 tests to spin
+  // on iOS14.
+  if (!base::ios::IsRunningOnIOS14OrLater()) {
+    if (!GetFirstResponder() && self.currentWebState) {
+      NewTabPageTabHelper* NTPHelper =
+          NewTabPageTabHelper::FromWebState(webState);
+      if (NTPHelper && NTPHelper->IsActive()) {
+        UIViewController* viewController =
+            _ntpCoordinatorsForWebStates[webState].viewController;
+        [viewController becomeFirstResponder];
+      } else {
+        [self.currentWebState->GetWebViewProxy() becomeFirstResponder];
+      }
     }
   }
 }
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller_egtest.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller_egtest.mm
index 4fb9d42..93c5c1b 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller_egtest.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller_egtest.mm
@@ -140,11 +140,20 @@
   [ChromeEarlGrey loadURL:testURL];
   [ChromeEarlGrey waitForWebStateContainingText:"File Picker Test"];
 
-  // Invoke the file picker and tap on the "Cancel" button to dismiss the file
-  // picker.
+  // Invoke the file picker.
   [ChromeEarlGrey tapWebStateElementWithID:@"file"];
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::CancelButton()]
-      performAction:grey_tap()];
+
+  if (@available(iOS 14, *)) {
+    // Tap on the toolbar to dismiss the file picker on iOS14.  In iOS14 a
+    // UIDropShadowView covers the entire app, so tapping anywhere should
+    // dismiss the file picker.
+    [[EarlGrey selectElementWithMatcher:chrome_test_util::PrimaryToolbar()]
+        performAction:grey_tap()];
+  } else {
+    // Tap on the "Cancel" button to dismiss the file picker before iOS14.
+    [[EarlGrey selectElementWithMatcher:chrome_test_util::CancelButton()]
+        performAction:grey_tap()];
+  }
   [ChromeEarlGreyUI waitForAppToIdle];
 }
 
diff --git a/ios/chrome/browser/ui/keyboard/keyboard_commands_egtest.mm b/ios/chrome/browser/ui/keyboard/keyboard_commands_egtest.mm
index 520d343..80e205fe 100644
--- a/ios/chrome/browser/ui/keyboard/keyboard_commands_egtest.mm
+++ b/ios/chrome/browser/ui/keyboard/keyboard_commands_egtest.mm
@@ -177,6 +177,12 @@
 // Tests that when the app is opened on a web page and a key is pressed, the
 // web view is the first responder.
 - (void)testWebViewIsFirstResponderUponKeyPress {
+  // TODO(crbug.com/1103822) GetFirstResponder/becomeFirstResponder disabled
+  // on iOS14.
+  if (@available(iOS 14, *)) {
+    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS14.");
+  }
+
   GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");
   [ChromeEarlGrey loadURL:self.testServer->GetURL("/pony.html")];
 
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_egtest.mm b/ios/chrome/browser/ui/omnibox/omnibox_egtest.mm
index 64134de..9873e3d6 100644
--- a/ios/chrome/browser/ui/omnibox/omnibox_egtest.mm
+++ b/ios/chrome/browser/ui/omnibox/omnibox_egtest.mm
@@ -638,6 +638,12 @@
 #define MAYBE_testNoDefaultMatch DISABLED_testNoDefaultMatch
 #endif
 - (void)MAYBE_testNoDefaultMatch {
+  // TODO(crbug.com/1098722) Omnibox pasteboard suggestions are currently
+  // disabled on iOS14.
+  if (@available(iOS 14, *)) {
+    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS14.");
+  }
+
   NSString* copiedText = @"test no default match1";
 
   // Put some text in pasteboard.
diff --git a/ios/chrome/browser/ui/settings/settings_table_view_controller.mm b/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
index 94cbeb2d..a7ddbf10 100644
--- a/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
@@ -19,6 +19,7 @@
 #import "components/prefs/ios/pref_observer_bridge.h"
 #include "components/prefs/pref_member.h"
 #include "components/prefs/pref_service.h"
+#include "components/search_engines/search_engines_pref_names.h"
 #include "components/search_engines/util.h"
 #include "components/signin/public/base/signin_metrics.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
@@ -52,6 +53,7 @@
 #import "ios/chrome/browser/ui/settings/cells/settings_switch_cell.h"
 #import "ios/chrome/browser/ui/settings/cells/settings_switch_item.h"
 #import "ios/chrome/browser/ui/settings/content_settings_table_view_controller.h"
+#import "ios/chrome/browser/ui/settings/elements/enterprise_info_popover_view_controller.h"
 #import "ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.h"
 #import "ios/chrome/browser/ui/settings/google_services/google_services_settings_coordinator.h"
 #import "ios/chrome/browser/ui/settings/language/language_settings_mediator.h"
@@ -66,8 +68,11 @@
 #import "ios/chrome/browser/ui/settings/utils/pref_backed_boolean.h"
 #import "ios/chrome/browser/ui/settings/voice_search_table_view_controller.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h"
+#include "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_detail_icon_item.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_image_item.h"
+#import "ios/chrome/browser/ui/table_view/cells/table_view_info_button_cell.h"
+#import "ios/chrome/browser/ui/table_view/cells/table_view_info_button_item.h"
 #import "ios/chrome/browser/ui/table_view/table_view_model.h"
 #include "ios/chrome/browser/ui/ui_feature_flags.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
@@ -131,6 +136,7 @@
   ItemGoogleServices,
   ItemTypeHeader,
   ItemTypeSearchEngine,
+  ItemTypeManagedDefaultSearchEngine,
   ItemTypePasswords,
   ItemTypeAutofillCreditCard,
   ItemTypeAutofillProfile,
@@ -395,8 +401,17 @@
 
   // Basics section
   [model addSectionWithIdentifier:SectionIdentifierBasics];
-  [model addItem:[self searchEngineDetailItem]
-      toSectionWithIdentifier:SectionIdentifierBasics];
+  // Show managed UI if default search engine is managed by policy.
+  // TODO(crbug.com/1103663): support the UI when default search engine is
+  // enabled by policy.
+  if (base::FeatureList::IsEnabled(kEnableIOSManagedSettingsUI) &&
+      [self isDefaultSearchEngineDisabledByPolicy]) {
+    [model addItem:[self managedSearchEngineItem]
+        toSectionWithIdentifier:SectionIdentifierBasics];
+  } else {
+    [model addItem:[self searchEngineDetailItem]
+        toSectionWithIdentifier:SectionIdentifierBasics];
+  }
   [model addItem:[self passwordsDetailItem]
       toSectionWithIdentifier:SectionIdentifierBasics];
   [model addItem:[self autoFillCreditCardDetailItem]
@@ -518,6 +533,20 @@
   return _defaultSearchEngineItem;
 }
 
+- (TableViewInfoButtonItem*)managedSearchEngineItem {
+  TableViewInfoButtonItem* managedDefaultSearchEngineItem =
+      [[TableViewInfoButtonItem alloc]
+          initWithType:ItemTypeManagedDefaultSearchEngine];
+  managedDefaultSearchEngineItem.text =
+      l10n_util::GetNSString(IDS_IOS_SEARCH_ENGINE_SETTING_TITLE);
+  managedDefaultSearchEngineItem.iconImageName = kSettingsSearchEngineImageName;
+  managedDefaultSearchEngineItem.statusText =
+      l10n_util::GetNSString(IDS_IOS_SEARCH_ENGINE_SETTING_DISABLED_STATUS);
+  managedDefaultSearchEngineItem.accessibilityIdentifier =
+      kSettingsManagedSearchEngineCellId;
+  return managedDefaultSearchEngineItem;
+}
+
 - (TableViewItem*)passwordsDetailItem {
   BOOL passwordsEnabled = _browserState->GetPrefs()->GetBoolean(
       password_manager::prefs::kCredentialsEnableService);
@@ -783,6 +812,15 @@
 #endif  // BUILDFLAG(CHROMIUM_BRANDING) && !defined(NDEBUG)
       break;
     }
+    case ItemTypeManagedDefaultSearchEngine: {
+      TableViewInfoButtonCell* managedCell =
+          base::mac::ObjCCastStrict<TableViewInfoButtonCell>(cell);
+      [managedCell.trailingButton
+                 addTarget:self
+                    action:@selector(didTapManagedUIInfoButton:)
+          forControlEvents:UIControlEventTouchUpInside];
+      break;
+    }
     default:
       break;
   }
@@ -903,6 +941,31 @@
   }
 }
 
+#pragma mark - Actions
+
+// Called when the user clicks on the information button of a managed
+// settings UI cell. Shows a contextual bubble with the information of the
+// enterprise.
+- (void)didTapManagedUIInfoButton:(UIButton*)buttonView {
+  EnterpriseInfoPopoverViewController* bubbleViewController =
+      [[EnterpriseInfoPopoverViewController alloc] initWithEnterpriseName:nil];
+
+  // Disable the button when showing the bubble.
+  // The button will be enabled when close the bubble in
+  // (void)popoverPresentationControllerDidDismissPopover: of
+  // EnterpriseInfoPopoverViewController.
+  buttonView.enabled = NO;
+
+  // Set the anchor and arrow direction of the bubble.
+  bubbleViewController.popoverPresentationController.sourceView = buttonView;
+  bubbleViewController.popoverPresentationController.sourceRect =
+      buttonView.bounds;
+  bubbleViewController.popoverPresentationController.permittedArrowDirections =
+      UIPopoverArrowDirectionAny;
+
+  [self presentViewController:bubbleViewController animated:YES completion:nil];
+}
+
 #pragma mark Switch Actions
 
 - (void)memorySwitchToggled:(UISwitch*)sender {
@@ -1095,6 +1158,18 @@
   [self reconfigureCellsForItems:@[ googleServicesItem ]];
 }
 
+// Check if the default search engine is disabled by policy.
+- (BOOL)isDefaultSearchEngineDisabledByPolicy {
+  const base::DictionaryValue* dict = _browserState->GetPrefs()->GetDictionary(
+      DefaultSearchManager::kDefaultSearchProviderDataPrefName);
+  base::Optional<bool> disabledByPolicy =
+      dict->FindBoolPath(DefaultSearchManager::kDisabledByPolicy);
+  if (disabledByPolicy) {
+    return YES;
+  }
+  return NO;
+}
+
 #pragma mark - SigninPresenter
 
 - (void)showSignin:(ShowSigninCommand*)command {
diff --git a/ios/chrome/browser/ui/settings/settings_table_view_controller_constants.h b/ios/chrome/browser/ui/settings/settings_table_view_controller_constants.h
index 178be1a9..b0e0b535 100644
--- a/ios/chrome/browser/ui/settings/settings_table_view_controller_constants.h
+++ b/ios/chrome/browser/ui/settings/settings_table_view_controller_constants.h
@@ -19,6 +19,9 @@
 // The accessibility identifier of the Search Engine cell.
 extern NSString* const kSettingsSearchEngineCellId;
 
+// The accessibility identifier of the Managed Search Engine cell.
+extern NSString* const kSettingsManagedSearchEngineCellId;
+
 // The accessibility identifier of the Voice Search cell.
 extern NSString* const kSettingsVoiceSearchCellId;
 
diff --git a/ios/chrome/browser/ui/settings/settings_table_view_controller_constants.mm b/ios/chrome/browser/ui/settings/settings_table_view_controller_constants.mm
index 8875d3e..be97f83 100644
--- a/ios/chrome/browser/ui/settings/settings_table_view_controller_constants.mm
+++ b/ios/chrome/browser/ui/settings/settings_table_view_controller_constants.mm
@@ -12,6 +12,8 @@
 NSString* const kSettingsSignInCellId = @"kSettingsSignInCellId";
 NSString* const kSettingsAccountCellId = @"kSettingsAccountCellId";
 NSString* const kSettingsSearchEngineCellId = @"kSettingsSearchEngineCellId";
+NSString* const kSettingsManagedSearchEngineCellId =
+    @"kSettingsManagedSearchEngineCellId";
 NSString* const kSettingsVoiceSearchCellId = @"kSettingsVoiceSearchCellId";
 NSString* const kSettingsGoogleSyncAndServicesCellId =
     @"kSettingsGoogleSyncAndServicesCellId";
diff --git a/ios/chrome/browser/ui/table_view/cells/BUILD.gn b/ios/chrome/browser/ui/table_view/cells/BUILD.gn
index fb2aaf1a..004ed7c 100644
--- a/ios/chrome/browser/ui/table_view/cells/BUILD.gn
+++ b/ios/chrome/browser/ui/table_view/cells/BUILD.gn
@@ -16,6 +16,8 @@
     "table_view_disclosure_header_footer_item.mm",
     "table_view_header_footer_item.h",
     "table_view_header_footer_item.mm",
+    "table_view_illustrated_item.h",
+    "table_view_illustrated_item.mm",
     "table_view_image_item.h",
     "table_view_image_item.mm",
     "table_view_info_button_cell.h",
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_illustrated_item.h b/ios/chrome/browser/ui/table_view/cells/table_view_illustrated_item.h
new file mode 100644
index 0000000..67f4884
--- /dev/null
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_illustrated_item.h
@@ -0,0 +1,37 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_TABLE_VIEW_CELLS_TABLE_VIEW_ILLUSTRATED_ITEM_H_
+#define IOS_CHROME_BROWSER_UI_TABLE_VIEW_CELLS_TABLE_VIEW_ILLUSTRATED_ITEM_H_
+
+#import <UIKit/UIKit.h>
+
+#import "ios/chrome/browser/ui/table_view/cells/table_view_cell.h"
+#import "ios/chrome/browser/ui/table_view/cells/table_view_item.h"
+
+// TableViewItem for the TableView Illustrated Cell.
+@interface TableViewIllustratedItem : TableViewItem
+// Image being displayed.
+@property(nonatomic, strong) UIImage* image;
+// Title being displayed under the image.
+@property(nonatomic, copy) NSString* title;
+// Subtitle being displayed under the title.
+@property(nonatomic, copy) NSString* subtitle;
+// Text of the button displayed under the subtitle.
+@property(nonatomic, copy) NSString* buttonText;
+@end
+
+// TableViewCell that displays an image, title, subtitle and button.
+@interface TableViewIllustratedCell : TableViewCell
+// The imageview that will display the image at the top of the cell.
+@property(nonatomic, readonly, strong) UIImageView* illustratedImageView;
+// Label displaying the title, underneath the image.
+@property(nonatomic, readonly, strong) UILabel* titleLabel;
+// Label displaying the subtitle, underneath the title.
+@property(nonatomic, readonly, strong) UILabel* subtitleLabel;
+// Button that will be displayed under the subtitle.
+@property(nonatomic, readonly, strong) UIButton* button;
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_TABLE_VIEW_CELLS_TABLE_VIEW_ILLUSTRATED_ITEM_H_
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_illustrated_item.mm b/ios/chrome/browser/ui/table_view/cells/table_view_illustrated_item.mm
new file mode 100644
index 0000000..c19726b
--- /dev/null
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_illustrated_item.mm
@@ -0,0 +1,185 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/table_view/cells/table_view_illustrated_item.h"
+
+#include "base/mac/foundation_util.h"
+#import "ios/chrome/browser/ui/table_view/chrome_table_view_styler.h"
+#import "ios/chrome/common/ui/colors/semantic_color_names.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+// The insets of the View content and additional margin for some of its items.
+const CGFloat kStackMargin = 32.0;
+const CGFloat kItemMargin = 16.0;
+// Spacing within stackView.
+const CGFloat kStackViewSpacing = 13.0;
+// Height of the image.
+const CGFloat kImageViewHeight = 150.0;
+// Horizontal Inset between button contents and edge.
+const CGFloat kButtonTitleHorizontalContentInset = 40.0;
+// Vertical Inset between button contents and edge.
+const CGFloat kButtonTitleVerticalContentInset = 8.0;
+// Button corner radius.
+const CGFloat kButtonCornerRadius = 8.0;
+}
+
+#pragma mark - TableViewIllustratedItem
+
+@implementation TableViewIllustratedItem
+
+- (instancetype)initWithType:(NSInteger)type {
+  self = [super initWithType:type];
+  if (self) {
+    self.cellClass = [TableViewIllustratedCell class];
+  }
+  return self;
+}
+
+- (void)configureCell:(TableViewCell*)tableCell
+           withStyler:(ChromeTableViewStyler*)styler {
+  [super configureCell:tableCell withStyler:styler];
+  TableViewIllustratedCell* cell =
+      base::mac::ObjCCastStrict<TableViewIllustratedCell>(tableCell);
+  [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
+  if (self.image) {
+    cell.illustratedImageView.image = self.image;
+  } else {
+    cell.illustratedImageView.hidden = YES;
+  }
+  if ([self.title length]) {
+    cell.titleLabel.text = self.title;
+  } else {
+    cell.titleLabel.hidden = YES;
+  }
+  if ([self.subtitle length]) {
+    cell.subtitleLabel.text = self.subtitle;
+  } else {
+    cell.subtitleLabel.hidden = YES;
+  }
+  if ([self.buttonText length]) {
+    [cell.button setTitle:self.buttonText forState:UIControlStateNormal];
+  } else {
+    cell.button.hidden = YES;
+  }
+
+  if (styler.cellTitleColor) {
+    cell.titleLabel.textColor = styler.cellTitleColor;
+  }
+  if (styler.tintColor) {
+    cell.button.backgroundColor = styler.tintColor;
+  }
+  if (styler.solidButtonTextColor) {
+    [cell.button setTitleColor:styler.solidButtonTextColor
+                      forState:UIControlStateNormal];
+  }
+}
+
+@end
+
+#pragma mark - TableViewIllustratedCell
+
+@implementation TableViewIllustratedCell
+
+- (instancetype)initWithStyle:(UITableViewCellStyle)style
+              reuseIdentifier:(NSString*)reuseIdentifier {
+  self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
+  if (self) {
+    _illustratedImageView = [[UIImageView alloc] initWithImage:nil];
+    _illustratedImageView.contentMode = UIViewContentModeScaleAspectFit;
+    _illustratedImageView.translatesAutoresizingMaskIntoConstraints = NO;
+    _illustratedImageView.clipsToBounds = YES;
+
+    _titleLabel = [[UILabel alloc] init];
+    _titleLabel.textColor = [UIColor colorNamed:kTextPrimaryColor];
+    _titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleTitle2];
+    _titleLabel.textAlignment = NSTextAlignmentCenter;
+    _titleLabel.numberOfLines = 0;
+    _titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
+
+    _subtitleLabel = [[UILabel alloc] init];
+    _subtitleLabel.textColor = [UIColor colorNamed:kTextSecondaryColor];
+    _subtitleLabel.font =
+        [UIFont preferredFontForTextStyle:UIFontTextStyleFootnote];
+    _subtitleLabel.textAlignment = NSTextAlignmentCenter;
+    _subtitleLabel.numberOfLines = 0;
+    _subtitleLabel.translatesAutoresizingMaskIntoConstraints = NO;
+
+    _button = [[UIButton alloc] init];
+    _button.backgroundColor = [UIColor colorNamed:kBlueColor];
+    [_button.titleLabel
+        setFont:[UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]];
+    _button.layer.cornerRadius = kButtonCornerRadius;
+    _button.translatesAutoresizingMaskIntoConstraints = NO;
+    _button.contentEdgeInsets = UIEdgeInsetsMake(
+        kButtonTitleVerticalContentInset, kButtonTitleHorizontalContentInset,
+        kButtonTitleVerticalContentInset, kButtonTitleHorizontalContentInset);
+
+    UIStackView* stackView = [[UIStackView alloc] initWithArrangedSubviews:@[
+      _illustratedImageView, _titleLabel, _subtitleLabel, _button
+    ]];
+    stackView.axis = UILayoutConstraintAxisVertical;
+    stackView.alignment = UIStackViewAlignmentCenter;
+    stackView.spacing = kStackViewSpacing;
+    stackView.translatesAutoresizingMaskIntoConstraints = NO;
+
+    [self.contentView addSubview:stackView];
+
+    // Set and activate constraints.
+    [NSLayoutConstraint activateConstraints:@[
+      [_illustratedImageView.heightAnchor
+          constraintEqualToConstant:kImageViewHeight],
+      [_illustratedImageView.leadingAnchor
+          constraintEqualToAnchor:stackView.leadingAnchor],
+      [_illustratedImageView.trailingAnchor
+          constraintEqualToAnchor:stackView.trailingAnchor],
+
+      [_titleLabel.leadingAnchor
+          constraintEqualToAnchor:stackView.leadingAnchor],
+      [_titleLabel.trailingAnchor
+          constraintEqualToAnchor:stackView.trailingAnchor],
+
+      // Subtitle and button should have additional margins on both sides
+      [_subtitleLabel.leadingAnchor
+          constraintEqualToAnchor:stackView.leadingAnchor
+                         constant:kItemMargin],
+      [_subtitleLabel.trailingAnchor
+          constraintEqualToAnchor:stackView.trailingAnchor
+                         constant:-kItemMargin],
+      [_button.leadingAnchor constraintEqualToAnchor:stackView.leadingAnchor
+                                            constant:kItemMargin],
+      [_button.trailingAnchor constraintEqualToAnchor:stackView.trailingAnchor
+                                             constant:-kItemMargin],
+
+      [stackView.leadingAnchor
+          constraintEqualToAnchor:self.contentView.leadingAnchor
+                         constant:kStackMargin],
+      [stackView.trailingAnchor
+          constraintEqualToAnchor:self.contentView.trailingAnchor
+                         constant:-kStackMargin],
+      [stackView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor],
+      [stackView.bottomAnchor
+          constraintEqualToAnchor:self.contentView.bottomAnchor],
+    ]];
+  }
+  return self;
+}
+
+- (void)prepareForReuse {
+  [super prepareForReuse];
+
+  self.illustratedImageView.hidden = NO;
+  self.titleLabel.hidden = NO;
+  self.subtitleLabel.hidden = NO;
+  self.button.hidden = NO;
+
+  [self.button removeTarget:nil
+                     action:nil
+           forControlEvents:UIControlEventAllEvents];
+}
+
+@end
diff --git a/ios/web/public/test/fakes/fake_cookie_store.h b/ios/web/public/test/fakes/fake_cookie_store.h
index 829aaf3a..f9f600ff 100644
--- a/ios/web/public/test/fakes/fake_cookie_store.h
+++ b/ios/web/public/test/fakes/fake_cookie_store.h
@@ -45,7 +45,7 @@
 
  private:
   net::CookieList all_cookies_;
-  net::CookieStatusList excluded_list_;
+  net::CookieAccessResultList excluded_list_;
 };
 
 }  // namespace web
diff --git a/ios/web_view/internal/passwords/web_view_password_feature_manager.h b/ios/web_view/internal/passwords/web_view_password_feature_manager.h
index 9c2ec2d..1a68f973 100644
--- a/ios/web_view/internal/passwords/web_view_password_feature_manager.h
+++ b/ios/web_view/internal/passwords/web_view_password_feature_manager.h
@@ -27,7 +27,8 @@
 
   bool IsOptedInForAccountStorage() const override;
   bool ShouldShowAccountStorageOptIn() const override;
-  bool ShouldShowAccountStorageReSignin() const override;
+  bool ShouldShowAccountStorageReSignin(
+      const GURL& current_page_url) const override;
   void OptInToAccountStorage() override;
   void OptOutOfAccountStorageAndClearSettings() override;
 
diff --git a/ios/web_view/internal/passwords/web_view_password_feature_manager.mm b/ios/web_view/internal/passwords/web_view_password_feature_manager.mm
index 439d8e3..e19121cb 100644
--- a/ios/web_view/internal/passwords/web_view_password_feature_manager.mm
+++ b/ios/web_view/internal/passwords/web_view_password_feature_manager.mm
@@ -35,7 +35,8 @@
   return false;
 }
 
-bool WebViewPasswordFeatureManager::ShouldShowAccountStorageReSignin() const {
+bool WebViewPasswordFeatureManager::ShouldShowAccountStorageReSignin(
+    const GURL& current_page_url) const {
   return false;
 }
 
diff --git a/media/audio/android/audio_manager_android.cc b/media/audio/android/audio_manager_android.cc
index f96d9f8dc7..827882e 100644
--- a/media/audio/android/audio_manager_android.cc
+++ b/media/audio/android/audio_manager_android.cc
@@ -488,9 +488,9 @@
   if (!base::FeatureList::IsEnabled(features::kUseAAudioDriver))
     return false;
 
-  if (base::android::BuildInfo::GetInstance()->sdk_int() <
-      base::android::SDK_VERSION_P) {
-    // We need APIs that weren't added until API Level 28.
+  if (!base::android::BuildInfo::GetInstance()->is_at_least_q()) {
+    // We need APIs that weren't added until API Level 28. Also, AAudio crashes
+    // on Android P, so only consider Q and above.
     return false;
   }
 
diff --git a/media/base/callback_registry.h b/media/base/callback_registry.h
index 5e4ea16..fe519cb 100644
--- a/media/base/callback_registry.h
+++ b/media/base/callback_registry.h
@@ -38,7 +38,7 @@
 // thread. The CallbackRegistry must outlive all CallbackRegistrations returned
 // by Register().
 // TODO(xhwang): This class is similar to base::CallbackList, but is simpler,
-// and provides thread safty. Consider merging these two.
+// and provides thread safety. Consider merging these two.
 template <typename... Args>
 class CallbackRegistry<void(Args...)> {
  public:
diff --git a/media/base/decryptor.h b/media/base/decryptor.h
index e084056..dbc91c7 100644
--- a/media/base/decryptor.h
+++ b/media/base/decryptor.h
@@ -44,19 +44,6 @@
   Decryptor();
   virtual ~Decryptor();
 
-  // Indicates that a new key has been added to the ContentDecryptionModule
-  // object associated with the Decryptor.
-  using NewKeyCB = base::RepeatingClosure;
-
-  // Registers a NewKeyCB which should be called when a new key is added to the
-  // decryptor. Only one NewKeyCB can be registered for one |stream_type|.
-  // If this function is called multiple times for the same |stream_type|, the
-  // previously registered callback will be replaced. In other words,
-  // registering a null callback cancels the originally registered callback.
-  // TODO(crbug.com/821288): Replace this with CdmContext::RegisterEventCB().
-  virtual void RegisterNewKeyCB(StreamType stream_type,
-                                NewKeyCB key_added_cb) = 0;
-
   // Indicates completion of a decryption operation.
   //
   // First parameter: The status of the decryption operation.
diff --git a/media/base/demuxer_stream.h b/media/base/demuxer_stream.h
index 1f45966..3023689 100644
--- a/media/base/demuxer_stream.h
+++ b/media/base/demuxer_stream.h
@@ -73,10 +73,6 @@
   typedef base::OnceCallback<void(Status, scoped_refptr<DecoderBuffer>)> ReadCB;
   virtual void Read(ReadCB read_cb) = 0;
 
-  // Returns true if a Read() call has been made but the |read_cb| has not yet
-  // been run.
-  virtual bool IsReadPending() const = 0;
-
   // Returns the audio/video decoder configuration. It is an error to call the
   // audio method on a video stream and vice versa. After |kConfigChanged| is
   // returned in a Read(), the caller should call this method again to retrieve
diff --git a/media/base/fake_demuxer_stream.cc b/media/base/fake_demuxer_stream.cc
index 0fb35ec..79cde5e 100644
--- a/media/base/fake_demuxer_stream.cc
+++ b/media/base/fake_demuxer_stream.cc
@@ -94,10 +94,6 @@
   DoRead();
 }
 
-bool FakeDemuxerStream::IsReadPending() const {
-  return !read_cb_.is_null();
-}
-
 AudioDecoderConfig FakeDemuxerStream::audio_decoder_config() {
   DCHECK(task_runner_->BelongsToCurrentThread());
   NOTREACHED();
diff --git a/media/base/fake_demuxer_stream.h b/media/base/fake_demuxer_stream.h
index 9abb3789..0a0a241d 100644
--- a/media/base/fake_demuxer_stream.h
+++ b/media/base/fake_demuxer_stream.h
@@ -43,7 +43,6 @@
 
   // DemuxerStream implementation.
   void Read(ReadCB read_cb) override;
-  bool IsReadPending() const override;
   AudioDecoderConfig audio_decoder_config() override;
   VideoDecoderConfig video_decoder_config() override;
   Type type() const override;
diff --git a/media/base/fake_text_track_stream.cc b/media/base/fake_text_track_stream.cc
index f734a6b..72a1655 100644
--- a/media/base/fake_text_track_stream.cc
+++ b/media/base/fake_text_track_stream.cc
@@ -37,10 +37,6 @@
   }
 }
 
-bool FakeTextTrackStream::IsReadPending() const {
-  return !read_cb_.is_null();
-}
-
 DemuxerStream::Type FakeTextTrackStream::type() const {
   return DemuxerStream::TEXT;
 }
diff --git a/media/base/fake_text_track_stream.h b/media/base/fake_text_track_stream.h
index 2475e5c7..83b087e 100644
--- a/media/base/fake_text_track_stream.h
+++ b/media/base/fake_text_track_stream.h
@@ -26,7 +26,6 @@
 
   // DemuxerStream implementation.
   void Read(ReadCB) override;
-  bool IsReadPending() const override;
   MOCK_METHOD0(audio_decoder_config, AudioDecoderConfig());
   MOCK_METHOD0(video_decoder_config, VideoDecoderConfig());
   Type type() const override;
diff --git a/media/base/media_serializers.h b/media/base/media_serializers.h
index 832625d..e86737d 100644
--- a/media/base/media_serializers.h
+++ b/media/base/media_serializers.h
@@ -378,7 +378,7 @@
 struct MediaSerializer<base::Location> {
   static base::Value Serialize(const base::Location& value) {
     base::Value result(base::Value::Type::DICTIONARY);
-    FIELD_SERIALIZE("file", value.file_name());
+    FIELD_SERIALIZE("file", value.file_name() ? value.file_name() : "unknown");
     FIELD_SERIALIZE("line", value.line_number());
     return result;
   }
diff --git a/media/base/mock_filters.h b/media/base/mock_filters.h
index 1929c6e..6249e02 100644
--- a/media/base/mock_filters.h
+++ b/media/base/mock_filters.h
@@ -18,6 +18,7 @@
 #include "media/base/audio_decoder_config.h"
 #include "media/base/audio_parameters.h"
 #include "media/base/audio_renderer.h"
+#include "media/base/callback_registry.h"
 #include "media/base/cdm_config.h"
 #include "media/base/cdm_context.h"
 #include "media/base/cdm_factory.h"
@@ -195,7 +196,6 @@
   Liveness liveness() const override;
   void Read(ReadCB read_cb) { OnRead(read_cb); }
   MOCK_METHOD1(OnRead, void(ReadCB& read_cb));
-  MOCK_CONST_METHOD0(IsReadPending, bool());
   AudioDecoderConfig audio_decoder_config() override;
   VideoDecoderConfig video_decoder_config() override;
   MOCK_METHOD0(EnableBitstreamConverter, void());
@@ -513,8 +513,6 @@
   MockDecryptor();
   ~MockDecryptor() override;
 
-  MOCK_METHOD2(RegisterNewKeyCB,
-               void(StreamType stream_type, NewKeyCB new_key_cb));
   MOCK_METHOD3(Decrypt,
                void(StreamType stream_type,
                     scoped_refptr<DecoderBuffer> encrypted,
@@ -543,6 +541,8 @@
   MockCdmContext();
   ~MockCdmContext() override;
 
+  MOCK_METHOD1(RegisterEventCB,
+               std::unique_ptr<CallbackRegistration>(EventCB event_cb));
   MOCK_METHOD0(GetDecryptor, Decryptor*());
   MOCK_METHOD0(RequiresMediaFoundationRenderer, bool());
 
diff --git a/media/blink/webmediaplayer_impl.cc b/media/blink/webmediaplayer_impl.cc
index fc7ec22..f0289c4d 100644
--- a/media/blink/webmediaplayer_impl.cc
+++ b/media/blink/webmediaplayer_impl.cc
@@ -2982,8 +2982,8 @@
 
   // Prevent duplicate delegate calls.
   // TODO(sandersd): Move this deduplication into the delegate itself.
-  // TODO(sandersd): WebContentsObserverSequenceChecker does not allow sending
-  // the 'playing' IPC more than once in a row, even if the metadata has
+  // TODO(sandersd): WebContentsObserverConsistencyChecker does not allow
+  // sending the 'playing' IPC more than once in a row, even if the metadata has
   // changed. Figure out whether it should.
   // Pretend that the media has no audio if it never played unmuted. This is to
   // avoid any action related to audible media such as taking audio focus or
diff --git a/media/cdm/aes_decryptor.cc b/media/cdm/aes_decryptor.cc
index c824781..aaa5775 100644
--- a/media/cdm/aes_decryptor.cc
+++ b/media/cdm/aes_decryptor.cc
@@ -16,7 +16,6 @@
 #include "base/time/time.h"
 #include "crypto/symmetric_key.h"
 #include "media/base/audio_decoder_config.h"
-#include "media/base/callback_registry.h"
 #include "media/base/cdm_promise.h"
 #include "media/base/decoder_buffer.h"
 #include "media/base/decrypt_config.h"
@@ -338,18 +337,8 @@
 void AesDecryptor::FinishUpdate(const std::string& session_id,
                                 bool key_added,
                                 std::unique_ptr<SimpleCdmPromise> promise) {
-  {
-    base::AutoLock auto_lock(new_key_cb_lock_);
-
-    if (new_audio_key_cb_)
-      new_audio_key_cb_.Run();
-
-    if (new_video_key_cb_)
-      new_video_key_cb_.Run();
-  }
-
+  event_callbacks_.Notify(Event::kHasAdditionalUsableKey);
   promise->resolve();
-
   session_keys_change_cb_.Run(
       session_id, key_added,
       GenerateKeysInfoList(session_id, CdmKeyInformation::USABLE));
@@ -457,8 +446,7 @@
 
 std::unique_ptr<CallbackRegistration> AesDecryptor::RegisterEventCB(
     EventCB event_cb) {
-  NOTIMPLEMENTED();
-  return nullptr;
+  return event_callbacks_.Register(std::move(event_cb));
 }
 
 Decryptor* AesDecryptor::GetDecryptor() {
@@ -469,22 +457,6 @@
   return kInvalidCdmId;
 }
 
-void AesDecryptor::RegisterNewKeyCB(StreamType stream_type,
-                                    NewKeyCB new_key_cb) {
-  base::AutoLock auto_lock(new_key_cb_lock_);
-
-  switch (stream_type) {
-    case kAudio:
-      new_audio_key_cb_ = std::move(new_key_cb);
-      break;
-    case kVideo:
-      new_video_key_cb_ = std::move(new_key_cb);
-      break;
-    default:
-      NOTREACHED();
-  }
-}
-
 void AesDecryptor::Decrypt(StreamType stream_type,
                            scoped_refptr<DecoderBuffer> encrypted,
                            DecryptCB decrypt_cb) {
diff --git a/media/cdm/aes_decryptor.h b/media/cdm/aes_decryptor.h
index 640e0ad..89dfd6b 100644
--- a/media/cdm/aes_decryptor.h
+++ b/media/cdm/aes_decryptor.h
@@ -17,6 +17,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/synchronization/lock.h"
 #include "base/thread_annotations.h"
+#include "media/base/callback_registry.h"
 #include "media/base/cdm_context.h"
 #include "media/base/cdm_key_information.h"
 #include "media/base/cdm_promise.h"
@@ -68,7 +69,6 @@
   int GetCdmId() const override;
 
   // Decryptor implementation.
-  void RegisterNewKeyCB(StreamType stream_type, NewKeyCB key_added_cb) override;
   void Decrypt(StreamType stream_type,
                scoped_refptr<DecoderBuffer> encrypted,
                DecryptCB decrypt_cb) override;
@@ -194,11 +194,7 @@
   // CdmSessionType for each session.
   std::map<std::string, CdmSessionType> open_sessions_;
 
-  // Protect |new_audio_key_cb_| and |new_video_key_cb_| as they are set on the
-  // main thread but called on the media thread.
-  mutable base::Lock new_key_cb_lock_;
-  NewKeyCB new_audio_key_cb_ GUARDED_BY(new_key_cb_lock_);
-  NewKeyCB new_video_key_cb_ GUARDED_BY(new_key_cb_lock_);
+  CallbackRegistry<EventCB::RunType> event_callbacks_;
 
   DISALLOW_COPY_AND_ASSIGN(AesDecryptor);
 };
diff --git a/media/cdm/cdm_adapter.cc b/media/cdm/cdm_adapter.cc
index bd619e71..9362071 100644
--- a/media/cdm/cdm_adapter.cc
+++ b/media/cdm/cdm_adapter.cc
@@ -405,8 +405,7 @@
 
 std::unique_ptr<CallbackRegistration> CdmAdapter::RegisterEventCB(
     EventCB event_cb) {
-  NOTIMPLEMENTED();
-  return nullptr;
+  return event_callbacks_.Register(std::move(event_cb));
 }
 
 Decryptor* CdmAdapter::GetDecryptor() {
@@ -419,22 +418,6 @@
   return CdmContext::kInvalidCdmId;
 }
 
-void CdmAdapter::RegisterNewKeyCB(StreamType stream_type,
-                                  NewKeyCB key_added_cb) {
-  DVLOG(3) << __func__;
-  DCHECK(task_runner_->BelongsToCurrentThread());
-  switch (stream_type) {
-    case kAudio:
-      new_audio_key_cb_ = std::move(key_added_cb);
-      return;
-    case kVideo:
-      new_video_key_cb_ = std::move(key_added_cb);
-      return;
-  }
-
-  NOTREACHED() << "Unexpected StreamType " << stream_type;
-}
-
 void CdmAdapter::Decrypt(StreamType stream_type,
                          scoped_refptr<DecoderBuffer> encrypted,
                          DecryptCB decrypt_cb) {
@@ -810,14 +793,8 @@
         info.system_code));
   }
 
-  // TODO(jrummell): Handling resume playback should be done in the media
-  // player, not in the Decryptors. http://crbug.com/413413.
-  if (has_additional_usable_key) {
-    if (new_audio_key_cb_)
-      new_audio_key_cb_.Run();
-    if (new_video_key_cb_)
-      new_video_key_cb_.Run();
-  }
+  if (has_additional_usable_key)
+    event_callbacks_.Notify(Event::kHasAdditionalUsableKey);
 
   session_keys_change_cb_.Run(session_id_str, has_additional_usable_key,
                               std::move(keys));
diff --git a/media/cdm/cdm_adapter.h b/media/cdm/cdm_adapter.h
index 199c1b51..5aeb51fbe 100644
--- a/media/cdm/cdm_adapter.h
+++ b/media/cdm/cdm_adapter.h
@@ -20,6 +20,7 @@
 #include "base/scoped_native_library.h"
 #include "base/threading/thread.h"
 #include "media/base/audio_buffer.h"
+#include "media/base/callback_registry.h"
 #include "media/base/cdm_config.h"
 #include "media/base/cdm_context.h"
 #include "media/base/cdm_factory.h"
@@ -96,7 +97,6 @@
   int GetCdmId() const final;
 
   // Decryptor implementation.
-  void RegisterNewKeyCB(StreamType stream_type, NewKeyCB key_added_cb) final;
   void Decrypt(StreamType stream_type,
                scoped_refptr<DecoderBuffer> encrypted,
                DecryptCB decrypt_cb) final;
@@ -233,9 +233,7 @@
   DecoderInitCB audio_init_cb_;
   DecoderInitCB video_init_cb_;
 
-  // Callbacks for new keys added.
-  NewKeyCB new_audio_key_cb_;
-  NewKeyCB new_video_key_cb_;
+  CallbackRegistry<EventCB::RunType> event_callbacks_;
 
   // Keep track of audio parameters.
   int audio_samples_per_second_ = 0;
diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc
index 782cfa9..db2ba40a 100644
--- a/media/filters/chunk_demuxer.cc
+++ b/media/filters/chunk_demuxer.cc
@@ -311,11 +311,6 @@
   CompletePendingReadIfPossible_Locked();
 }
 
-bool ChunkDemuxerStream::IsReadPending() const {
-  base::AutoLock auto_lock(lock_);
-  return !read_cb_.is_null();
-}
-
 DemuxerStream::Type ChunkDemuxerStream::type() const { return type_; }
 
 DemuxerStream::Liveness ChunkDemuxerStream::liveness() const {
diff --git a/media/filters/chunk_demuxer.h b/media/filters/chunk_demuxer.h
index dc6dcfa..f86d931 100644
--- a/media/filters/chunk_demuxer.h
+++ b/media/filters/chunk_demuxer.h
@@ -119,7 +119,6 @@
 
   // DemuxerStream methods.
   void Read(ReadCB read_cb) override;
-  bool IsReadPending() const override;
   Type type() const override;
   Liveness liveness() const override;
   AudioDecoderConfig audio_decoder_config() override;
diff --git a/media/filters/decoder_selector_unittest.cc b/media/filters/decoder_selector_unittest.cc
index b498309..d018657 100644
--- a/media/filters/decoder_selector_unittest.cc
+++ b/media/filters/decoder_selector_unittest.cc
@@ -30,6 +30,7 @@
 using ::base::test::RunCallback;
 using ::base::test::RunOnceCallback;
 using ::testing::_;
+using ::testing::AnyNumber;
 using ::testing::IsNull;
 using ::testing::NiceMock;
 using ::testing::NotNull;
@@ -341,6 +342,8 @@
 
     cdm_context_ = std::make_unique<StrictMock<MockCdmContext>>();
 
+    EXPECT_CALL(*cdm_context_, RegisterEventCB(_)).Times(AnyNumber());
+
     if (capability == kNoDecryptor) {
       EXPECT_CALL(*cdm_context_, GetDecryptor())
           .WillRepeatedly(Return(nullptr));
diff --git a/media/filters/decoder_stream.h b/media/filters/decoder_stream.h
index 1e4d3331c..b130828 100644
--- a/media/filters/decoder_stream.h
+++ b/media/filters/decoder_stream.h
@@ -146,6 +146,8 @@
     return fallback_buffers_.size();
   }
 
+  bool is_demuxer_read_pending() const { return pending_demuxer_read_; }
+
   DecoderSelector<StreamType>& GetDecoderSelectorForTesting(
       util::PassKey<class VideoDecoderStreamTest>) {
     return decoder_selector_;
diff --git a/media/filters/decrypting_audio_decoder.cc b/media/filters/decrypting_audio_decoder.cc
index 1945c25c..f8fb421 100644
--- a/media/filters/decrypting_audio_decoder.cc
+++ b/media/filters/decrypting_audio_decoder.cc
@@ -18,7 +18,6 @@
 #include "media/base/audio_decoder_config.h"
 #include "media/base/audio_timestamp_helper.h"
 #include "media/base/bind_to_current_loop.h"
-#include "media/base/cdm_context.h"
 #include "media/base/decoder_buffer.h"
 #include "media/base/media_log.h"
 #include "media/base/timestamp_constants.h"
@@ -74,7 +73,6 @@
   // the decryptor for clear content as well.
   support_clear_content_ = true;
 
-  weak_this_ = weak_factory_.GetWeakPtr();
   output_cb_ = BindToCurrentLoop(output_cb);
 
   DCHECK(waiting_cb);
@@ -97,6 +95,9 @@
     }
 
     decryptor_ = cdm_context->GetDecryptor();
+    event_cb_registration_ = cdm_context->RegisterEventCB(
+        base::BindRepeating(&DecryptingAudioDecoder::OnCdmContextEvent,
+                            weak_factory_.GetWeakPtr()));
   } else {
     // Reinitialization (i.e. upon a config change). The new config can be
     // encrypted or clear.
@@ -190,8 +191,9 @@
 void DecryptingAudioDecoder::InitializeDecoder() {
   state_ = kPendingDecoderInit;
   decryptor_->InitializeAudioDecoder(
-      config_, BindToCurrentLoop(base::Bind(
-                   &DecryptingAudioDecoder::FinishInitialization, weak_this_)));
+      config_, BindToCurrentLoop(
+                   base::BindOnce(&DecryptingAudioDecoder::FinishInitialization,
+                                  weak_factory_.GetWeakPtr())));
 }
 
 void DecryptingAudioDecoder::FinishInitialization(bool success) {
@@ -206,6 +208,7 @@
     DVLOG(1) << __func__ << ": failed to init audio decoder on decryptor";
     std::move(init_cb_).Run(StatusCode::kDecoderInitializeNeverCompleted);
     decryptor_ = nullptr;
+    event_cb_registration_.reset();
     state_ = kError;
     return;
   }
@@ -214,10 +217,6 @@
   timestamp_helper_.reset(
       new AudioTimestampHelper(config_.samples_per_second()));
 
-  decryptor_->RegisterNewKeyCB(
-      Decryptor::kAudio, BindToCurrentLoop(base::BindRepeating(
-                             &DecryptingAudioDecoder::OnKeyAdded, weak_this_)));
-
   state_ = kIdle;
   std::move(init_cb_).Run(OkStatus());
 }
@@ -232,9 +231,9 @@
   }
 
   decryptor_->DecryptAndDecodeAudio(
-      pending_buffer_to_decode_,
-      BindToCurrentLoop(base::Bind(&DecryptingAudioDecoder::DeliverFrame,
-                                   weak_this_, buffer_size)));
+      pending_buffer_to_decode_, BindToCurrentLoop(base::BindRepeating(
+                                     &DecryptingAudioDecoder::DeliverFrame,
+                                     weak_factory_.GetWeakPtr(), buffer_size)));
 }
 
 void DecryptingAudioDecoder::DeliverFrame(
@@ -319,9 +318,12 @@
   std::move(decode_cb_).Run(DecodeStatus::OK);
 }
 
-void DecryptingAudioDecoder::OnKeyAdded() {
+void DecryptingAudioDecoder::OnCdmContextEvent(CdmContext::Event event) {
   DCHECK(task_runner_->BelongsToCurrentThread());
 
+  if (event != CdmContext::Event::kHasAdditionalUsableKey)
+    return;
+
   if (state_ == kPendingDecode) {
     key_added_while_decode_pending_ = true;
     return;
diff --git a/media/filters/decrypting_audio_decoder.h b/media/filters/decrypting_audio_decoder.h
index 55d2fc4..d176cdb8 100644
--- a/media/filters/decrypting_audio_decoder.h
+++ b/media/filters/decrypting_audio_decoder.h
@@ -13,6 +13,8 @@
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
 #include "media/base/audio_decoder.h"
+#include "media/base/callback_registry.h"
+#include "media/base/cdm_context.h"
 #include "media/base/decryptor.h"
 #include "media/base/demuxer_stream.h"
 
@@ -79,9 +81,8 @@
                     Decryptor::Status status,
                     const Decryptor::AudioFrames& frames);
 
-  // Callback for the |decryptor_| to notify this object that a new key has been
-  // added.
-  void OnKeyAdded();
+  // Callback for the CDM to notify |this|.
+  void OnCdmContextEvent(CdmContext::Event event);
 
   // Resets decoder and calls |reset_cb_|.
   void DoReset();
@@ -122,7 +123,9 @@
   // clear content, we want to ensure this decoder remains used.
   bool support_clear_content_ = false;
 
-  base::WeakPtr<DecryptingAudioDecoder> weak_this_;
+  // To keep the CdmContext event callback registered.
+  std::unique_ptr<CallbackRegistration> event_cb_registration_;
+
   base::WeakPtrFactory<DecryptingAudioDecoder> weak_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(DecryptingAudioDecoder);
diff --git a/media/filters/decrypting_audio_decoder_unittest.cc b/media/filters/decrypting_audio_decoder_unittest.cc
index c364c73..25717ca 100644
--- a/media/filters/decrypting_audio_decoder_unittest.cc
+++ b/media/filters/decrypting_audio_decoder_unittest.cc
@@ -109,8 +109,10 @@
     EXPECT_CALL(*decryptor_, InitializeAudioDecoder(_, _))
         .Times(AtMost(1))
         .WillOnce(RunOnceCallback<1>(true));
-    EXPECT_CALL(*decryptor_, RegisterNewKeyCB(Decryptor::kAudio, _))
-        .WillOnce(SaveArg<1>(&key_added_cb_));
+    EXPECT_CALL(*cdm_context_, RegisterEventCB(_)).WillOnce([&](auto cb) {
+      event_cb_ = cb;
+      return std::make_unique<CallbackRegistration>();
+    });
 
     config_.Initialize(kCodecVorbis, kSampleFormatPlanarF32,
                        CHANNEL_LAYOUT_STEREO, kSampleRate, EmptyExtraData(),
@@ -124,8 +126,6 @@
     EXPECT_CALL(*decryptor_, DeinitializeDecoder(Decryptor::kAudio));
     EXPECT_CALL(*decryptor_, InitializeAudioDecoder(_, _))
         .WillOnce(RunOnceCallback<1>(true));
-    EXPECT_CALL(*decryptor_, RegisterNewKeyCB(Decryptor::kAudio, _))
-        .WillOnce(SaveArg<1>(&key_added_cb_));
     decoder_->Initialize(
         new_config, cdm_context_.get(),
         base::BindOnce([](Status status) { EXPECT_TRUE(status.is_ok()); }),
@@ -263,7 +263,7 @@
   int num_frames_in_decryptor_;
 
   Decryptor::DecoderInitCB pending_init_cb_;
-  Decryptor::NewKeyCB key_added_cb_;
+  CdmContext::EventCB event_cb_;
   Decryptor::AudioDecodeCB pending_audio_decode_cb_;
 
   // Constant buffer/frames, to be used/returned by |decoder_| and |decryptor_|.
@@ -291,6 +291,10 @@
 // Ensure decoder handles unsupported audio configs without crashing.
 TEST_F(DecryptingAudioDecoderTest, Initialize_UnsupportedAudioConfig) {
   SetCdmType(CDM_WITH_DECRYPTOR);
+  EXPECT_CALL(*cdm_context_, RegisterEventCB(_)).WillOnce([&](auto cb) {
+    event_cb_ = cb;
+    return std::make_unique<CallbackRegistration>();
+  });
   EXPECT_CALL(*decryptor_, InitializeAudioDecoder(_, _))
       .WillOnce(RunOnceCallback<1>(false));
 
@@ -411,7 +415,7 @@
       .WillRepeatedly(RunCallback<1>(Decryptor::kSuccess, decoded_frame_list_));
   EXPECT_CALL(*this, FrameReady(decoded_frame_));
   EXPECT_CALL(*this, DecodeDone(DecodeStatus::OK));
-  key_added_cb_.Run();
+  event_cb_.Run(CdmContext::Event::kHasAdditionalUsableKey);
   base::RunLoop().RunUntilIdle();
 }
 
@@ -427,7 +431,7 @@
   EXPECT_CALL(*this, DecodeDone(DecodeStatus::OK));
   // The audio decode callback is returned after the correct decryption key is
   // added.
-  key_added_cb_.Run();
+  event_cb_.Run(CdmContext::Event::kHasAdditionalUsableKey);
   std::move(pending_audio_decode_cb_)
       .Run(Decryptor::kNoKey, Decryptor::AudioFrames());
   base::RunLoop().RunUntilIdle();
diff --git a/media/filters/decrypting_demuxer_stream.cc b/media/filters/decrypting_demuxer_stream.cc
index 5a5282b..553f9cc3 100644
--- a/media/filters/decrypting_demuxer_stream.cc
+++ b/media/filters/decrypting_demuxer_stream.cc
@@ -48,7 +48,6 @@
   DCHECK(cdm_context);
   DCHECK(!demuxer_stream_);
 
-  weak_this_ = weak_factory_.GetWeakPtr();
   demuxer_stream_ = stream;
   init_cb_ = BindToCurrentLoop(std::move(status_cb));
 
@@ -63,10 +62,8 @@
 
   decryptor_ = cdm_context->GetDecryptor();
 
-  decryptor_->RegisterNewKeyCB(
-      GetDecryptorStreamType(),
-      BindToCurrentLoop(base::BindRepeating(
-          &DecryptingDemuxerStream::OnKeyAdded, weak_this_)));
+  event_cb_registration_ = cdm_context->RegisterEventCB(base::BindRepeating(
+      &DecryptingDemuxerStream::OnCdmContextEvent, weak_factory_.GetWeakPtr()));
 
   state_ = kIdle;
   std::move(init_cb_).Run(PIPELINE_OK);
@@ -81,12 +78,9 @@
 
   read_cb_ = BindToCurrentLoop(std::move(read_cb));
   state_ = kPendingDemuxerRead;
-  demuxer_stream_->Read(base::BindOnce(
-      &DecryptingDemuxerStream::OnBufferReadFromDemuxerStream, weak_this_));
-}
-
-bool DecryptingDemuxerStream::IsReadPending() const {
-  return !read_cb_.is_null();
+  demuxer_stream_->Read(
+      base::BindOnce(&DecryptingDemuxerStream::OnBufferReadFromDemuxerStream,
+                     weak_factory_.GetWeakPtr()));
 }
 
 void DecryptingDemuxerStream::Reset(base::OnceClosure closure) {
@@ -246,10 +240,10 @@
       "media", "DecryptingDemuxerStream::DecryptPendingBuffer", this, "type",
       DemuxerStream::GetTypeName(demuxer_stream_->type()), "timestamp_us",
       pending_buffer_to_decrypt_->timestamp().InMicroseconds());
-  decryptor_->Decrypt(
-      GetDecryptorStreamType(), pending_buffer_to_decrypt_,
-      BindToCurrentLoop(base::BindOnce(
-          &DecryptingDemuxerStream::OnBufferDecrypted, weak_this_)));
+  decryptor_->Decrypt(GetDecryptorStreamType(), pending_buffer_to_decrypt_,
+                      BindToCurrentLoop(base::BindOnce(
+                          &DecryptingDemuxerStream::OnBufferDecrypted,
+                          weak_factory_.GetWeakPtr())));
 }
 
 void DecryptingDemuxerStream::OnBufferDecrypted(
@@ -321,9 +315,12 @@
   std::move(read_cb_).Run(kOk, std::move(decrypted_buffer));
 }
 
-void DecryptingDemuxerStream::OnKeyAdded() {
+void DecryptingDemuxerStream::OnCdmContextEvent(CdmContext::Event event) {
   DCHECK(task_runner_->BelongsToCurrentThread());
 
+  if (event != CdmContext::Event::kHasAdditionalUsableKey)
+    return;
+
   if (state_ == kPendingDecrypt) {
     key_added_while_decrypt_pending_ = true;
     return;
diff --git a/media/filters/decrypting_demuxer_stream.h b/media/filters/decrypting_demuxer_stream.h
index 12acbca..f8e9a2b 100644
--- a/media/filters/decrypting_demuxer_stream.h
+++ b/media/filters/decrypting_demuxer_stream.h
@@ -10,6 +10,8 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "media/base/audio_decoder_config.h"
+#include "media/base/callback_registry.h"
+#include "media/base/cdm_context.h"
 #include "media/base/decryptor.h"
 #include "media/base/demuxer_stream.h"
 #include "media/base/pipeline_status.h"
@@ -56,7 +58,6 @@
 
   // DemuxerStream implementation.
   void Read(ReadCB read_cb) override;
-  bool IsReadPending() const override;
   AudioDecoderConfig audio_decoder_config() override;
   VideoDecoderConfig video_decoder_config() override;
   Type type() const override;
@@ -125,9 +126,8 @@
   void OnBufferDecrypted(Decryptor::Status status,
                          scoped_refptr<DecoderBuffer> decrypted_buffer);
 
-  // Callback for the |decryptor_| to notify this object that a new key has been
-  // added.
-  void OnKeyAdded();
+  // Callback for the CDM to notify |this|.
+  void OnCdmContextEvent(CdmContext::Event event);
 
   // Resets decoder and calls |reset_cb_|.
   void DoReset();
@@ -170,7 +170,9 @@
   // decrypting again in case the newly added key is the correct decryption key.
   bool key_added_while_decrypt_pending_ = false;
 
-  base::WeakPtr<DecryptingDemuxerStream> weak_this_;
+  // To keep the CdmContext event callback registered.
+  std::unique_ptr<CallbackRegistration> event_cb_registration_;
+
   base::WeakPtrFactory<DecryptingDemuxerStream> weak_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(DecryptingDemuxerStream);
diff --git a/media/filters/decrypting_demuxer_stream_unittest.cc b/media/filters/decrypting_demuxer_stream_unittest.cc
index b03f8955..8ea826b7 100644
--- a/media/filters/decrypting_demuxer_stream_unittest.cc
+++ b/media/filters/decrypting_demuxer_stream_unittest.cc
@@ -138,8 +138,10 @@
 
   void Initialize() {
     SetCdmType(CDM_WITH_DECRYPTOR);
-    EXPECT_CALL(*decryptor_, RegisterNewKeyCB(Decryptor::kAudio, _))
-        .WillOnce(SaveArg<1>(&key_added_cb_));
+    EXPECT_CALL(*cdm_context_, RegisterEventCB(_)).WillOnce([&](auto cb) {
+      event_cb_ = cb;
+      return std::make_unique<CallbackRegistration>();
+    });
 
     AudioDecoderConfig input_config(kCodecVorbis, kSampleFormatPlanarF32,
                                     CHANNEL_LAYOUT_STEREO, 44100,
@@ -284,7 +286,7 @@
   std::unique_ptr<StrictMock<MockDemuxerStream>> input_video_stream_;
 
   DemuxerStream::ReadCB pending_demuxer_read_cb_;
-  Decryptor::NewKeyCB key_added_cb_;
+  CdmContext::EventCB event_cb_;
   Decryptor::DecryptCB pending_decrypt_cb_;
 
   // Constant buffers to be returned by the input demuxer streams and the
@@ -304,8 +306,10 @@
 
 TEST_F(DecryptingDemuxerStreamTest, Initialize_NormalVideo) {
   SetCdmType(CDM_WITH_DECRYPTOR);
-  EXPECT_CALL(*decryptor_, RegisterNewKeyCB(Decryptor::kVideo, _))
-      .WillOnce(SaveArg<1>(&key_added_cb_));
+  EXPECT_CALL(*cdm_context_, RegisterEventCB(_)).WillOnce([&](auto cb) {
+    event_cb_ = cb;
+    return std::make_unique<CallbackRegistration>();
+  });
 
   VideoDecoderConfig input_config = TestVideoConfig::NormalEncrypted();
   InitializeVideoAndExpectStatus(input_config, PIPELINE_OK);
@@ -399,7 +403,7 @@
       .WillRepeatedly(
           RunOnceCallback<2>(Decryptor::kSuccess, decrypted_buffer_));
   EXPECT_CALL(*this, BufferReady(DemuxerStream::kOk, decrypted_buffer_));
-  key_added_cb_.Run();
+  event_cb_.Run(CdmContext::Event::kHasAdditionalUsableKey);
   base::RunLoop().RunUntilIdle();
 }
 
@@ -417,7 +421,7 @@
           RunOnceCallback<2>(Decryptor::kSuccess, decrypted_buffer_));
   EXPECT_CALL(*this, BufferReady(DemuxerStream::kOk, decrypted_buffer_));
   // The decrypt callback is returned after the correct decryption key is added.
-  key_added_cb_.Run();
+  event_cb_.Run(CdmContext::Event::kHasAdditionalUsableKey);
   std::move(pending_decrypt_cb_).Run(Decryptor::kNoKey, nullptr);
   base::RunLoop().RunUntilIdle();
 }
diff --git a/media/filters/decrypting_media_resource_unittest.cc b/media/filters/decrypting_media_resource_unittest.cc
index e936a0f2..1049434d 100644
--- a/media/filters/decrypting_media_resource_unittest.cc
+++ b/media/filters/decrypting_media_resource_unittest.cc
@@ -34,8 +34,8 @@
 namespace media {
 
 static constexpr int kFakeBufferSize = 16;
-static constexpr uint8_t kFakeKeyId[] = {0x4b, 0x65, 0x79, 0x20, 0x49, 0x44};
-static constexpr uint8_t kFakeIv[DecryptConfig::kDecryptionKeySize] = {0};
+static constexpr char kFakeKeyId[] = "Key ID";
+static constexpr char kFakeIv[] = "0123456789abcdef";
 
 // Use anonymous namespace here to prevent the actions to be defined multiple
 // times across multiple test files. Sadly we can't use static for them.
@@ -51,20 +51,15 @@
 class DecryptingMediaResourceTest : public testing::Test {
  public:
   DecryptingMediaResourceTest() {
-    encrypted_buffer_ =
-        scoped_refptr<DecoderBuffer>(new DecoderBuffer(kFakeBufferSize));
-    encrypted_buffer_->set_decrypt_config(DecryptConfig::CreateCencConfig(
-        std::string(reinterpret_cast<const char*>(kFakeKeyId),
-                    base::size(kFakeKeyId)),
-        std::string(reinterpret_cast<const char*>(kFakeIv),
-                    base::size(kFakeIv)),
-        {}));
+    encrypted_buffer_ = base::MakeRefCounted<DecoderBuffer>(kFakeBufferSize);
+    encrypted_buffer_->set_decrypt_config(
+        DecryptConfig::CreateCencConfig(kFakeKeyId, kFakeIv, {}));
 
+    EXPECT_CALL(cdm_context_, RegisterEventCB(_)).Times(AnyNumber());
     EXPECT_CALL(cdm_context_, GetDecryptor())
         .WillRepeatedly(Return(&decryptor_));
     EXPECT_CALL(decryptor_, CanAlwaysDecrypt()).WillRepeatedly(Return(true));
     EXPECT_CALL(decryptor_, CancelDecrypt(_)).Times(AnyNumber());
-    EXPECT_CALL(decryptor_, RegisterNewKeyCB(_, _)).Times(AnyNumber());
     EXPECT_CALL(demuxer_, GetAllStreams())
         .WillRepeatedly(
             Invoke(this, &DecryptingMediaResourceTest::GetAllStreams));
diff --git a/media/filters/decrypting_video_decoder.cc b/media/filters/decrypting_video_decoder.cc
index 929a567..b78b7a3 100644
--- a/media/filters/decrypting_video_decoder.cc
+++ b/media/filters/decrypting_video_decoder.cc
@@ -47,6 +47,7 @@
   DCHECK(config.IsValidConfig());
 
   init_cb_ = BindToCurrentLoop(std::move(init_cb));
+
   if (!cdm_context) {
     // Once we have a CDM context, one should always be present.
     DCHECK(!support_clear_content_);
@@ -64,7 +65,6 @@
   support_clear_content_ = true;
 
   output_cb_ = BindToCurrentLoop(output_cb);
-  weak_this_ = weak_factory_.GetWeakPtr();
   config_ = config;
 
   DCHECK(waiting_cb);
@@ -78,6 +78,9 @@
     }
 
     decryptor_ = cdm_context->GetDecryptor();
+    event_cb_registration_ = cdm_context->RegisterEventCB(
+        base::BindRepeating(&DecryptingVideoDecoder::OnCdmContextEvent,
+                            weak_factory_.GetWeakPtr()));
   } else {
     // Reinitialization (i.e. upon a config change). The new config can be
     // encrypted or clear.
@@ -86,8 +89,9 @@
 
   state_ = kPendingDecoderInit;
   decryptor_->InitializeVideoDecoder(
-      config_, BindToCurrentLoop(base::BindOnce(
-                   &DecryptingVideoDecoder::FinishInitialization, weak_this_)));
+      config_, BindToCurrentLoop(
+                   base::BindOnce(&DecryptingVideoDecoder::FinishInitialization,
+                                  weak_factory_.GetWeakPtr())));
 }
 
 bool DecryptingVideoDecoder::SupportsDecryption() const {
@@ -191,14 +195,11 @@
     DVLOG(1) << __func__ << ": failed to init video decoder on decryptor";
     std::move(init_cb_).Run(StatusCode::kDecoderInitializeNeverCompleted);
     decryptor_ = nullptr;
+    event_cb_registration_.reset();
     state_ = kError;
     return;
   }
 
-  decryptor_->RegisterNewKeyCB(
-      Decryptor::kVideo, BindToCurrentLoop(base::BindRepeating(
-                             &DecryptingVideoDecoder::OnKeyAdded, weak_this_)));
-
   // Success!
   state_ = kIdle;
   std::move(init_cb_).Run(OkStatus());
@@ -221,7 +222,7 @@
   decryptor_->DecryptAndDecodeVideo(
       pending_buffer_to_decode_,
       BindToCurrentLoop(base::BindRepeating(
-          &DecryptingVideoDecoder::DeliverFrame, weak_this_)));
+          &DecryptingVideoDecoder::DeliverFrame, weak_factory_.GetWeakPtr())));
 }
 
 void DecryptingVideoDecoder::DeliverFrame(Decryptor::Status status,
@@ -318,10 +319,13 @@
   std::move(decode_cb_).Run(DecodeStatus::OK);
 }
 
-void DecryptingVideoDecoder::OnKeyAdded() {
-  DVLOG(2) << "OnKeyAdded()";
+void DecryptingVideoDecoder::OnCdmContextEvent(CdmContext::Event event) {
+  DVLOG(2) << __func__;
   DCHECK(task_runner_->BelongsToCurrentThread());
 
+  if (event != CdmContext::Event::kHasAdditionalUsableKey)
+    return;
+
   if (state_ == kPendingDecode) {
     key_added_while_decode_pending_ = true;
     return;
diff --git a/media/filters/decrypting_video_decoder.h b/media/filters/decrypting_video_decoder.h
index f5e4eeb..bdc40e5 100644
--- a/media/filters/decrypting_video_decoder.h
+++ b/media/filters/decrypting_video_decoder.h
@@ -10,6 +10,8 @@
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
+#include "media/base/callback_registry.h"
+#include "media/base/cdm_context.h"
 #include "media/base/decryptor.h"
 #include "media/base/video_decoder.h"
 #include "media/base/video_decoder_config.h"
@@ -72,9 +74,8 @@
   // Callback for Decryptor::DecryptAndDecodeVideo().
   void DeliverFrame(Decryptor::Status status, scoped_refptr<VideoFrame> frame);
 
-  // Callback for the |decryptor_| to notify this object that a new key has been
-  // added.
-  void OnKeyAdded();
+  // Callback for the CDM to notify |this|.
+  void OnCdmContextEvent(CdmContext::Event event);
 
   // Reset decoder and call |reset_cb_|.
   void DoReset();
@@ -113,7 +114,9 @@
   // clear content, we want to ensure this decoder remains used.
   bool support_clear_content_ = false;
 
-  base::WeakPtr<DecryptingVideoDecoder> weak_this_;
+  // To keep the CdmContext event callback registered.
+  std::unique_ptr<CallbackRegistration> event_cb_registration_;
+
   base::WeakPtrFactory<DecryptingVideoDecoder> weak_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(DecryptingVideoDecoder);
diff --git a/media/filters/decrypting_video_decoder_unittest.cc b/media/filters/decrypting_video_decoder_unittest.cc
index df6cabff..47c9f3d 100644
--- a/media/filters/decrypting_video_decoder_unittest.cc
+++ b/media/filters/decrypting_video_decoder_unittest.cc
@@ -96,8 +96,10 @@
     SetCdmType(CDM_WITH_DECRYPTOR);
     EXPECT_CALL(*decryptor_, InitializeVideoDecoder(_, _))
         .WillOnce(RunOnceCallback<1>(true));
-    EXPECT_CALL(*decryptor_, RegisterNewKeyCB(Decryptor::kVideo, _))
-        .WillOnce(SaveArg<1>(&key_added_cb_));
+    EXPECT_CALL(*cdm_context_, RegisterEventCB(_)).WillOnce([&](auto cb) {
+      event_cb_ = cb;
+      return std::make_unique<CallbackRegistration>();
+    });
 
     InitializeAndExpectResult(TestVideoConfig::NormalEncrypted(), true);
   }
@@ -107,8 +109,6 @@
     EXPECT_CALL(*decryptor_, DeinitializeDecoder(Decryptor::kVideo));
     EXPECT_CALL(*decryptor_, InitializeVideoDecoder(_, _))
         .WillOnce(RunOnceCallback<1>(true));
-    EXPECT_CALL(*decryptor_, RegisterNewKeyCB(Decryptor::kVideo, _))
-        .WillOnce(SaveArg<1>(&key_added_cb_));
 
     InitializeAndExpectResult(new_config, true);
   }
@@ -239,7 +239,7 @@
   int num_frames_in_decryptor_;
 
   Decryptor::DecoderInitCB pending_init_cb_;
-  Decryptor::NewKeyCB key_added_cb_;
+  CdmContext::EventCB event_cb_;
   Decryptor::VideoDecodeCB pending_video_decode_cb_;
 
   // Constant buffer/frames.
@@ -262,10 +262,12 @@
 
 TEST_F(DecryptingVideoDecoderTest, Initialize_Failure) {
   SetCdmType(CDM_WITH_DECRYPTOR);
+  EXPECT_CALL(*cdm_context_, RegisterEventCB(_)).WillOnce([&](auto cb) {
+    event_cb_ = cb;
+    return std::make_unique<CallbackRegistration>();
+  });
   EXPECT_CALL(*decryptor_, InitializeVideoDecoder(_, _))
       .WillRepeatedly(RunOnceCallback<1>(false));
-  EXPECT_CALL(*decryptor_, RegisterNewKeyCB(Decryptor::kVideo, _))
-      .WillRepeatedly(SaveArg<1>(&key_added_cb_));
 
   InitializeAndExpectResult(TestVideoConfig::NormalEncrypted(), false);
 }
@@ -335,7 +337,7 @@
           RunCallback<1>(Decryptor::kSuccess, decoded_video_frame_));
   EXPECT_CALL(*this, FrameReady(decoded_video_frame_));
   EXPECT_CALL(*this, DecodeDone(DecodeStatus::OK));
-  key_added_cb_.Run();
+  event_cb_.Run(CdmContext::Event::kHasAdditionalUsableKey);
   base::RunLoop().RunUntilIdle();
 }
 
@@ -352,7 +354,7 @@
   EXPECT_CALL(*this, DecodeDone(DecodeStatus::OK));
   // The video decode callback is returned after the correct decryption key is
   // added.
-  key_added_cb_.Run();
+  event_cb_.Run(CdmContext::Event::kHasAdditionalUsableKey);
   std::move(pending_video_decode_cb_).Run(Decryptor::kNoKey, null_video_frame_);
   base::RunLoop().RunUntilIdle();
 }
@@ -412,6 +414,10 @@
 // Test destruction when the decoder is in kPendingDecoderInit state.
 TEST_F(DecryptingVideoDecoderTest, Destroy_DuringPendingDecoderInit) {
   SetCdmType(CDM_WITH_DECRYPTOR);
+  EXPECT_CALL(*cdm_context_, RegisterEventCB(_)).WillOnce([&](auto cb) {
+    event_cb_ = cb;
+    return std::make_unique<CallbackRegistration>();
+  });
   EXPECT_CALL(*decryptor_, InitializeVideoDecoder(_, _))
       .WillOnce(WithArg<1>(Invoke([&](Decryptor::DecoderInitCB init_cb) {
         pending_init_cb_ = std::move(init_cb);
diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc
index a7ed542..6987a2e 100644
--- a/media/filters/ffmpeg_demuxer.cc
+++ b/media/filters/ffmpeg_demuxer.cc
@@ -746,10 +746,6 @@
   SatisfyPendingRead();
 }
 
-bool FFmpegDemuxerStream::IsReadPending() const {
-  return !read_cb_.is_null();
-}
-
 void FFmpegDemuxerStream::EnableBitstreamConverter() {
   DCHECK(task_runner_->BelongsToCurrentThread());
 
diff --git a/media/filters/ffmpeg_demuxer.h b/media/filters/ffmpeg_demuxer.h
index ab12030c..0c714728 100644
--- a/media/filters/ffmpeg_demuxer.h
+++ b/media/filters/ffmpeg_demuxer.h
@@ -115,7 +115,6 @@
   Type type() const override;
   Liveness liveness() const override;
   void Read(ReadCB read_cb) override;
-  bool IsReadPending() const override;
   void EnableBitstreamConverter() override;
   bool SupportsConfigChanges() override;
   AudioDecoderConfig audio_decoder_config() override;
diff --git a/media/filters/video_decoder_stream_unittest.cc b/media/filters/video_decoder_stream_unittest.cc
index 01e5be7..b42c2c5d 100644
--- a/media/filters/video_decoder_stream_unittest.cc
+++ b/media/filters/video_decoder_stream_unittest.cc
@@ -124,6 +124,7 @@
     if (GetParam().is_encrypted) {
       cdm_context_.reset(new StrictMock<MockCdmContext>());
 
+      EXPECT_CALL(*cdm_context_, RegisterEventCB(_)).Times(AnyNumber());
       EXPECT_CALL(*cdm_context_, GetDecryptor())
           .WillRepeatedly(Return(decryptor_.get()));
     }
@@ -514,8 +515,7 @@
   std::unique_ptr<FakeDemuxerStream> demuxer_stream_;
   std::unique_ptr<StrictMock<MockCdmContext>> cdm_context_;
 
-  // Use NiceMock since we don't care about most of calls on the decryptor,
-  // e.g. RegisterNewKeyCB().
+  // Use NiceMock since we don't care about most of calls on the decryptor.
   std::unique_ptr<NiceMock<MockDecryptor>> decryptor_;
 
   // References to the list of decoders to be select from by DecoderSelector.
diff --git a/media/fuchsia/audio/fuchsia_audio_renderer.cc b/media/fuchsia/audio/fuchsia_audio_renderer.cc
index 0b1c04b..84da634 100644
--- a/media/fuchsia/audio/fuchsia_audio_renderer.cc
+++ b/media/fuchsia/audio/fuchsia_audio_renderer.cc
@@ -307,8 +307,9 @@
 
   base::AutoLock lock(timeline_lock_);
 
-  const bool is_time_moving = state_ == PlaybackState::kPlaying ||
-                              state_ == PlaybackState::kEndOfStream;
+  const bool is_time_moving = (state_ == PlaybackState::kPlaying ||
+                               state_ == PlaybackState::kEndOfStream) &&
+                              (media_delta_ > 0);
 
   if (media_timestamps.empty()) {
     wall_clock_times->push_back(is_time_moving ? now : base::TimeTicks());
diff --git a/media/fuchsia/cdm/fuchsia_cdm.cc b/media/fuchsia/cdm/fuchsia_cdm.cc
index fbd8416..d7f633e6 100644
--- a/media/fuchsia/cdm/fuchsia_cdm.cc
+++ b/media/fuchsia/cdm/fuchsia_cdm.cc
@@ -448,8 +448,7 @@
 
 std::unique_ptr<CallbackRegistration> FuchsiaCdm::RegisterEventCB(
     EventCB event_cb) {
-  NOTIMPLEMENTED();
-  return nullptr;
+  return event_callbacks_.Register(std::move(event_cb));
 }
 
 Decryptor* FuchsiaCdm::GetDecryptor() {
@@ -465,7 +464,7 @@
 }
 
 void FuchsiaCdm::OnNewKey() {
-  decryptor_.OnNewKey();
+  event_callbacks_.Notify(Event::kHasAdditionalUsableKey);
   {
     base::AutoLock auto_lock(new_key_cb_for_video_lock_);
     if (new_key_cb_for_video_)
diff --git a/media/fuchsia/cdm/fuchsia_cdm.h b/media/fuchsia/cdm/fuchsia_cdm.h
index 574a398..b39422bc 100644
--- a/media/fuchsia/cdm/fuchsia_cdm.h
+++ b/media/fuchsia/cdm/fuchsia_cdm.h
@@ -10,6 +10,7 @@
 #include "base/containers/flat_map.h"
 #include "base/macros.h"
 #include "base/optional.h"
+#include "media/base/callback_registry.h"
 #include "media/base/cdm_context.h"
 #include "media/base/cdm_promise_adapter.h"
 #include "media/base/content_decryption_module.h"
@@ -101,6 +102,8 @@
   base::RepeatingClosure new_key_cb_for_video_
       GUARDED_BY(new_key_cb_for_video_lock_);
 
+  CallbackRegistry<EventCB::RunType> event_callbacks_;
+
   DISALLOW_COPY_AND_ASSIGN(FuchsiaCdm);
 };
 
diff --git a/media/fuchsia/cdm/fuchsia_decryptor.cc b/media/fuchsia/cdm/fuchsia_decryptor.cc
index e10b2a1..09c44e6 100644
--- a/media/fuchsia/cdm/fuchsia_decryptor.cc
+++ b/media/fuchsia/cdm/fuchsia_decryptor.cc
@@ -28,15 +28,6 @@
   }
 }
 
-void FuchsiaDecryptor::RegisterNewKeyCB(StreamType stream_type,
-                                        NewKeyCB new_key_cb) {
-  if (stream_type != kAudio)
-    return;
-
-  base::AutoLock auto_lock(new_key_cb_lock_);
-  new_key_cb_ = std::move(new_key_cb);
-}
-
 void FuchsiaDecryptor::Decrypt(StreamType stream_type,
                                scoped_refptr<DecoderBuffer> encrypted,
                                DecryptCB decrypt_cb) {
@@ -97,10 +88,4 @@
   return false;
 }
 
-void FuchsiaDecryptor::OnNewKey() {
-  base::AutoLock auto_lock(new_key_cb_lock_);
-  if (new_key_cb_)
-    std::move(new_key_cb_).Run();
-}
-
 }  // namespace media
diff --git a/media/fuchsia/cdm/fuchsia_decryptor.h b/media/fuchsia/cdm/fuchsia_decryptor.h
index 9b8b71a..fd0b17e 100644
--- a/media/fuchsia/cdm/fuchsia_decryptor.h
+++ b/media/fuchsia/cdm/fuchsia_decryptor.h
@@ -33,7 +33,6 @@
   ~FuchsiaDecryptor() override;
 
   // media::Decryptor implementation:
-  void RegisterNewKeyCB(StreamType stream_type, NewKeyCB key_added_cb) override;
   void Decrypt(StreamType stream_type,
                scoped_refptr<DecoderBuffer> encrypted,
                DecryptCB decrypt_cb) override;
@@ -50,15 +49,9 @@
   void DeinitializeDecoder(StreamType stream_type) override;
   bool CanAlwaysDecrypt() override;
 
-  // Called by FuchsiaCdm to notify about the new key.
-  void OnNewKey();
-
  private:
   fuchsia::media::drm::ContentDecryptionModule* const cdm_;
 
-  base::Lock new_key_cb_lock_;
-  NewKeyCB new_key_cb_ GUARDED_BY(new_key_cb_lock_);
-
   std::unique_ptr<FuchsiaClearStreamDecryptor> audio_decryptor_;
 
   // TaskRunner for the thread on which |audio_decryptor_| was created.
diff --git a/media/gpu/vaapi/vaapi_unittest.cc b/media/gpu/vaapi/vaapi_unittest.cc
index 814d077..994bced 100644
--- a/media/gpu/vaapi/vaapi_unittest.cc
+++ b/media/gpu/vaapi/vaapi_unittest.cc
@@ -30,9 +30,8 @@
 base::Optional<VAProfile> ConvertToVAProfile(VideoCodecProfile profile) {
   // A map between VideoCodecProfile and VAProfile.
   const std::map<VideoCodecProfile, VAProfile> kProfileMap = {
-      // VAProfileH264Baseline is marked deprecated in <va/va.h> from libva 2.0.
-      // consider making VAProfileH264ConstrainedBaseline the default one.
-      {H264PROFILE_BASELINE, VAProfileH264Baseline},
+      // VAProfileH264Baseline is deprecated in <va/va.h> from libva 2.0.0.
+      {H264PROFILE_BASELINE, VAProfileH264ConstrainedBaseline},
       {H264PROFILE_MAIN, VAProfileH264Main},
       {H264PROFILE_HIGH, VAProfileH264High},
       {VP8PROFILE_ANY, VAProfileVP8Version0_3},
@@ -49,6 +48,8 @@
   const std::map<std::string, VAProfile> kStringToVAProfile = {
       {"VAProfileNone", VAProfileNone},
       {"VAProfileH264ConstrainedBaseline", VAProfileH264ConstrainedBaseline},
+      // Even though it's deprecated, we leave VAProfileH264Baseline's
+      // translation here to assert we never encounter it.
       {"VAProfileH264Baseline", VAProfileH264Baseline},
       {"VAProfileH264Main", VAProfileH264Main},
       {"VAProfileH264High", VAProfileH264High},
@@ -152,6 +153,14 @@
   EXPECT_NE(VaapiWrapper::GetImplementationType(), VAImplementation::kInvalid);
 }
 
+// Commit [1] deprecated VAProfileH264Baseline from libva in 2017 (release
+// 2.0.0). This test verifies that such profile is never seen in the lab.
+// [1] https://github.com/intel/libva/commit/6f69256f8ccc9a73c0b196ab77ac69ab1f4f33c2
+TEST_F(VaapiTest, VerifyNoVAProfileH264Baseline) {
+  const auto va_info = RetrieveVAInfoOutput();
+  EXPECT_FALSE(base::Contains(va_info, VAProfileH264Baseline));
+}
+
 // Verifies that every VAProfile from VaapiWrapper::GetSupportedDecodeProfiles()
 // is indeed supported by the command line vainfo utility and by
 // VaapiWrapper::IsDecodeSupported().
@@ -162,24 +171,12 @@
     const auto va_profile = ConvertToVAProfile(profile.profile);
     ASSERT_TRUE(va_profile.has_value());
 
-    bool is_profile_supported =
-        base::Contains(va_info, *va_profile) &&
-        base::Contains(va_info.at(*va_profile), VAEntrypointVLD);
-    bool is_decode_supported = VaapiWrapper::IsDecodeSupported(*va_profile);
-    // H264PROFILE_BASELINE may be supported by VAProfileH264Baseline
-    // (deprecated) or by VAProfileH264ConstrainedBaseline. This is the same
-    // logic as in vaapi_wrapper.cc.
-    if (profile.profile == H264PROFILE_BASELINE) {
-      is_profile_supported |= base::Contains(
-          va_info.at(VAProfileH264ConstrainedBaseline), VAEntrypointVLD);
-      is_decode_supported |=
-          VaapiWrapper::IsDecodeSupported(VAProfileH264ConstrainedBaseline);
-    }
-
-    EXPECT_TRUE(is_profile_supported)
-        << " profile: " << GetProfileName(profile.profile);
-    EXPECT_TRUE(is_decode_supported)
-        << " profile: " << GetProfileName(profile.profile);
+    EXPECT_TRUE(base::Contains(va_info.at(*va_profile), VAEntrypointVLD))
+        << " profile: " << GetProfileName(profile.profile)
+        << ", va profile: " << vaProfileStr(*va_profile);
+    EXPECT_TRUE(VaapiWrapper::IsDecodeSupported(*va_profile))
+        << " profile: " << GetProfileName(profile.profile)
+        << ", va profile: " << vaProfileStr(*va_profile);
   }
 }
 
@@ -192,23 +189,10 @@
     const auto va_profile = ConvertToVAProfile(profile.profile);
     ASSERT_TRUE(va_profile.has_value());
 
-    bool is_profile_supported =
-        base::Contains(va_info, *va_profile) &&
-        (base::Contains(va_info.at(*va_profile), VAEntrypointEncSlice) ||
-         base::Contains(va_info.at(*va_profile), VAEntrypointEncSliceLP));
-    // H264PROFILE_BASELINE may be supported by VAProfileH264Baseline
-    // (deprecated) or by VAProfileH264ConstrainedBaseline. This is the same
-    // logic as in vaapi_wrapper.cc.
-    if (profile.profile == H264PROFILE_BASELINE) {
-      is_profile_supported |=
-          base::Contains(va_info.at(VAProfileH264ConstrainedBaseline),
-                         VAEntrypointEncSlice) ||
-          base::Contains(va_info.at(VAProfileH264ConstrainedBaseline),
-                         VAEntrypointEncSliceLP);
-    }
-
-    EXPECT_TRUE(is_profile_supported)
-        << " profile: " << GetProfileName(profile.profile);
+    EXPECT_TRUE(base::Contains(va_info.at(*va_profile), VAEntrypointEncSlice) ||
+                base::Contains(va_info.at(*va_profile), VAEntrypointEncSliceLP))
+        << " profile: " << GetProfileName(profile.profile)
+        << ", va profile: " << vaProfileStr(*va_profile);
   }
 }
 
diff --git a/media/gpu/vaapi/vaapi_wrapper.cc b/media/gpu/vaapi/vaapi_wrapper.cc
index 113c6cc9..f1d9e4f 100644
--- a/media/gpu/vaapi/vaapi_wrapper.cc
+++ b/media/gpu/vaapi/vaapi_wrapper.cc
@@ -249,10 +249,9 @@
 }
 
 // Map of the supported VaProfiles indexed by media's VideoCodecProfile.
-std::map<VideoCodecProfile, VAProfile> kMediaToVAProfileMap = {
-    // VAProfileH264Baseline is marked deprecated in <va/va.h> from libva 2.0.
-    // consider making VAProfileH264ConstrainedBaseline the default one.
-    {H264PROFILE_BASELINE, VAProfileH264Baseline},
+const std::map<VideoCodecProfile, VAProfile> kMediaToVAProfileMap = {
+    // VAProfileH264Baseline is deprecated in <va/va.h> since libva 2.0.0.
+    {H264PROFILE_BASELINE, VAProfileH264ConstrainedBaseline},
     {H264PROFILE_MAIN, VAProfileH264Main},
     // TODO(posciak): See if we can/want to support other variants of
     // H264PROFILE_HIGH*.
@@ -265,6 +264,14 @@
     {VP9PROFILE_PROFILE3, VAProfileVP9Profile3},
 };
 
+// Maps a VideoCodecProfile |profile| to a VAProfile, or VAProfileNone.
+VAProfile ProfileToVAProfile(VideoCodecProfile profile,
+                             VaapiWrapper::CodecMode mode) {
+  if (!base::Contains(kMediaToVAProfileMap, profile))
+    return VAProfileNone;
+  return kMediaToVAProfileMap.at(profile);
+}
+
 bool IsBlackListedDriver(const std::string& va_vendor_string,
                          VaapiWrapper::CodecMode mode,
                          VAProfile va_profile) {
@@ -550,9 +557,10 @@
       required_attribs->push_back({VAConfigAttribRateControl, VA_RC_CQP});
   }
 
+  constexpr VAProfile kSupportedH264VaProfilesForEncoding[] = {
+      VAProfileH264ConstrainedBaseline, VAProfileH264Main, VAProfileH264High};
   // VAConfigAttribEncPackedHeaders is H.264 specific.
-  if ((profile >= VAProfileH264Baseline && profile <= VAProfileH264High) ||
-      (profile == VAProfileH264ConstrainedBaseline)) {
+  if (base::Contains(kSupportedH264VaProfilesForEncoding, profile)) {
     // Encode with Packed header if a driver supports.
     VAConfigAttrib attrib;
     attrib.type = VAConfigAttribEncPackedHeaders;
@@ -973,33 +981,6 @@
   return is_any_profile_supported;
 }
 
-// Maps a VideoCodecProfile |profile| to a VAProfile. This function includes a
-// workaround for https://crbug.com/345569: if |va_profile| is h264 baseline and
-// this is not supported, we try constrained baseline.
-VAProfile ProfileToVAProfile(VideoCodecProfile profile,
-                             VaapiWrapper::CodecMode mode) {
-  if (!base::Contains(kMediaToVAProfileMap, profile))
-    return VAProfileNone;
-
-  VAProfile va_profile = kMediaToVAProfileMap[profile];
-
-  // https://crbug.com/345569: VideoCodecProfile has no information whether the
-  // profile is constrained or not, so we have no way to know here. Try for
-  // baseline first, but if it is not supported, try constrained baseline and
-  // hope this is what it actually is (which in practice is true for a great
-  // majority of cases).
-  if (va_profile == VAProfileH264Baseline) {
-    const VASupportedProfiles& supported_profiles = VASupportedProfiles::Get();
-    if (!supported_profiles.IsProfileSupported(mode, VAProfileH264Baseline) &&
-        supported_profiles.IsProfileSupported(
-            mode, VAProfileH264ConstrainedBaseline)) {
-      va_profile = VAProfileH264ConstrainedBaseline;
-    }
-  }
-
-  return va_profile;
-}
-
 void DestroyVAImage(VADisplay va_display, VAImage image) {
   if (image.image_id != VA_INVALID_ID)
     vaDestroyImage(va_display, image.image_id);
diff --git a/media/gpu/windows/d3d11_texture_selector.cc b/media/gpu/windows/d3d11_texture_selector.cc
index b3f0c78..7c3e854 100644
--- a/media/gpu/windows/d3d11_texture_selector.cc
+++ b/media/gpu/windows/d3d11_texture_selector.cc
@@ -158,6 +158,11 @@
   if (base::FeatureList::IsEnabled(kD3D11VideoDecoderAlwaysCopy))
     needs_texture_copy = true;
 
+  // TODO(crbug.com/971952): |use_single_video_decoder_texture| is used to force
+  // a texture copy, to see if it fixes the issue.  If so, it'll be renamed.
+  if (workarounds.use_single_video_decoder_texture)
+    needs_texture_copy = true;
+
   MEDIA_LOG(INFO, media_log)
       << "D3D11VideoDecoder output color space: "
       << (output_color_space ? output_color_space->ToString()
diff --git a/media/gpu/windows/d3d11_video_decoder.cc b/media/gpu/windows/d3d11_video_decoder.cc
index cc2661d..087b5fab 100644
--- a/media/gpu/windows/d3d11_video_decoder.cc
+++ b/media/gpu/windows/d3d11_video_decoder.cc
@@ -258,13 +258,17 @@
   // 14 is clear, then it's the former, else it's the latter.
   //
   // Let the workaround override array texture mode, if enabled.
+  // TODO(crbug.com/971952): Ignore |use_single_video_decoder_texture_| here,
+  // since it might be the case that it's not actually the right fix.  Instead,
+  // We use this workaround to force a copy later.  The workaround will be
+  // renamed if this turns out to fix the issue, but we might need to merge back
+  // and smaller changes are better.
   //
   // For more information, please see:
   // https://download.microsoft.com/download/9/2/A/92A4E198-67E0-4ABD-9DB7-635D711C2752/DXVA_VPx.pdf
   // https://download.microsoft.com/download/5/f/c/5fc4ec5c-bd8c-4624-8034-319c1bab7671/DXVA_H264.pdf
   use_single_video_decoder_texture_ =
-      !!(dec_config.ConfigDecoderSpecific & (1 << 14)) ||
-      gpu_workarounds_.use_single_video_decoder_texture;
+      !!(dec_config.ConfigDecoderSpecific & (1 << 14));
   if (use_single_video_decoder_texture_)
     MEDIA_LOG(INFO, media_log_) << "D3D11VideoDecoder is using single textures";
   else
diff --git a/media/mojo/clients/mojo_cdm.cc b/media/mojo/clients/mojo_cdm.cc
index b421dc8..b7baf3a 100644
--- a/media/mojo/clients/mojo_cdm.cc
+++ b/media/mojo/clients/mojo_cdm.cc
@@ -239,6 +239,11 @@
   return this;
 }
 
+std::unique_ptr<CallbackRegistration> MojoCdm::RegisterEventCB(
+    EventCB event_cb) {
+  return event_callbacks_.Register(std::move(event_cb));
+}
+
 Decryptor* MojoCdm::GetDecryptor() {
   base::AutoLock auto_lock(lock_);
 
@@ -289,17 +294,8 @@
   DVLOG(2) << __func__;
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
-  // TODO(jrummell): Handling resume playback should be done in the media
-  // player, not in the Decryptors. http://crbug.com/413413.
-  if (has_additional_usable_key) {
-    base::AutoLock auto_lock(lock_);
-    if (decryptor_) {
-      DCHECK(decryptor_task_runner_);
-      decryptor_task_runner_->PostTask(
-          FROM_HERE,
-          base::BindOnce(&MojoCdm::OnKeyAdded, weak_factory_.GetWeakPtr()));
-    }
-  }
+  if (has_additional_usable_key)
+    event_callbacks_.Notify(Event::kHasAdditionalUsableKey);
 
   session_keys_change_cb_.Run(session_id, has_additional_usable_key,
                               std::move(keys_info));
@@ -314,16 +310,6 @@
       session_id, base::Time::FromDoubleT(new_expiry_time_sec));
 }
 
-void MojoCdm::OnKeyAdded() {
-  base::AutoLock auto_lock(lock_);
-
-  DCHECK(decryptor_task_runner_);
-  DCHECK(decryptor_task_runner_->BelongsToCurrentThread());
-  DCHECK(decryptor_);
-
-  decryptor_->OnKeyAdded();
-}
-
 void MojoCdm::OnSimpleCdmPromiseResult(uint32_t promise_id,
                                        mojom::CdmPromiseResultPtr result) {
   if (result->success)
diff --git a/media/mojo/clients/mojo_cdm.h b/media/mojo/clients/mojo_cdm.h
index bce6833..50242d0 100644
--- a/media/mojo/clients/mojo_cdm.h
+++ b/media/mojo/clients/mojo_cdm.h
@@ -15,6 +15,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/synchronization/lock.h"
 #include "base/threading/thread_checker.h"
+#include "media/base/callback_registry.h"
 #include "media/base/cdm_context.h"
 #include "media/base/cdm_initialized_promise.h"
 #include "media/base/cdm_promise_adapter.h"
@@ -74,6 +75,7 @@
 
   // CdmContext implementation. Can be called on a different thread.
   // All GetDecryptor() calls must be made on the same thread.
+  std::unique_ptr<CallbackRegistration> RegisterEventCB(EventCB event_cb) final;
   Decryptor* GetDecryptor() final;
   int GetCdmId() const final;
 
@@ -95,9 +97,6 @@
   void OnSessionExpirationUpdate(const std::string& session_id,
                                  double new_expiry_time_sec) final;
 
-  // Callback when new decryption key is available.
-  void OnKeyAdded();
-
   // Callbacks to handle CDM promises.
   void OnSimpleCdmPromiseResult(uint32_t promise_id,
                                 mojom::CdmPromiseResultPtr result);
@@ -147,6 +146,8 @@
   // Keep track of outstanding promises.
   CdmPromiseAdapter cdm_promise_adapter_;
 
+  CallbackRegistry<EventCB::RunType> event_callbacks_;
+
   // This must be the last member.
   base::WeakPtrFactory<MojoCdm> weak_factory_{this};
 
diff --git a/media/mojo/clients/mojo_decryptor.cc b/media/mojo/clients/mojo_decryptor.cc
index 8ee88d5..1188892 100644
--- a/media/mojo/clients/mojo_decryptor.cc
+++ b/media/mojo/clients/mojo_decryptor.cc
@@ -90,21 +90,6 @@
   DCHECK(thread_checker_.CalledOnValidThread());
 }
 
-void MojoDecryptor::RegisterNewKeyCB(StreamType stream_type,
-                                     NewKeyCB key_added_cb) {
-  DCHECK(thread_checker_.CalledOnValidThread());
-  switch (stream_type) {
-    case kAudio:
-      new_audio_key_cb_ = std::move(key_added_cb);
-      break;
-    case kVideo:
-      new_video_key_cb_ = std::move(key_added_cb);
-      break;
-    default:
-      NOTREACHED();
-  }
-}
-
 void MojoDecryptor::Decrypt(StreamType stream_type,
                             scoped_refptr<DecoderBuffer> encrypted,
                             DecryptCB decrypt_cb) {
@@ -208,17 +193,6 @@
   remote_decryptor_->DeinitializeDecoder(stream_type);
 }
 
-void MojoDecryptor::OnKeyAdded() {
-  DVLOG(1) << __func__;
-  DCHECK(thread_checker_.CalledOnValidThread());
-
-  if (new_audio_key_cb_)
-    new_audio_key_cb_.Run();
-
-  if (new_video_key_cb_)
-    new_video_key_cb_.Run();
-}
-
 void MojoDecryptor::OnBufferDecrypted(DecryptCB decrypt_cb,
                                       Status status,
                                       mojom::DecoderBufferPtr buffer) {
diff --git a/media/mojo/clients/mojo_decryptor.h b/media/mojo/clients/mojo_decryptor.h
index d5d57882..e5cc543d 100644
--- a/media/mojo/clients/mojo_decryptor.h
+++ b/media/mojo/clients/mojo_decryptor.h
@@ -34,7 +34,6 @@
   ~MojoDecryptor() final;
 
   // Decryptor implementation.
-  void RegisterNewKeyCB(StreamType stream_type, NewKeyCB key_added_cb) final;
   void Decrypt(StreamType stream_type,
                scoped_refptr<DecoderBuffer> encrypted,
                DecryptCB decrypt_cb) final;
@@ -50,9 +49,6 @@
   void ResetDecoder(StreamType stream_type) final;
   void DeinitializeDecoder(StreamType stream_type) final;
 
-  // Called when keys have changed and an additional key is available.
-  void OnKeyAdded();
-
  private:
   // These are once callbacks corresponding to repeating callbacks DecryptCB,
   // DecoderInitCB, AudioDecodeCB and VideoDecodeCB. They are needed so that we
@@ -100,9 +96,6 @@
   // |remote_decryptor_|, shared by audio and video.
   std::unique_ptr<MojoDecoderBufferReader> decrypted_buffer_reader_;
 
-  NewKeyCB new_audio_key_cb_;
-  NewKeyCB new_video_key_cb_;
-
   base::WeakPtrFactory<MojoDecryptor> weak_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(MojoDecryptor);
diff --git a/media/mojo/services/mojo_demuxer_stream_adapter.cc b/media/mojo/services/mojo_demuxer_stream_adapter.cc
index e2587d4b..e958289e 100644
--- a/media/mojo/services/mojo_demuxer_stream_adapter.cc
+++ b/media/mojo/services/mojo_demuxer_stream_adapter.cc
@@ -42,10 +42,6 @@
                                        weak_factory_.GetWeakPtr()));
 }
 
-bool MojoDemuxerStreamAdapter::IsReadPending() const {
-  return !read_cb_.is_null();
-}
-
 AudioDecoderConfig MojoDemuxerStreamAdapter::audio_decoder_config() {
   DCHECK_EQ(type_, AUDIO);
   return audio_config_;
diff --git a/media/mojo/services/mojo_demuxer_stream_adapter.h b/media/mojo/services/mojo_demuxer_stream_adapter.h
index 15119d1..513a7d5 100644
--- a/media/mojo/services/mojo_demuxer_stream_adapter.h
+++ b/media/mojo/services/mojo_demuxer_stream_adapter.h
@@ -40,7 +40,6 @@
 
   // DemuxerStream implementation.
   void Read(ReadCB read_cb) override;
-  bool IsReadPending() const override;
   AudioDecoderConfig audio_decoder_config() override;
   VideoDecoderConfig video_decoder_config() override;
   Type type() const override;
diff --git a/media/remoting/fake_media_resource.cc b/media/remoting/fake_media_resource.cc
index 710b84a8..39ff3c2 100644
--- a/media/remoting/fake_media_resource.cc
+++ b/media/remoting/fake_media_resource.cc
@@ -51,10 +51,6 @@
   std::move(read_cb).Run(kOk, buffer);
 }
 
-bool FakeDemuxerStream::IsReadPending() const {
-  return !pending_read_cb_.is_null();
-}
-
 AudioDecoderConfig FakeDemuxerStream::audio_decoder_config() {
   return audio_config_;
 }
diff --git a/media/remoting/fake_media_resource.h b/media/remoting/fake_media_resource.h
index f6391f8..ca09e3d 100644
--- a/media/remoting/fake_media_resource.h
+++ b/media/remoting/fake_media_resource.h
@@ -25,7 +25,6 @@
   // DemuxerStream implementation.
   MOCK_METHOD1(Read, void(ReadCB read_cb));
   void FakeRead(ReadCB read_cb);
-  bool IsReadPending() const override;
   AudioDecoderConfig audio_decoder_config() override;
   VideoDecoderConfig video_decoder_config() override;
   Type type() const override;
diff --git a/media/remoting/stream_provider.cc b/media/remoting/stream_provider.cc
index 198cf6c..d2df3a0 100644
--- a/media/remoting/stream_provider.cc
+++ b/media/remoting/stream_provider.cc
@@ -340,10 +340,6 @@
   CompleteRead(DemuxerStream::kOk);
 }
 
-bool StreamProvider::MediaStream::IsReadPending() const {
-  return !read_complete_callback_.is_null();
-}
-
 void StreamProvider::MediaStream::CompleteRead(DemuxerStream::Status status) {
   DCHECK(media_task_runner_->BelongsToCurrentThread());
 
diff --git a/media/remoting/stream_provider.h b/media/remoting/stream_provider.h
index 95d2c21..3b18c47 100644
--- a/media/remoting/stream_provider.h
+++ b/media/remoting/stream_provider.h
@@ -96,7 +96,6 @@
 
     // DemuxerStream implementation.
     void Read(ReadCB read_cb) override;
-    bool IsReadPending() const override;
     AudioDecoderConfig audio_decoder_config() override;
     VideoDecoderConfig video_decoder_config() override;
     DemuxerStream::Type type() const override;
diff --git a/media/renderers/audio_renderer_impl.cc b/media/renderers/audio_renderer_impl.cc
index 2277160..b1a28c1 100644
--- a/media/renderers/audio_renderer_impl.cc
+++ b/media/renderers/audio_renderer_impl.cc
@@ -707,8 +707,9 @@
   // the decoder for an "underflow" that is really just a seek.
   BufferingStateChangeReason reason = BUFFERING_CHANGE_REASON_UNKNOWN;
   if (state_ == kPlaying && buffering_state == BUFFERING_HAVE_NOTHING) {
-    reason = demuxer_stream_->IsReadPending() ? DEMUXER_UNDERFLOW
-                                              : DECODER_UNDERFLOW;
+    reason = audio_decoder_stream_->is_demuxer_read_pending()
+                 ? DEMUXER_UNDERFLOW
+                 : DECODER_UNDERFLOW;
   }
 
   media_log_->AddEvent<MediaLogEvent::kBufferingStateChanged>(
diff --git a/media/renderers/audio_renderer_impl_unittest.cc b/media/renderers/audio_renderer_impl_unittest.cc
index b9371b58..0436734 100644
--- a/media/renderers/audio_renderer_impl_unittest.cc
+++ b/media/renderers/audio_renderer_impl_unittest.cc
@@ -144,13 +144,28 @@
   // Mock out demuxer reads.
   void ConfigureDemuxerStream(bool supports_config_changes) {
     EXPECT_CALL(demuxer_stream_, OnRead(_))
-        .WillRepeatedly(RunOnceCallback<0>(
-            DemuxerStream::kOk,
-            scoped_refptr<DecoderBuffer>(new DecoderBuffer(0))));
+        .WillRepeatedly(Invoke(this, &AudioRendererImplTest::OnDemuxerRead));
     EXPECT_CALL(demuxer_stream_, SupportsConfigChanges())
         .WillRepeatedly(Return(supports_config_changes));
   }
 
+  void OnDemuxerRead(DemuxerStream::ReadCB& read_cb) {
+    if (simulate_demuxer_stall_) {
+      simulate_demuxer_stall_ = false;
+      stalled_demixer_read_cb_ = std::move(read_cb);
+      return;
+    }
+    scoped_refptr<DecoderBuffer> decoder_buffer(new DecoderBuffer(0));
+    std::move(read_cb).Run(DemuxerStream::kOk, decoder_buffer);
+  }
+
+  bool IsDemuxerStalled() { return !!stalled_demixer_read_cb_; }
+
+  void UnstallDemuxer() {
+    EXPECT_TRUE(IsDemuxerStalled());
+    OnDemuxerRead(stalled_demixer_read_cb_);
+  }
+
   // Reconfigures a renderer without config change support using given params.
   void ConfigureBasicRenderer(const AudioParameters& params) {
     hardware_params_ = params;
@@ -295,7 +310,7 @@
     SatisfyPendingRead(InputFrames(kInputFramesChunk));
     flush_event.RunAndWait();
 
-    EXPECT_FALSE(IsReadPending());
+    EXPECT_FALSE(IsDecodePending());
   }
 
   void Preroll() { Preroll(base::TimeDelta(), base::TimeDelta(), PIPELINE_OK); }
@@ -324,7 +339,7 @@
 
   void StopTicking() { renderer_->StopTicking(); }
 
-  bool IsReadPending() const { return !!decode_cb_; }
+  bool IsDecodePending() const { return !!decode_cb_; }
 
   void WaitForPendingRead() {
     SCOPED_TRACE("WaitForPendingRead()");
@@ -387,12 +402,12 @@
   // Delivers frames until |renderer_|'s internal buffer is full and no longer
   // has pending reads.
   void DeliverRemainingAudio() {
-    // NOTE: !IsReadPending() -> frames_remaining_in_buffer() == 0... but the
+    // NOTE: !IsDecodePending() -> frames_remaining_in_buffer() == 0... but the
     // arrow is unidirectional! DecoderStream does its own buffering of decoded
     // output such that it generally triggers reads even after the renderer's
     // buffer is full. Hence, the loop below must check both of the conditions
     // to ensure no pending reads exist after the function returns.
-    while (frames_remaining_in_buffer().value > 0 || IsReadPending()) {
+    while (frames_remaining_in_buffer().value > 0 || IsDecodePending()) {
       SatisfyPendingRead(InputFrames(kInputFramesChunk));
     }
   }
@@ -559,6 +574,11 @@
   MockDemuxerStream demuxer_stream_;
   MockMediaClient media_client_;
 
+  // When |simulate_demuxer_stall_| is set OnDemuxerRead() will put the callback
+  // in  |stalled_demixer_read_cb_| instead of calling it.
+  bool simulate_demuxer_stall_ = false;
+  DemuxerStream::ReadCB stalled_demixer_read_cb_;
+
   // Used for satisfying reads.
   AudioDecoder::OutputCB output_cb_;
   AudioDecoder::DecodeCB decode_cb_;
@@ -595,7 +615,7 @@
 
   // Stop playback and flush
   StopTicking();
-  EXPECT_TRUE(IsReadPending());
+  EXPECT_TRUE(IsDecodePending());
   // Flush and expect to be notified that we have nothing.
   EXPECT_CALL(*this, OnBufferingStateChange(BUFFERING_HAVE_NOTHING, _));
   FlushDuringPendingRead();
@@ -711,7 +731,6 @@
   // pending read.
   EXPECT_CALL(
       *this, OnBufferingStateChange(BUFFERING_HAVE_NOTHING, DECODER_UNDERFLOW));
-  EXPECT_CALL(demuxer_stream_, IsReadPending()).WillOnce(Return(false));
   EXPECT_FALSE(ConsumeBufferedData(OutputFrames(1)));
 
   // Verify we're still not getting audio data.
@@ -741,7 +760,6 @@
   // pending read.
   EXPECT_CALL(
       *this, OnBufferingStateChange(BUFFERING_HAVE_NOTHING, DEMUXER_UNDERFLOW));
-  EXPECT_CALL(demuxer_stream_, IsReadPending()).WillOnce(Return(true));
   EXPECT_FALSE(ConsumeBufferedData(OutputFrames(1)));
 
   // Verify we're still not getting audio data.
@@ -751,6 +769,12 @@
   // Deliver enough data to have enough for buffering.
   EXPECT_CALL(*this, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH,
                                             BUFFERING_CHANGE_REASON_UNKNOWN));
+
+  // Stall the demuxer to trigger underflow.
+  simulate_demuxer_stall_ = true;
+  SatisfyPendingRead(InputFrames(kInputFramesChunk));
+  UnstallDemuxer();
+
   DeliverRemainingAudio();
 
   // Verify we're getting audio data.
@@ -943,7 +967,7 @@
 
   StopTicking();
 
-  EXPECT_TRUE(IsReadPending());
+  EXPECT_TRUE(IsDecodePending());
 
   // Flush and expect to be notified that we have nothing.
   EXPECT_CALL(*this, OnBufferingStateChange(BUFFERING_HAVE_NOTHING, _));
@@ -967,7 +991,7 @@
 
   StopTicking();
 
-  EXPECT_TRUE(IsReadPending());
+  EXPECT_TRUE(IsDecodePending());
 
   renderer_.reset();
 }
@@ -984,7 +1008,7 @@
 
   StopTicking();
 
-  EXPECT_TRUE(IsReadPending());
+  EXPECT_TRUE(IsDecodePending());
 
   // Start flushing.
   WaitableMessageLoopEvent flush_event;
diff --git a/media/renderers/decrypting_renderer_unittest.cc b/media/renderers/decrypting_renderer_unittest.cc
index 8d55c71..3e6f88d7 100644
--- a/media/renderers/decrypting_renderer_unittest.cc
+++ b/media/renderers/decrypting_renderer_unittest.cc
@@ -41,12 +41,12 @@
         std::move(renderer), &null_media_log_,
         task_environment_.GetMainThreadTaskRunner());
 
+    EXPECT_CALL(cdm_context_, RegisterEventCB(_)).Times(AnyNumber());
     EXPECT_CALL(cdm_context_, GetDecryptor())
         .WillRepeatedly(Return(&decryptor_));
     EXPECT_CALL(decryptor_, CanAlwaysDecrypt())
         .WillRepeatedly(ReturnPointee(&use_aes_decryptor_));
     EXPECT_CALL(decryptor_, CancelDecrypt(_)).Times(AnyNumber());
-    EXPECT_CALL(decryptor_, RegisterNewKeyCB(_, _)).Times(AnyNumber());
     EXPECT_CALL(media_resource_, GetAllStreams())
         .WillRepeatedly(Invoke(this, &DecryptingRendererTest::GetAllStreams));
     EXPECT_CALL(media_resource_, GetType())
diff --git a/media/renderers/video_renderer_impl.cc b/media/renderers/video_renderer_impl.cc
index 31f0df0c..69c7bc9 100644
--- a/media/renderers/video_renderer_impl.cc
+++ b/media/renderers/video_renderer_impl.cc
@@ -346,8 +346,9 @@
   // the decoder for an "underflow" that is really just a seek.
   BufferingStateChangeReason reason = BUFFERING_CHANGE_REASON_UNKNOWN;
   if (state_ == kPlaying && buffering_state == BUFFERING_HAVE_NOTHING) {
-    reason = demuxer_stream_->IsReadPending() ? DEMUXER_UNDERFLOW
-                                              : DECODER_UNDERFLOW;
+    reason = video_decoder_stream_->is_demuxer_read_pending()
+                 ? DEMUXER_UNDERFLOW
+                 : DECODER_UNDERFLOW;
   }
 
   media_log_->AddEvent<MediaLogEvent::kBufferingStateChanged>(
diff --git a/media/renderers/video_renderer_impl_unittest.cc b/media/renderers/video_renderer_impl_unittest.cc
index 093dccb..f82ccb5 100644
--- a/media/renderers/video_renderer_impl_unittest.cc
+++ b/media/renderers/video_renderer_impl_unittest.cc
@@ -107,24 +107,9 @@
     demuxer_stream_.set_video_decoder_config(TestVideoConfig::Normal());
 
     // We expect these to be called but we don't care how/when. Tests can
-    // customize the provided buffer returned via MakeDecoderBuffer().
+    // customize the provided buffer returned via OnDemuxerRead().
     ON_CALL(demuxer_stream_, OnRead(_))
-        .WillByDefault(Invoke(this, &VideoRendererImplTest::MakeDecoderBuffer));
-  }
-
-  void MakeDecoderBuffer(DemuxerStream::ReadCB& read_cb) {
-    scoped_refptr<DecoderBuffer> decoder_buffer(new DecoderBuffer(0));
-
-    // Set |decoder_buffer| timestamp such that it won't match any of the
-    // times provided to QueueFrames(). Otherwise the default timestamp of 0 may
-    // match some frames and not others, which causes non-uniform handling in
-    // DecoderStreamTraits.
-    decoder_buffer->set_timestamp(kNoTimestamp);
-
-    // Test hook for to specify a custom buffer duration.
-    decoder_buffer->set_duration(buffer_duration_);
-
-    std::move(read_cb).Run(DemuxerStream::kOk, decoder_buffer);
+        .WillByDefault(Invoke(this, &VideoRendererImplTest::OnDemuxerRead));
   }
 
   ~VideoRendererImplTest() override = default;
@@ -188,6 +173,35 @@
     base::RunLoop().RunUntilIdle();
   }
 
+  void OnDemuxerRead(DemuxerStream::ReadCB& read_cb) {
+    if (simulate_demuxer_stall_after_n_reads_ >= 0) {
+      if (simulate_demuxer_stall_after_n_reads_-- == 0) {
+        stalled_demixer_read_cb_ = std::move(read_cb);
+        return;
+      }
+    }
+
+    scoped_refptr<DecoderBuffer> decoder_buffer(new DecoderBuffer(0));
+
+    // Set |decoder_buffer| timestamp such that it won't match any of the
+    // times provided to QueueFrames(). Otherwise the default timestamp of 0 may
+    // match some frames and not others, which causes non-uniform handling in
+    // DecoderStreamTraits.
+    decoder_buffer->set_timestamp(kNoTimestamp);
+
+    // Test hook for to specify a custom buffer duration.
+    decoder_buffer->set_duration(buffer_duration_);
+
+    std::move(read_cb).Run(DemuxerStream::kOk, decoder_buffer);
+  }
+
+  bool IsDemuxerStalled() { return !!stalled_demixer_read_cb_; }
+
+  void UnstallDemuxer() {
+    EXPECT_TRUE(IsDemuxerStalled());
+    OnDemuxerRead(stalled_demixer_read_cb_);
+  }
+
   // Parses a string representation of video frames and generates corresponding
   // VideoFrame objects in |decode_results_|.
   //
@@ -234,7 +248,7 @@
     decode_results_.push_back(std::make_pair(status, frame));
   }
 
-  bool IsReadPending() { return !!decode_cb_; }
+  bool IsDecodePending() { return !!decode_cb_; }
 
   void WaitForError(PipelineStatus expected) {
     SCOPED_TRACE(base::StringPrintf("WaitForError(%d)", expected));
@@ -339,6 +353,10 @@
 
   bool expect_init_success_;
 
+  // Specifies how many reads should complete before demuxer stalls.
+  int simulate_demuxer_stall_after_n_reads_ = -1;
+  DemuxerStream::ReadCB stalled_demixer_read_cb_;
+
   // Use StrictMock<T> to catch missing/extra callbacks.
   class MockCB : public MockRendererClient {
    public:
@@ -351,7 +369,7 @@
 
   WallClockTimeSource time_source_;
 
-  // Duration set on DecoderBuffers. See MakeDecoderBuffer().
+  // Duration set on DecoderBuffers. See OnDemuxerRead().
   base::TimeDelta buffer_duration_;
 
  private:
@@ -680,7 +698,7 @@
   StartPlayingFrom(0);
 
   // Check that there is an outstanding Read() request.
-  EXPECT_TRUE(IsReadPending());
+  EXPECT_TRUE(IsDecodePending());
 
   Destroy();
 }
@@ -704,7 +722,7 @@
         .WillOnce(RunOnceClosure(event.GetClosure()));
     StartPlayingFrom(0);
 
-    EXPECT_TRUE(IsReadPending());
+    EXPECT_TRUE(IsDecodePending());
     SatisfyPendingDecodeWithEndOfStream();
 
     event.RunAndWait();
@@ -733,7 +751,7 @@
     StartPlayingFrom(0);
     renderer_->OnTimeProgressing();
 
-    EXPECT_TRUE(IsReadPending());
+    EXPECT_TRUE(IsDecodePending());
     SatisfyPendingDecodeWithEndOfStream();
     WaitForEnded();
 
@@ -1225,6 +1243,9 @@
     if (test_type == UnderflowTestType::CANT_READ_WITHOUT_STALLING)
       ON_CALL(*decoder_, CanReadWithoutStalling()).WillByDefault(Return(false));
 
+    if (underflow_type == DEMUXER_UNDERFLOW) {
+      simulate_demuxer_stall_after_n_reads_ = 4;
+    }
     QueueFrames("0 20 40 60");
 
     {
@@ -1269,8 +1290,6 @@
     {
       SCOPED_TRACE("Waiting for BUFFERING_HAVE_NOTHING");
       WaitableMessageLoopEvent event;
-      EXPECT_CALL(demuxer_stream_, IsReadPending())
-          .WillOnce(Return(underflow_type == DEMUXER_UNDERFLOW));
       EXPECT_CALL(mock_cb_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING,
                                                    underflow_type))
           .WillOnce(RunOnceClosure(event.GetClosure()));
@@ -1283,7 +1302,8 @@
     time_source_.StopTicking();
     renderer_->OnTimeStopped();
     EXPECT_EQ(0u, renderer_->frames_queued_for_testing());
-    ASSERT_TRUE(IsReadPending());
+    ASSERT_EQ(underflow_type == DEMUXER_UNDERFLOW, IsDemuxerStalled());
+    ASSERT_EQ(underflow_type == DECODER_UNDERFLOW, IsDecodePending());
 
     // Stopping time signals a confirmed underflow to VRI. Verify updates to
     // buffering limits.
@@ -1313,6 +1333,9 @@
 TEST_P(UnderflowTest, UnderflowAndEosTest) {
   TestBufferToHaveEnoughThenUnderflow();
 
+  if (IsDemuxerStalled())
+    UnstallDemuxer();
+
   // Receiving end of stream should signal having enough.
   {
     SCOPED_TRACE("Waiting for BUFFERING_HAVE_ENOUGH");
@@ -1333,6 +1356,9 @@
 TEST_P(UnderflowTest, UnderflowAndRecoverTest) {
   TestBufferToHaveEnoughThenUnderflow();
 
+  if (IsDemuxerStalled())
+    UnstallDemuxer();
+
   // Queue some frames, satisfy reads, and make sure expired frames are gone
   // when the renderer paints the first frame.
   {
@@ -1536,6 +1562,9 @@
   // Set latency hint to a medium value.
   renderer_->SetLatencyHint(base::TimeDelta::FromMilliseconds(200));
 
+  // Stall the demuxer after 7 frames.
+  simulate_demuxer_stall_after_n_reads_ = 7;
+
   // Queue up enough frames to trigger HAVE_ENOUGH. Each frame is 30 ms apart.
   // At this spacing, 200ms rounds to 7 frames.
   EXPECT_CALL(mock_cb_, FrameReceived(HasTimestampMatcher(0)));
@@ -1556,7 +1585,6 @@
     SCOPED_TRACE("Waiting for BUFFERING_HAVE_NOTHING");
     WaitableMessageLoopEvent event;
     EXPECT_CALL(mock_cb_, FrameReceived(HasTimestampMatcher(180)));
-    EXPECT_CALL(demuxer_stream_, IsReadPending()).WillOnce(Return(true));
     EXPECT_CALL(mock_cb_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING,
                                                  DEMUXER_UNDERFLOW))
         .WillOnce(RunOnceClosure(event.GetClosure()));
diff --git a/net/cookies/canonical_cookie.cc b/net/cookies/canonical_cookie.cc
index 5d4c3b7d..4c6f855 100644
--- a/net/cookies/canonical_cookie.cc
+++ b/net/cookies/canonical_cookie.cc
@@ -945,25 +945,26 @@
   return (base::Time::Now() - creation_date_) <= age_threshold;
 }
 
-CookieAndLineWithStatus::CookieAndLineWithStatus() = default;
+CookieAndLineWithAccessResult::CookieAndLineWithAccessResult() = default;
 
-CookieAndLineWithStatus::CookieAndLineWithStatus(
+CookieAndLineWithAccessResult::CookieAndLineWithAccessResult(
     base::Optional<CanonicalCookie> cookie,
     std::string cookie_string,
-    CookieInclusionStatus status)
+    CookieAccessResult access_result)
     : cookie(std::move(cookie)),
       cookie_string(std::move(cookie_string)),
-      status(status) {}
+      access_result(access_result) {}
 
-CookieAndLineWithStatus::CookieAndLineWithStatus(
-    const CookieAndLineWithStatus&) = default;
+CookieAndLineWithAccessResult::CookieAndLineWithAccessResult(
+    const CookieAndLineWithAccessResult&) = default;
 
-CookieAndLineWithStatus& CookieAndLineWithStatus::operator=(
-    const CookieAndLineWithStatus& cookie_and_line_with_status) = default;
-
-CookieAndLineWithStatus::CookieAndLineWithStatus(CookieAndLineWithStatus&&) =
+CookieAndLineWithAccessResult& CookieAndLineWithAccessResult::operator=(
+    const CookieAndLineWithAccessResult& cookie_and_line_with_access_result) =
     default;
 
-CookieAndLineWithStatus::~CookieAndLineWithStatus() = default;
+CookieAndLineWithAccessResult::CookieAndLineWithAccessResult(
+    CookieAndLineWithAccessResult&&) = default;
+
+CookieAndLineWithAccessResult::~CookieAndLineWithAccessResult() = default;
 
 }  // namespace net
diff --git a/net/cookies/canonical_cookie.h b/net/cookies/canonical_cookie.h
index f3ec943..4cb6336 100644
--- a/net/cookies/canonical_cookie.h
+++ b/net/cookies/canonical_cookie.h
@@ -26,13 +26,12 @@
 class ParsedCookie;
 class CanonicalCookie;
 
-struct CookieWithStatus;
 struct CookieWithAccessResult;
-struct CookieAndLineWithStatus;
+struct CookieAndLineWithAccessResult;
 
 using CookieList = std::vector<CanonicalCookie>;
-using CookieStatusList = std::vector<CookieWithStatus>;
-using CookieAndLineStatusList = std::vector<CookieAndLineWithStatus>;
+using CookieAndLineAccessResultList =
+    std::vector<CookieAndLineWithAccessResult>;
 using CookieAccessResultList = std::vector<CookieWithAccessResult>;
 
 class NET_EXPORT CanonicalCookie {
@@ -362,34 +361,27 @@
   CookieSourceScheme source_scheme_;
 };
 
-// These enable us to pass along a list of excluded cookie with the reason they
-// were excluded
-struct CookieWithStatus {
-  CanonicalCookie cookie;
-  CookieInclusionStatus status;
-};
-
 // Used to pass excluded cookie information when it's possible that the
 // canonical cookie object may not be available.
-struct NET_EXPORT CookieAndLineWithStatus {
-  CookieAndLineWithStatus();
-  CookieAndLineWithStatus(base::Optional<CanonicalCookie> cookie,
-                          std::string cookie_string,
-                          CookieInclusionStatus status);
-  CookieAndLineWithStatus(
-      const CookieAndLineWithStatus& cookie_and_line_with_status);
+struct NET_EXPORT CookieAndLineWithAccessResult {
+  CookieAndLineWithAccessResult();
+  CookieAndLineWithAccessResult(base::Optional<CanonicalCookie> cookie,
+                                std::string cookie_string,
+                                CookieAccessResult access_result);
+  CookieAndLineWithAccessResult(
+      const CookieAndLineWithAccessResult& cookie_and_line_with_access_result);
 
-  CookieAndLineWithStatus& operator=(
-      const CookieAndLineWithStatus& cookie_and_line_with_status);
+  CookieAndLineWithAccessResult& operator=(
+      const CookieAndLineWithAccessResult& cookie_and_line_with_access_result);
 
-  CookieAndLineWithStatus(
-      CookieAndLineWithStatus&& cookie_and_line_with_status);
+  CookieAndLineWithAccessResult(
+      CookieAndLineWithAccessResult&& cookie_and_line_with_access_result);
 
-  ~CookieAndLineWithStatus();
+  ~CookieAndLineWithAccessResult();
 
   base::Optional<CanonicalCookie> cookie;
   std::string cookie_string;
-  CookieInclusionStatus status;
+  CookieAccessResult access_result;
 };
 
 struct CookieWithAccessResult {
diff --git a/net/dns/httpssvc_metrics.cc b/net/dns/httpssvc_metrics.cc
index 453e2a3..84228ad 100644
--- a/net/dns/httpssvc_metrics.cc
+++ b/net/dns/httpssvc_metrics.cc
@@ -59,7 +59,6 @@
 }
 
 bool HttpssvcExperimentDomainCache::IsControl(base::StringPiece domain) {
-  std::vector<base::StringPiece> control_domains;
   if (!base::FeatureList::IsEnabled(features::kDnsHttpssvc))
     return false;
   if (features::kDnsHttpssvcControlDomainWildcard.Get())
@@ -153,8 +152,8 @@
   DCHECK(base::FeatureList::IsEnabled(features::kDnsHttpssvc));
   DCHECK(features::kDnsHttpssvcUseIntegrity.Get());
 
-  DCHECK(in_progress_);
-  in_progress_ = false;
+  DCHECK(!already_recorded_);
+  already_recorded_ = true;
 
   // We really have no metrics to record without |integrity_resolve_time_| and
   // |non_integrity_resolve_times_|. If this HttpssvcMetrics is in an
diff --git a/net/dns/httpssvc_metrics.h b/net/dns/httpssvc_metrics.h
index 76e4795..8594b7c 100644
--- a/net/dns/httpssvc_metrics.h
+++ b/net/dns/httpssvc_metrics.h
@@ -93,10 +93,10 @@
 
   void set_doh_provider_id(base::Optional<std::string> doh_provider_id);
 
+  const bool expect_intact_;
   // RecordIntegrityMetrics() will do nothing when |disqualified_| is true.
   bool disqualified_ = false;
-  const bool expect_intact_;
-  bool in_progress_ = true;
+  bool already_recorded_ = false;
   base::Optional<std::string> doh_provider_id_;
   base::Optional<enum HttpssvcDnsRcode> rcode_integrity_;
   size_t num_integrity_records_ = 0;
diff --git a/net/http/transport_security_state_static.json b/net/http/transport_security_state_static.json
index e63d066..0977198 100644
--- a/net/http/transport_security_state_static.json
+++ b/net/http/transport_security_state_static.json
@@ -4195,7 +4195,6 @@
     { "name": "secure.advancepayroll.com.au", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "0x1337.eu", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "206rc.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "360gradus.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "adimaja.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "akhilindurti.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "alcazaar.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -4998,7 +4997,6 @@
     { "name": "teddy.ch", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "tetsumaki.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "textracer.dk", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "tf2b.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "thedark1337.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "themicrocapital.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "theojones.name", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -8284,7 +8282,6 @@
     { "name": "cg-systems.hu", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "cgan.pw", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "chabaudparfum.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "championsofregnum.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "chaos-inc.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "charityclear.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "charmyadesara.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -9113,7 +9110,6 @@
     { "name": "loadso.me", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "localbitcoins.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "logario.com.br", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "london-transfers.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "londonlanguageexchange.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "longboarding-ulm.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "lordjevington.co.uk", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -9157,7 +9153,6 @@
     { "name": "manningbrothers.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "maosensanguentadasdejesus.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "marcel-preuss.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "marchagen.nl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "marcontrol.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "mark-a-hydrant.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "marketingdesignu.cz", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -9188,7 +9183,6 @@
     { "name": "mdpraha.cz", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "mdwftw.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "mechanus.io", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "medba.se", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "mediaburst.co.uk", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "mediaselection.eu", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "medm-test.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -11281,7 +11275,6 @@
     { "name": "webdev.mobi", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "webmedpharmacy.co.uk", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "werktor.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "wheeler.kiwi.nz", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "whiterabbitcakery.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "whistleb.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "widemann.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -12330,7 +12323,6 @@
     { "name": "cybozulive-dev.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "czechvirus.cz", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "czerno.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "d66.nl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "daemen.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "dalfsennet.nl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "darktime.ru", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -12636,7 +12628,6 @@
     { "name": "ichnichtskaufmann.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "icloud.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "icntorrent.download", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "icymint.me", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "idconsult.nl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "idmobile.co.uk", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "idsafe.co.za", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -12695,7 +12686,6 @@
     { "name": "ivinet.cl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "ivitalia.it", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "ixio.cz", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "jacobian.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "jadopado.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "jakereynolds.co", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "jamesf.xyz", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -13096,7 +13086,6 @@
     { "name": "printerest.io", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "prnav.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "prodct.info", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "productdesignsoftware.com.au", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "producto8.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "proefteksten.nl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "profusion.io", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -13472,7 +13461,6 @@
     { "name": "used-in.jp", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "usedesk.ru", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "utvbloggen.se", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "uzmandroid.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "uzmandroid.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "uzmandroid.top", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "v2ex.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -13550,7 +13538,6 @@
     { "name": "wuhengmin.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "x-ripped-hd.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "xbtce.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "xecureit.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "xfix.pw", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "xjoin.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "xmedius.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -15492,7 +15479,6 @@
     { "name": "coincoin.eu.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "cosmundi.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "cuetoems.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "compartir.party", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "commune-preuilly.fr", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "cybersmart.co.uk", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "couponcodeq.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -15550,7 +15536,6 @@
     { "name": "devdesco.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "datenschutz-individuell.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "develux.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "deliberatedigital.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "davidglidden.eu", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "chaska.co.za", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "digitalero.rip", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -15937,7 +15922,6 @@
     { "name": "linklocker.co", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "libraryextension.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "krizevci.info", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "lonerwolf.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "leaks.directory", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "link-sanitizer.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "linksanitizer.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -16067,7 +16051,6 @@
     { "name": "muling.lu", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "moevenpick-cafe.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "momut.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "molun.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "nbrown.us", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "nien.co", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "melangebrasil.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -16520,7 +16503,6 @@
     { "name": "xtom.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "wyday.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "yabrt.cn", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "xilkoi.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "webtechgadgetry.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "xn--dmonenjger-q5ag.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "xiamuzi.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -18226,7 +18208,6 @@
     { "name": "reinaldudras.ee", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "repair.by", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "nzquakes.maori.nz", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "securethe.news", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "muckingabout.eu", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "safetext.me", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "ruhrnalist.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -18766,7 +18747,6 @@
     { "name": "yame2.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "youcruit.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "youon.tokyo", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "zer0.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "wpcarer.pro", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "uygindir.ml", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "zen-ume.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -22195,7 +22175,6 @@
     { "name": "wstx.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "wxh.jp", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "xd.fi", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "xecure.zone", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "xenoworld.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "xeonlab.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "xeonlab.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -22483,7 +22462,6 @@
     { "name": "anxiolytics.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "anymetrix.io", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "anyon.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "aozora.moe", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "apila.care", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "aplikaceproandroid.cz", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "aplu.fr", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -23268,7 +23246,6 @@
     { "name": "eurovision.ie", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "evasioncreole.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "evecalm.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "eventive.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "eventsafrica.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "everyday.eu.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "eveshaiwu.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -23597,7 +23574,6 @@
     { "name": "hedonium.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "hedweb.co.uk", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "hefengautoparts.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "heiaheia.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "heidisheroes.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "hekeki.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "hellofilters.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -24286,7 +24262,6 @@
     { "name": "mlcambiental.com.br", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "mlpchan.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "mlpvc-rr.ml", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "mmcc.pe", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "mmilog.hu", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "mmt.my", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "mnec.io", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -24308,7 +24283,6 @@
     { "name": "moneychangersoftware.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "montagne-tendance.ch", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "montsaintaignan.fr", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "moodzshop.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "mopsuite.club", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "morbotron.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "mordrum.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -24329,7 +24303,6 @@
     { "name": "mstdn-tech.jp", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "mstdn.blue", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "mstdn.club", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "mstdn.fr", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "mstdn.io", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "mstdn.nl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "mtb.wtf", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -25489,7 +25462,6 @@
     { "name": "vatelecom.dk", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "vayaport.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "vcam.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "vdisk24.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "veganism.co.uk", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "veglog.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "vendigital.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -25730,7 +25702,6 @@
     { "name": "zooish.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "zoological-gardens.eu", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "zsoltsandor.me", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "zuefle.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "zughilfen-test.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "zukix.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "zwb3.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -25944,7 +25915,6 @@
     { "name": "arg.zone", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "armenians.online", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "annetaan.fi", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "asd.gov.au", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "arifp.me", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "aooobo.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "appartementmarsum.nl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -26104,7 +26074,6 @@
     { "name": "beacinsight.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "bestbyte.com.br", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "bernhardkau.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "belastingdienst-in-beeld.nl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "bison.co", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "blenderrecipereviews.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "biblioblog.fr", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -26150,7 +26119,6 @@
     { "name": "boukoubengo.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "bou.lt", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "braintm.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "boodmo.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "blakecoin.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "bondarenko.dn.ua", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "brettelliff.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -27228,7 +27196,6 @@
     { "name": "julianickel.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "juliawebber.co.za", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "jornalalerta.com.br", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "joyjohnston.ca", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "jorovik.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "johnfulgenzi.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "just-pools.co.za", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -27309,7 +27276,6 @@
     { "name": "kovspace.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "kpop.re", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "labrasaq8.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "koalapress.fr", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "ksukelife.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "kirrie.pe.kr", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "laflash.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -27473,7 +27439,6 @@
     { "name": "lisgade.dk", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "loanmatch.sg", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "magentaize.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "macstore.pe", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "mail.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "malikussa.id", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "magebankin.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -27721,7 +27686,6 @@
     { "name": "mydigitalweek.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "nhliberty.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "newgrowbook.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "nicolajanedesigns.co.uk", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "negativzinsen.info", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "nicoleoquendo.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "michaeln.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -27787,7 +27751,6 @@
     { "name": "nlrb.gov", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "ojaioliveoil.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "nishaswonderland.be", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "oberoi.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "omarh.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "omyogarishikesh.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "normankranich.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -28503,13 +28466,9 @@
     { "name": "taartenfeesies.nl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "suzi3d.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "tatiloley.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "teambeam.ch", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "teambeam.at", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "teambeam.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "tchnics.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "techshift.eu", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "techshift.nl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "teambeam.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "symbiose-bien-etre.ch", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "teddylu.info", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "techshift.se", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -30486,7 +30445,6 @@
     { "name": "redessantaluzia.com.br", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "rylore.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "safegold.ca", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "reineberthe.ch", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "rudrastyh.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "roulons-autrement.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "pretrialservices.gov", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -30789,7 +30747,6 @@
     { "name": "tucsonfcu.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "trackeye.dk", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "tristanfarkas.one", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "tufilo.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "trainline.cn", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "texastwostepdivorce.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "tmcreationweb.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -30835,7 +30792,6 @@
     { "name": "sugarsweetorsour.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "undeadbrains.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "vadennissanofhiltonheadparts.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "utugnn.ru", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "valenciadevops.me", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "u4mh-dev-portal.azurewebsites.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "uxux.pl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -31347,7 +31303,6 @@
     { "name": "helpstarloja.com.br", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "homesandal.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "hotcandlestick.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "howardwatts.co.uk", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "hulpbijmarketing.nl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "huskyduvercors.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "ineed.com.mt", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -31400,7 +31355,6 @@
     { "name": "marilynmartin.com.au", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "mathijskingma.nl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "maury-moteurs.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "medeinos.lt", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "mediafocus.biz", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "memiux.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "mexicom.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -31795,7 +31749,6 @@
     { "name": "ingo-schlueter.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "ingoschlueter.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "inter-corporate.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "intraobes.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "ioslo.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "iplantom.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "ipo-times.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -33024,7 +32977,6 @@
     { "name": "kiteschoolnoordwijk.nl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "kj-prince.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "kjellner.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "kks-karlstadt.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "knnet.ch", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "knownsec.cf", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "knurps.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -35110,7 +35062,6 @@
     { "name": "hairraisingphotobooths.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "hak5.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "halkirkbouncycastles.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "hancatemc.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "happybounce.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "happykidscastles.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "haribilalic.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -35451,7 +35402,6 @@
     { "name": "myowndisk.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "mzh.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "n26.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "nakedtruthbeauty.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nanovolt.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "narazaka.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "narviz.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -36234,7 +36184,6 @@
     { "name": "gotobrno.cz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "gouptime.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "gpsolarpanels.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "greenville.ag", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "gulchuk.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "guochang.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "gvobgyn.ca", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -36483,7 +36432,6 @@
     { "name": "sarkarischeme.in", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "satimagingcorp.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "satragreen.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "save-me-aachen.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "save-me-koeln.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "savemoneyonenergy.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "scala.click", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -36551,9 +36499,6 @@
     { "name": "suts.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "swacp.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "swiss-apartments.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "syhost.at", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "syhost.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "syhost.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "system365.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "taabe.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "takedownthissite.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -36912,7 +36857,6 @@
     { "name": "jinkuru.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "jltctech.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "jobbsafari.no", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "jobbsafari.se", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "jobindex.dk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "johndball.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "johntomasowa.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -37683,7 +37627,6 @@
     { "name": "wot-tudasbazis.hu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "wvw-8522.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "xdty.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "xeedbeam.me", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "xjpvictor.info", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "xn--dragni-g1a.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "xn--o77hka.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -38992,7 +38935,6 @@
     { "name": "season.moe", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sebastianpedersen.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sebasveeke.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "sec.red", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sec455.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sec530.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sec555.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -41244,7 +41186,6 @@
     { "name": "failoverplan.it", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "fairssl.se", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "faisalshuvo.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "faithleaks.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "familyworld.gr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "faraonplay5.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "fegli.gov", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -41709,7 +41650,6 @@
     { "name": "xzoneadventure.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "yahan.tv", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "yazaral.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "ylde.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "yourskin.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "yuanjiazhao.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "yukari.cafe", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -42935,7 +42875,6 @@
     { "name": "peerigon.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "permeance108.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "pesto.video", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "pflanzenshop-emsland.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ph3r3tz.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "pharmica.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "pharmica.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -44943,7 +44882,6 @@
     { "name": "prismacloud.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "pro-image.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "projet-fly.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "promo-computers.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "promohulp.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "protege.moi", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "psychologie-hofner.at", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -47385,7 +47323,6 @@
     { "name": "reflexive.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "refuelcollective.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "reinaertvandecruys.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "reparo.pe", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "repology.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "residentiallocksmithsanantoniotx.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "responsive-shop.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -47651,7 +47588,6 @@
     { "name": "comocurarlagastritistratamientonatural.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "cosasque.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "cprheartcenter.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "craigary.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "crimefreeliving.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "crochetnerd.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "curareldolordeespalda.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -48033,7 +47969,6 @@
     { "name": "b-freerobux.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "backupsinop.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "badgirlsbible.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "balivillassanur.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "bandar303.id", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "bankgradesecurity.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "barf-alarm.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -48283,7 +48218,6 @@
     { "name": "identity.plus", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ifangpei.cn", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ifangpei.com.cn", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "igeh-immo.at", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ihongzu.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ihzys.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ii74.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -48660,7 +48594,6 @@
     { "name": "truong.fi", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "tsu-ku-ro.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ttspttsp.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "tu-immoprojekt.at", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "tusksol.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "tvqc.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "u0010.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -49014,7 +48947,6 @@
     { "name": "saleduck.com.vn", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "samtalen.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sannesfotklinikk.no", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "saol.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sattamatkadpboss.mobi", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "scroll.in", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "secretsdujeu.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -49346,7 +49278,6 @@
     { "name": "connorhatch.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "conociendosalama.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "constructieve.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "contractdigital.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "coolbitx.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "coonawarrawines.com.au", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "coppermein.co.za", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -49457,7 +49388,6 @@
     { "name": "ecardoo.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "eentweevijf.be", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "efcross.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "egres.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ehbssl.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ehub.cz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ehub.hu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -50614,7 +50544,6 @@
     { "name": "locksmithspringtx.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "locksmithswestville.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "lohmeier.it", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "long-journey.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "lucz.co", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "lumen.sh", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "lupinenorthamerica.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -52492,7 +52421,6 @@
     { "name": "computer-science-schools.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "conclinica.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "construction-colleges.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "costellofc.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "covaci.pro", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "crazy-cat.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "credittoken.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -53063,7 +52991,6 @@
     { "name": "stgeorgegolfing.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "stgm.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "stickstueb.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "stisaac.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "stisidores.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "stjosephspringcity.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "stmariagoretti.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -53630,7 +53557,6 @@
     { "name": "usaseanconnect.gov", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "usdoj.gov", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "userra.gov", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "usphs.gov", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ussst.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ustugov.kiev.ua", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ustugova.kiev.ua", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -53923,7 +53849,6 @@
     { "name": "lolico.moe", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "lrdo.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "luowu.cc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "luvplay.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "lynsec.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "machinetransport.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "maitheme.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -54333,8 +54258,6 @@
     { "name": "consegnafioridomicilio.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "constitution.website", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "cool.haus", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "cooperativehandmade.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "cooperativehandmade.pe", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "copydz.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "corpuschristisouthriver.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "costcoinsider.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -56466,7 +56389,6 @@
     { "name": "secretpigeon.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "section77.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "securist.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "secvault.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "seedcoworking.es", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "segnidisegni.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sekikawa.biz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -57427,7 +57349,6 @@
     { "name": "ewok.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "excella.me", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "exmart.ng", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "eye-encounters.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "eyrelles-tissus.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "faeservice.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "fafarishoptrading.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -58045,7 +57966,6 @@
     { "name": "545755.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "555wfcp.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "58nav.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "5beanskit.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "5stars.tv", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "5yeb.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "620881.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -58589,7 +58509,6 @@
     { "name": "integrata.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "internetgardener.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "intropickup.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "ipripojeni.cz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ipso.paris", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "irismq.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "irlfp.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -58891,7 +58810,6 @@
     { "name": "optimaner.pl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ordoh.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ore.cool", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "origin8delicafes.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "osolutionscorp.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ostachstore.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "otisko.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -59290,7 +59208,6 @@
     { "name": "yooguo123.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "yooomu.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "yosakoinight.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "youhs.top", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "yourbodyknows.dk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "yourbodyknows.is", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "yourscotlandtour.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -59300,7 +59217,6 @@
     { "name": "yycbike.info", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "yyy116.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "yyy608.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "zaffit.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "zalzalac.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "zap-mag.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "zeyi.fan", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -59481,7 +59397,6 @@
     { "name": "improvision.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "indiansmartpanel.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "inflated.cloud", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "inoxmavang.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "isaropiping.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "jabba.homelinux.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "jonas.me", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -60210,7 +60125,6 @@
     { "name": "mindmax.fi", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "mionerve.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "mionerve.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "misterseguros.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "mmgal.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "mostcomfortableworkboots.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "motogb.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -61833,7 +61747,6 @@
     { "name": "romaindepeigne.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "rssnews.world", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "s1128.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "sac.moe", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "saytu.co", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "schoenstatt-fathers.link", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "schoolarchive.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -61918,7 +61831,6 @@
     { "name": "vinosalmundo.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "vivemedialab.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "voceempaz.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "vrcinvestigations.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "wage-feeg.gc.ca", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "wallis-inside.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "waterproofingahmedabad.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -62090,7 +62002,6 @@
     { "name": "gyoza.beer", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "haju.fi", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "hatcher.cloud", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "hikawa.top", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "hillier-swift.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "hitrost.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "honglitrading.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -62333,7 +62244,6 @@
     { "name": "bariatrica.es", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "barneydavey.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "baugelitt.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "bcdiesel.ca", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "bestladyshaver.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "betrifft-mich-dsgvo.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "bi1gif.radio", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -62621,7 +62531,6 @@
     { "name": "267661.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "351113.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "382225.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "4u.am", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "54.sb", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "592227.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "599980.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -62718,7 +62627,6 @@
     { "name": "dmoj.ca", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "dreamdestine.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "drgerthplasticsurgery.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "drjobs.com.au", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "dsgvo-addon.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "e-beyond.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "e-labo.works", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -63251,7 +63159,6 @@
     { "name": "myvegan.menu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nalsai.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nba-croatia.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "negril.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nibouw.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nico.today", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nikitashevchenko.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -64439,7 +64346,6 @@
     { "name": "torneobottacin.it", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "tosatopsicologabologna.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "touchtunesnz.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "towtruck.website", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "tozdev.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "tpastream.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "tradeonfx.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -64648,7 +64554,6 @@
     { "name": "basketforex.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "bazari.com.pl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "berkat-luqs.ddns.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "bf5.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "bfh.science", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "bibliotecadeseguranca.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "billigastehemsidan.se", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -64785,7 +64690,6 @@
     { "name": "liztattoo.se", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "llbcpa.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "lokalna.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "lonwan.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "lunite.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "lycetre.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "malenaamatomd.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -64899,7 +64803,6 @@
     { "name": "uronlinestreams.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "usbcurrent.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "v5ray.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "venti-athens.gr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "victorpelletmill.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "vscodownloader.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "warezoom.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -65158,7 +65061,6 @@
     { "name": "ahd1234.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ainishitou.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "airslate.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "ajwebsolutions.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "alfaproweb.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "aliv.biz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "aloexn.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -65518,7 +65420,6 @@
     { "name": "sointelcom.com.co", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sort.land", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sourdough.vc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "soverin.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "soyfanonline.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "space-inc.co.jp", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ssc8809.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -66487,7 +66388,6 @@
     { "name": "xinbo390.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "xinbo396.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "xinbo400.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "xlfilippou.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "xn--8n2am80a.tech", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "xn--acompaamientoholistico-pec.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "xn--hmdiseoweb-y9a.com.ar", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -67734,7 +67634,6 @@
     { "name": "compassbest.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "compratecno.cl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "contentq.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "cordobaaldia.com.mx", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "corevetconnect.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "coronersconnect.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "cortealcastello.it", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -67923,7 +67822,6 @@
     { "name": "lockme.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "lockme.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "lockme.pl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "lookgadgets.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "lumenbrowser.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "m6729.co", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "m6729.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -69255,7 +69153,6 @@
     { "name": "grantpark.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "guancha.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "gulcinulutuna.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "haehnel.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "hawaiiwho.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "healthfitapp.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "heinrich-kleyer-schule.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -69917,7 +69814,6 @@
     { "name": "copan.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "cosentus.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "crossnet.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "cultureshift.co", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "cumnock.name", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "cyberweightloss.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "d3dev.cf", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -69972,12 +69868,10 @@
     { "name": "financecontrol.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "floridawaterapparel.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "forsaleinedmonton.ca", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "fossdaily.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "freelancemw.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "friseur-foerder.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "fsavc.org.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "fundingrainbows.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "fyroeo.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "gatewayclub.com.au", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "gb-repair.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "gesunddurchenergie.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -70451,7 +70345,6 @@
     { "name": "3dtootmine.ee", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "432web.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "588e.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "58w66.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "62222.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "88btt.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "91milk.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -71625,7 +71518,6 @@
     { "name": "365b58.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "365y0.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "365y00.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "365y1.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "365y11.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "365y2.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "365y22.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -71786,31 +71678,7 @@
     { "name": "51365d.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "51365dd.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "51365ee.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "52002a.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "52002b.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "52002c.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "52002d.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "52002e.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "52002f.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "52002g.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "52002h.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "52002i.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "52002j.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "52002k.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "52002l.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "52002m.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "52002n.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "52002o.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "52002p.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "52002q.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "52002r.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "52002s.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "52002t.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "52002u.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "52002v.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "52002w.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "52002x.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "52002y.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "55554048.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "555b58.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "5566bet.vip", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -71828,7 +71696,6 @@
     { "name": "59859l.vip", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "59859y.vip", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "59859z.vip", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "598877.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "59rus.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "5ilg.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "60n13.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -71968,8 +71835,6 @@
     { "name": "80n13.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "811121.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "812221.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "8278aa.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "8278bet.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "8278eee.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "8278jjj.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "83365365.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -72010,12 +71875,9 @@
     { "name": "999b58.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "99n13.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "a30365.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "a365vip1.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "a365vip2.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "a365vip3.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "a365vip5.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "a365vip7.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "a365vip9.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "a6632.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "aa7666.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "aadv.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -76354,15 +76216,6 @@
     { "name": "roy-buehring.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "rps-auto.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "rubblerock.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "s550.cc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "s551.cc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "s552.cc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "s553.cc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "s554.cc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "s556.cc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "s557.cc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "s558.cc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "s559.cc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sa68.cc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sam66.cc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sand66.cc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -76401,8 +76254,6 @@
     { "name": "systemausfall.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "t6370.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "t8003.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "taguette.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "taguette.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "taguette.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "teamacadia.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "teamsuccess.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -76733,7 +76584,6 @@
     { "name": "nativeonestop.gov", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nbm.gov", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nkp.bg", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "nksky.cn", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "noobsrus.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "novacoaching.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nuverabusiness.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -76894,7 +76744,6 @@
     { "name": "whitesuithacking.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "widgetmaker.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "wireshocks.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "wolkenbauer.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "worio.co", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "wpabzar.ir", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "wx6688.cc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -77016,7 +76865,6 @@
     { "name": "droid101.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "drweinrach.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "duijf.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "dukers-baelemans.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "dwood.store", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "dxmpay.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "dyneco.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -77049,7 +76897,6 @@
     { "name": "flixstats.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "food4healthybones.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "frasesdodia.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "freakyaweso.me", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ftnpower.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "fullreggaetonrd.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "gamegear.club", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -77254,7 +77101,6 @@
     { "name": "vch.moe", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "venusbeautyproducts.in", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "verwimp.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "victorfiller.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "vnology.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "wd36.cc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "webszolgaltatas.hu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -77504,7 +77350,6 @@
     { "name": "netfirmtextile.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nethorizon.cn", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "networksolutionsconsultant.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "newstargeted.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "noga4you.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nomzamo.spdns.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nsine.be", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -77771,7 +77616,6 @@
     { "name": "fairgreenlimited.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "falasteenjobs.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "fashworldtrends.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "fasthost.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "fd020.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "festx.co.za", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "fgafsaneh.ir", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -77839,7 +77683,6 @@
     { "name": "itemstore.ir", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "jackspub.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "japansm.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "jennysarl.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "jerseyink.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "joedeblasio.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "julienstalder.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -78241,7 +78084,6 @@
     { "name": "premiumdeal.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "premkumar.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "primglaz.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "proweb.solutions", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "pugetsoundspas.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "qosmoschools.edu.my", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "quiz4math.gr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -78355,7 +78197,6 @@
     { "name": "162229.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "22i.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "27is.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "31du.cn", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "3369p.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "3389p.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "3666ks.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -79432,7 +79273,6 @@
     { "name": "resolvergroup.com.au", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "rfid-schutz.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "riograndesurgeons.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "risxx.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "roelenscitynews.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "rogersnowing.cn", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "rory.best", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -79464,7 +79304,6 @@
     { "name": "sky-torch.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "skyntalent.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sluimann.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "snipermarkettiming.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "snoopyfacts.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "solsocog.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sombemerchant.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -79837,7 +79676,6 @@
     { "name": "b67803.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "b67804.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "b67805.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "b70883.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "b70884.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "b70885.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "baanpingchan.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -80885,7 +80723,6 @@
     { "name": "winckelmann2020.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "woodwo.se", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "wossl.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "wtfbryan.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "wulala.us", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "xahbspl.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ya.mk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -82586,7 +82423,6 @@
     { "name": "19990gg.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "19990ii.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "19990jj.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "19990k.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "19990q.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "19990r.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "19990tt.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -82598,7 +82434,6 @@
     { "name": "5i.gs", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "608vets.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "77dd.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "88740a.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "88740b.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "88740g.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "a2ch.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -82780,7 +82615,6 @@
     { "name": "fisiotohome.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "flass.lu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "flightright.it", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "flixcheck.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "floristik-online.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "fluffy.moe", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "flyersmarket.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -84545,7 +84379,6 @@
     { "name": "schnitzel-und-co.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sebastian-walla.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "selectra.pt", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "seobase.pro", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sexonosalao.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sgsy.bid", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sh0uld.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -85446,7 +85279,6 @@
     { "name": "trycaviar.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "tsmn.com.au", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "tuoicay.vn", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "tvtion.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "tvzahist.com.ua", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "tzonevrakis.gr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ucdap.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -87684,7 +87516,6 @@
     { "name": "vacation-croatia.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "valescaind.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "valkohalla.dk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "vap.llc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "vapteke.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "variable.dk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "vcacursus.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -87692,7 +87523,6 @@
     { "name": "vdo-webshop.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "vedatkarabacak.av.tr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "veiergangvermut.dk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "verata.co", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "veryhappy.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "veryhome.com.pe", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "veryssl.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -87968,9 +87798,6 @@
     { "name": "huai123.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "i0day.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ibex-lb.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "iikd.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "ikud-seminare.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "ikud.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "in2-comms.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "internet-meesters.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "internewscast.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -88188,7 +88015,6 @@
     { "name": "616758.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "616798.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "6upagent.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "76678.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "878989.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "a1demolitionhauling.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "a66.la", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -88236,7 +88062,6 @@
     { "name": "b5902.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "b5903.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "b5904.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "b5905.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "b5906.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "b5907.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "b5908.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -88296,7 +88121,6 @@
     { "name": "elradix.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "empoweraces.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "enity.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "farizhan.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "fatiguesyndrome.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "fauwater.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "favedog.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -88632,7 +88456,6 @@
     { "name": "americoadvogados.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "amigosencanada.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "amv.su", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "angeloanan.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "animem.es", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "annexorien.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "antistate.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -89420,7 +89243,6 @@
     { "name": "dotcomtest02-single.azurewebsites.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "drastik.cz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "drivenbyperspective.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "droidee.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "dubaire.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "dusty.gr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "dutchassistancedogs.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -89891,7 +89713,6 @@
     { "name": "bramming-fysio.dk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "bricomium.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "brigade-electronics.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "brisbaneflamenco.com.au", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "brooklyntheborough.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "brunoamaral.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "buffalowdown.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -90151,7 +89972,6 @@
     { "name": "fortresslinux.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "freelance-webdesign.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "freiengrunder-hof.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "friendsinfilm.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "fritzkasten.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "fruxnux.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "fujiyakimono.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -90259,7 +90079,6 @@
     { "name": "ifashionable.info", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "iganesh.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ilawgix.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "ilovehoney.com.au", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "imoasis.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "imoasis.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "incomingfire.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -90461,7 +90280,6 @@
     { "name": "musicgivesmelife.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "mybigsaving.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "mybon.online", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "mybrisbanewebsite.com.au", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "mybsms.gr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "myconcorde.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "myersking.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -90927,7 +90745,6 @@
     { "name": "webauthnlogin.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "webcams4date.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "webforsales.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "webstudioswest.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "weeblez.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "wejdmark.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "werkenbijvanderventions.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -90982,9 +90799,6 @@
     { "name": "00228vip6.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "00228vip8.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "00228w.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "00228xx.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "00228y.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "00228zz.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "01zemi.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "0cdn.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "0x0.rip", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -91013,7 +90827,6 @@
     { "name": "37987a.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "37987c.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "37987d.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "37987e.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "37987f.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "505343.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "64i.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -91046,7 +90859,6 @@
     { "name": "9968565.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "9968606.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "9968676.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "9968678.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "9968787.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "9968808.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "9968989.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -91054,7 +90866,6 @@
     { "name": "9968live.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "9968love.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "9968xpj.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "a00228.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "aa00228.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "aarsunwoods.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "abelordbalagtas.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -91132,7 +90943,6 @@
     { "name": "avionschool.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "axisins.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "azadcyber.info", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "b77018.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "baixarvideosgratis.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "bajarvideosinstagram.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "baltimoreroofingservices.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -91270,7 +91080,6 @@
     { "name": "cumulus.photo", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "curl.tw", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "customerfocus.co.za", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "d00228.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "dada.is", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "daimonikos.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "dalvik.sh", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -91348,7 +91157,6 @@
     { "name": "e-global.pt", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "e-guardian.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "e-receta.cl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "e00228.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "e42.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "eamigo.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "easyserver.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -91470,7 +91278,6 @@
     { "name": "habr.ee", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "half.in.th", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "halkidikitransfers.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "handspun.co.za", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "hangar4.es", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "harlan.cc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "hasenmueller.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -91507,7 +91314,6 @@
     { "name": "iloveviral.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "imarukita.ninja", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "imisto.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "immigrative.ca", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "importacioneswily.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "incometaxbengaluru.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "incortum.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -91754,7 +91560,6 @@
     { "name": "nwtraders.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nyaa.am", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nyan.to", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "o00228.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ocalculator.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ok-ex.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "okremarketing.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -91847,7 +91652,6 @@
     { "name": "proris.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "pryan.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "pussplay.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "q00228.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "q1z.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "qcrx.cn", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "qd6kz.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -92065,7 +91869,6 @@
     { "name": "twfwd.email", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "twistgeschenken.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ty0m.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "u00228.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "uc4h.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "uk.kg", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ultimatedogstore.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -92193,7 +91996,6 @@
     { "name": "yukiblog.tw", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "yuy.info", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "yykb.jp", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "z00228.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "zakcutner.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "zango.com.au", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "zehnegira.ir", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -93271,7 +93073,6 @@
     { "name": "663365r.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "663365s.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "663365t.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "663365u.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "663365v.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "663365w.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "663365x.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -93652,7 +93453,6 @@
     { "name": "anex.us", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "aniwatch.me", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "apinsa.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "aranym.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "arcza.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "arcza.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "arn0.cc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -93867,7 +93667,6 @@
     { "name": "japlin.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "jdtangney.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "jeeptourpocos.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "jennastjohn.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "jenyak.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "jetular.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "jetular.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -94549,7 +94348,6 @@
     { "name": "3niu4.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "3niu7.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "3niu9.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "888am8.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "96658.la", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "98e.site", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "aboutpakistan.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -94841,7 +94639,6 @@
     { "name": "vipvoiceover.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "web-warrior.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "webest.pl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "wecomm.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "whaletail.ai", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "whywelive.me", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "williamk.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -95028,7 +94825,6 @@
     { "name": "bet365vip2020.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "bet3xx.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "bet3zz.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "bewegingdenk.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "beyondthepitch.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "bikealways.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "bingohalls.ca", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -95704,7 +95500,6 @@
     { "name": "nextvery.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nhadatpho.com.vn", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nibrasfashion.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "nickfarr.me", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nintendoreporters.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nkuxlogistics.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "noirpvp.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -99515,7 +99310,6 @@
     { "name": "hi-million.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "hidden.gq", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "hien.cf", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "higea.mx", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "high-school-atka.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "higheducation.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "highintegrity.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -102903,7 +102697,6 @@
     { "name": "sale-internet.cf", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "salegor.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "salery.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "saletodo.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "saleturs.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sallyangeli.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "salt-travel.cf", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -103385,7 +103178,6 @@
     { "name": "sosok.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sotnya.cf", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sotnyk.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "sougou.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "soulla.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "soulpowercoaching.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "soulsinner.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -104871,7 +104663,6 @@
     { "name": "wheelspin.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "whigfieldspain.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "whishart.cf", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "whistlingdog.media", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "white-info.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "white-noise.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "white-skull.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -105619,7 +105410,6 @@
     { "name": "infralicht.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "initiative-3d.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "insertface.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "interpurpose.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "inventaliber.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ioncubedecode.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ipitia.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -106014,7 +105804,6 @@
     { "name": "dd152.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ddw.pw", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "demarit.fi", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "den.taxi", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "dhammacitta.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "digino.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "dimigo.codes", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -106291,7 +106080,6 @@
     { "name": "playstationplus.es", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "poi-radary.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ponyar.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "popkultura.info.pl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "pornhib.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "pornhun.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "precisionlender.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -106970,7 +106758,6 @@
     { "name": "vutumusic.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "w3ctag.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "wardpieters.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "watchco.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "web-worker.cn", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "web.hr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "webhr.co", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -107199,7 +106986,6 @@
     { "name": "filipstaffa.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "fletcherdoescrime.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "fliptracker.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "flixcheck.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "forexnew.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "fortawesome.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "fotopianka.pl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -107346,7 +107132,6 @@
     { "name": "nasalucx.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nat.ac", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nawarasa.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "needsupport.us", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nemesisenterprises.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nemsurvey.dk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nerba.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -107554,7 +107339,6 @@
     { "name": "zottika.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "1masquepourtous.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "4cut.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "69shu.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "995ccc.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "99furnitureideasandtips.gq", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "a-colorful-life.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -107670,7 +107454,6 @@
     { "name": "designburners.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "destiney-arkaden.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "dg1298.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "digitail.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "diis.plus", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "diversegold.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "djfunkyju.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -108181,7 +107964,6 @@
     { "name": "bj-deal.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "blancamartinez.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "bodybuildingsupplementsexplained.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "bofoxdesign.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "boomerangworkouts.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "boophotobooth.pt", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "borderless.ro", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -109041,7 +108823,6 @@
     { "name": "mitsubishielectric-rce.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "mitsuvictory.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "mixedmenus.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "mixify.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "mm30019.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "mmcafe.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "modicollege.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -109138,7 +108919,6 @@
     { "name": "pooteng.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "pornline.sex", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "pornogam.porn", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "pornogo.sex", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "portfreezone.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "portusidades.com.pt", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "pp30019.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -109407,7 +109187,6 @@
     { "name": "americanhoneyproducers.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "anblife.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "antidopamine.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "anuncioacompanhantes.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "arc.run", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "argovpn.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "askingmonkey.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -110140,7 +109919,6 @@
     { "name": "ibtba.cc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "idehvector.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "iexpats.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "illumepgh.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "imperialism.rip", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "infantry.org.ua", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "infomalin.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -110486,7 +110264,6 @@
     { "name": "aksuplast.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "alexandbonnie.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "alexkushner.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "alojamientos-cuba.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ambicorpenergy.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "animestreamingfr.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "anthonys-landscaping.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -110572,10 +110349,8 @@
     { "name": "cyklistika24.cz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "d-solutions.com.au", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "d-va.cf", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "damacosmeticos.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "dancesafe.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "dannys.space", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "dealershipdrop.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "delta-wings.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "deltaworkssecurity.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "deltaworkssecurity.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -110648,7 +110423,6 @@
     { "name": "gsaauctions.gov", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "habana.ai", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "hdevent.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "hearted.co", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "hekoro.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "heliumbrno.cz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "herbamedicine.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -111235,7 +111009,6 @@
     { "name": "middle.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "midl.me", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "millennium-thisiswhoweare.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "millersprolandscape.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "miriamgamburd.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "missbitcoin.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "mitchkiah.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -111367,7 +111140,6 @@
     { "name": "shincastella.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "shoppingjin.pk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sieliakus.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "simplylocalhosting.com.au", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sincromyl.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sitinjau.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "slabstage.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -111490,7 +111262,6 @@
     { "name": "adkup.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "agviet88.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ajvco.com.hk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "alemorbel.es", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "alexandrepedrosa.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "aliamex.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "alltape.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -111536,7 +111307,6 @@
     { "name": "biocbdolio.it", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "blogsnote.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "brainstorm.social", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "brnrx.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "bruniano.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "buckanddoe.farm", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "buckdoeand.co", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -111561,9 +111331,7 @@
     { "name": "chacoonline.com.py", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "chacraexperimental.com.py", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "charlyclearsky.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "christiangaro.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "chronikdanceclub.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "chshuju.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "cities.mx", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "clapbacks.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "clarity-online.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -111716,7 +111484,6 @@
     { "name": "ifederalland.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "igloopreview.ca", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "igloosandbox.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "imhotx.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "index-of.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "infohas.ma", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "inmadesarrollos.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -111778,14 +111545,12 @@
     { "name": "lucidea.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "lumi.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "machin.email", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "maciej.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "madurasfollando.online", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "magaliff.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "magikbyte.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "maisonmere.group", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "maksatmoda.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "malcathatochen.co.il", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "maniadicane.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "manulife.ca", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "mapuut.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "marblenexus.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -111847,7 +111612,6 @@
     { "name": "nieuwebroek.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nieuwebroek.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nihon-mozartaikoukai.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "nocoffeetech.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "nonuplebroken.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "notrero13.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "npc-ts.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -112256,7 +112020,6 @@
     { "name": "finapi.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "fivetecnologia.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "fixverkaufen.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "floline.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "flooddoctorva.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "flow-serv.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "foodling.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -112300,7 +112063,6 @@
     { "name": "havebetterconversations.com.au", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "hazardhub.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "healthbrochures.info", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "hementaze.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "herbarex.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "herreriamauricio.com.ar", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "hesabcenter.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -112400,7 +112162,6 @@
     { "name": "leclicbazar.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "lemans.com.gt", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "letrissimas.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "letssolarize.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "levico.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "lifehacker.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "listing-here.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -112425,7 +112186,6 @@
     { "name": "mchughisle.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "meatfoods.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "metrourgentcarestl.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "mindspace.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "miscursosdebelleza.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "misterboddy.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "mit.gg", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -112485,7 +112245,6 @@
     { "name": "precisionstocks.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "privatefiles.host", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "proxybay.id", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "qinh.ren", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "qlulife.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "rad2share.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "rase.rocks", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -112524,7 +112283,6 @@
     { "name": "skedo.tech", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "skovbosburgerblog.dk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "slimmarkets.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "smokkelenken.no", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "smyleo.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "so-buff.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "so-commerce.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -112707,7 +112465,6 @@
     { "name": "buymore.cl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "bzhserv.ovh", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "c3sa.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "cacaogenomedb.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "cardinaleducation.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "casinomiami.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "caspi.org.il", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -112797,7 +112554,6 @@
     { "name": "fireplacesutah.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "flagstoneim.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "flibanserina.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "flippednotes.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "foodyshoody.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "foro.red", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "fotografoivanalmeida.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -112806,7 +112562,6 @@
     { "name": "gabe.download", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "gabrielgroup.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "gamecss.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "gamedaim.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ganardinerotrabajandoporinternet.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ganglioslinfaticos.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "garciacleaningsf.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -112843,7 +112598,6 @@
     { "name": "horustream.online", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "hothbricks.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "hpsears.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "hrjfeedstock.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "hundimiento.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "hyy.chat", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "i9elo.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -112885,7 +112639,6 @@
     { "name": "justgamblers.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "jwor.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "k-2a.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "ka0s.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "karasevm.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "kawaiihaven.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "kibersalon.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -113276,7 +113029,6 @@
     { "name": "autobraga.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "aventurasnorowa.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "balearicholidu.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "bambusushibar.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "batterijeshop.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "bekmekci.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "benleb.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -113827,6 +113579,609 @@
     { "name": "ziggletech.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "zoomative.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "zpider.cloud", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "01btc.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "1xbetapk.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "2002000.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "65book.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "90dayloans.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "aacc.ac", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "acerosfortuna.com.mx", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "acquadiparma.kr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "admin.place", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "admindaily.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "advania.info", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "adventurecorps.gq", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "afishablogs.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "afrohub.se", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "agenciadigitalbolivia.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "agroclefic.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ajadmin.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ajadmin.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "akitoy.mx", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "alancabrera.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "alexandrevicente.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "allbookstores.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "amevoice.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "androidmovile.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "anihonetwallpaper.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "answerit.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "apkteen.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "arcadia.com.ph", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "artexhibition.jp", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "artzphotography.ie", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "asatys-partners.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "asatys.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ashimwe.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "atope.art", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "audian.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "australianpropertyanalytics.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "axisdesignarchitects.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "axisdesignarchitects.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "babacloud.ddns.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "backlinksgenerator.in", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "badeand.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "badwi.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "baocaosuhp.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bathrobes.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bavaropuntacanahotels.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "beaconfed.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "begin-motorcycling.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "beliishko.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "beliyo.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "benoitchantre.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bessmertie.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bestecbdolie.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bestnetentcasino.info", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bestnetflowanalyzer.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "billbuddy.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bjpecas.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "blankpagebiz.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bluecoatnetflowsupport.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bmm.com.co", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "boggsinvesting.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "boomshadow.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "borisovv.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bot-socket.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "brandketers.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "brauer-augenoptik.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "brest-bel.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "brest-region.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bsa-dom.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "buildingmaterials.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "builtingym.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bumianoa.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "businesstravelmelbourne.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "buyasheep.tw", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "buypurenature.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "c-g-h.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cakes.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "capitalbay.news", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "captainkids.vn", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "careerprep101.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "carltonelitetravel.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cashewfinancing.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "catcoxx.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cathcartconsulting.com.au", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cbproject.co.za", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cherry-handmade.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cherryband.space", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "chipdenim.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "chrystofer.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ciadesuporte.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ciscoasanetflow.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cisconetflowleader.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cisconetflowpartners.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cisconetflowreporting.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cisconetflowsupport.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cleanclearwater.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "clearmaxx.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cliftonheritage.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cloud-hair.jp", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cloutcloset.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "conbida.co", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "confort-sante.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cookingaround.town", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "corinsamsterdam.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "correcthorse.pw", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "courseorbit.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "crazycliq.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "creativemysterymind.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "crimean-wines.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "crochetkim.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cyber.securitytactics.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cyberattackincidentresponse.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cyberdevelopment.es", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cybersecurityincidentresponse.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "d-tousei.co.jp", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dacha.today", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dalliard.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dangerscience.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "datch.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "davidband.com.au", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "davmimer-mercerie.ro", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "daxenexpress.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "daymarksi.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "decorpol-renovation.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "demxausa.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "digicoca.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "digitalcrisis.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "digitalimpactlab.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "digitalphoto.group", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "digitalphoto.tech", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ditfiorinicamargo.com.ar", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dneprovski.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dollarhero.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dolys.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "donarmany.online", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "doodlebeads.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "drillcalendar.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "driveral.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "droidtuto.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "drshadankabiri.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "drunkcalc.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dsg.lol", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dsg.red", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dsn-k.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dumpper.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dumpper.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dunedot.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dynamicathletes.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "e8bet.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ecosial.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "elektroprom.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "emmausmexico.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "empresa365.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "enabling.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "englishlatino.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "englishplus.in", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "enterprisenetworksecurity.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "eqab.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "erboristeria.milano.it", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ermeglio.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "erwindesigner.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "europeancuisine.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "evertus.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "evidecor.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "exarcheia.online", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "exotictravel.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "eyelash-navi.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "f1tv-streams.live", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "fabil.id", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "factoryalimentos.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "fale.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "fashion-swimwear.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "fcl.guru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "fifemedicalgroup.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "fire-places.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "fishtank.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "fl.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "flexiblenetflow.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "florianbouchet.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "flowercityflavor.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "flowreplicator.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "foair.me", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "forexwine.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "franchiseguide.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "francuskie.pl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "freenetflow.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "freethehustle.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ftx.tech", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "funreaktor.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "fureais.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "gabairealestate.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "galaksidot.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "gallery-kaze.jp", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "gas-boilers.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "gate.sc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "gearnify.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "gewel.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "gimnazija-skofjaloka.si", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "gizmolord.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "goblackwood.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "godortheworld.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "goldentech.ca", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "goldentechelectronics.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "goplaypal.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "goudronblanc.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "govnosite.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "grammarhouse.co.za", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "grappy.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "graycat.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "greenhous-technology.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "greenwiki.ca", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "grickle.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "griffinli.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "growthandrenewal.ca", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "gservera.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "gutetexte.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "h-maxton.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "h-var.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hab.dynu.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hairsalon-wish.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "haloknauf.si", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hamartrophy.cf", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hardhatengineer.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "harrisexteriors.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "helco.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "helpwaarbenik.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hkl-gruppe.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hoistsdirect.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "holyfamilychurch.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hongkongliberate.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hongkongwillwin.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hostreputation.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hrlive.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hrseoservice.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "humanrights.gov.au", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hundub.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hustlefreesolutions.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "i-port-voice.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "iconintegration.com.au", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "iesconsultores.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ilondres.es", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "incidentresponsesolution.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "incidentresponsesystem.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "incpak.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "infinifour.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "infomate360.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "infradot.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "infrapixel.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "infrarate.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "infrasend.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "infravoice.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "initblogger.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "internetbloger.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "investigatemalware.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "investigatingmalware.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "invitescafe.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ipaddressreputation.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ipfixcollector.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "iproductrepair.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "isthatarabic.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "isthisarabic.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "itero.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "j-maxton.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "jackandrascals.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "jainmantras.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "jawwad.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "jenkinsry.fi", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "jochem.pro", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "jonsey.co", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "jpcorriganlaw.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "jpn-ks.co.jp", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "jscsshtml.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "juliannorton.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "juliekristin.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "jurposluga.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "jybrid.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "kajirakuda.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "kameliya.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "kaminoweb.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "kangkang.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "kangkang.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "karakav.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "kemptechnologies.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "khadu12.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "kharkiv.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "koj.co", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "koylusemra.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "krk-gaming.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "kyle-s.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "lakeshorepoolsandtubs.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "lamai-crochets.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "landing-phillipferreira.herokuapp.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "law-zur.co.il", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "lcs.wiki", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "leaderinnetflow.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "legko-pohudet.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "lenii.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "lifetimestack.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "lindogdahl.dk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "linuxnetflow.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "lirapogrebnicentar.hr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "logger-phillipferreira.herokuapp.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "logoenvue.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "lojaxo.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "londoncarpetcleaningltd.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "longlivehongkong.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "lotustreasurechest.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "maalexi.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "macautocouture.gq", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "makemillion.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "malwaredevil.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "malwareincidentresponse.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mamaliefde.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "marcenariaembh.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "markacutt.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "marshallpeak.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mashina.world", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "matcha14.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mauditeboisson.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "maytinh.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mcoutinho.pt", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "medicalpeople.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "medicinae.solutions", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "medicinaesolutions.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "medicinaesolutions.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "medsister.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "meigetsuen1980.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mejofi.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mejofi.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mejofi.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mejofi.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mejofi.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "melaldesign.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mermakov.info", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "meteonederbetuwe.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mexicanfibers.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "michael-r.ddns.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mikesplumbingswfl.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "millersprofessionalsco.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "minimalistmenu.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "miyagi-ctr.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "miyagi-r.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mobiler-handel.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mobimsua.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "momi-chura.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "moneysavingpro.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "monitorbandwidth.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "monitoringanetwork.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "moparisthe.best", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "morandofora.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mori-cdc.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mountaincastle.store", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mowing-the-lawn.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mrcool.store", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mrtg.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mssa.jp", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mtsn3padang.sch.id", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "muchbetterthancash.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mutahar.me", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mydaymark.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mylittlegrocer.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mylittlegrocer.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mynimo.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mytribes.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "nab-services.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "najarkadeh.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "nakagawa-d.co.jp", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "nakagawa-s.jp", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "narcisqeshm.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "nash-dom.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "navalaulakh.com.au", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ncloud.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "nedap-source.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "needflare.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "netflowanalysissolutions.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "netflowsword.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "netflowtrafficanalysis.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "networkinternetmonitor.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "networkperformancemonitoring.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "networksecuritysolutions.info", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "networkthreatdetection.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "networkthreatprotection.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "networktrafficanalysis.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "networktrafficanalyzer.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "networktrafficmonitoring.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "nigelvm.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "nonstopjob.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "oasisbodycare.jp", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "onetwo-vermietung.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "online-divorce.co.za", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "oooh.events", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "oraculobrasil.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pagalofacil.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "painart.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "painresource.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "paketwisataliburan.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pakjefooi.be", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pakjefooi.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pakjefooi.email", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pakjefooi.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pakjefooi.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pakjefooi.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pakjefooi.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "palatte.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pathogen.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pcbexchange.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pedalr.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "penatizavarise.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "perlina.co.il", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pet-net.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "petbirds.gr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "phuketbeach.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "piajans.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "piavonpadberg.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pieceofcake.solutions", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pilot.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pizzaspaghettiemandolino.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "plixer.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ponnohaat.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "poppinspayroll.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "powderedcloud.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "powerpadel.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "previstart.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "prij.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "primalsurvivor.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "privacysvcs.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "proekt.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "profinvestment.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "profit24.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "progamermundo.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pronto-intervento-fognature.it", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "protesthongkong.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "purse-les.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pvideo.cz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pws-passau.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "quackquack.in", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "quichost.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "quiq.sh", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "rainbeaus.gq", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ramle.be", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ratihluhur.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ravenflight.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "raymondha.ca", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "raymondha.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "reading-assist.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "rebellion.scot", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "recolic.cc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "relatory.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "reliablepest.com.ph", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ridegravel.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "rise.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "riverbednetflowsupport.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "roottsquare.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "rrdtool.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ruthlavidente.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "safestreets.cf", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sakuracommunity.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "salati.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "samalova-chata.cz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sandboxstationchildcare.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sandramorrone.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sarah-secret.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sardegnarifiuti.it", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "satyamshivamsundaram.in", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "saul-eslake.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "savewithtrove.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sayurstock.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "scoresensei.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sdelatmrt.spb.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "secrets-marketing.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "security-courses.online", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "segurdatacr.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sekko2.jp", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sendai-cdc.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sendai-ctr.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sendai-rc.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sendai-sougou.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sendai-works.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sfltrends.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "shadowsproject.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "share-links.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sheptytsky.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "shinobayderm.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "shireyishunjian.pro", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "shooba.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "shop-cosmetic.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "shpiliak.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "shpiliak.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "shraymonks.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "shunmei-hari.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sigmacomputers.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "skreutz.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "skunkapeservers.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "smallwhitebear.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "smd-tlt.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "snazel.ae", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "snazel.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "snazel.ee", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "snazel.lu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "snazel.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sociologyk.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sohka.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "solace.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "solofi.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "souldigging.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "soundlight.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "souvenirsdebordeaux.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "spacesas.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "special-equipment.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "specialtyrigging.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sports-equipmen.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "spurto.in", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "stairmaster.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "steeve-legal-photographie.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "stewpolley.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "storin.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "stratreg.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "streamlineverify.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sup39.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "supergood.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "superis.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "surnet.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sylino.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "t-dent.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tacworldusa.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tafusu-support.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tappyshop.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "taylored.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tchatland.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tci-style.pl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tciit.pl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "team-toranomon.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "terraesencial.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "theachero.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "thebestshuk.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "thediabetesnews.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "thegoodheartedwoman.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "theracismforum.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "thinkscar.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "threatdetection.info", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tinkmai.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tobyx.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tohoku-fukushi.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tomchen.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "toolsu.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "topjeans.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "toptur.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "totoland6.cc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tousei.tokyo.jp", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tradingtag.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "transportfeverfrance.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "trapz.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "trasatsatelital.com.ar", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "travelcompany.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "travelhuge.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "travelunicorns.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tsp.gov", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tuvankinhdoanhonline.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "twilo.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "twistertech.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "txwriterstudio.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "uesc.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ulrike-sichert-schuster.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "unae.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "uniteinhealth.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "upturn.com.eg", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "usespetaculo.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "uwat.cc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "valledeloso.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ve3zsh.ca", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "vectops.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "viereview.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "visafruit.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "vitallispsy.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "vk-agent.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "vsevkusno.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "wade.gdn", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "water-filters.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "waterdamagehouston.us", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "wazito-fc.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "we6666.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "webisle.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "whatisinternetsecurity.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "whatisipfix.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "whatisnetflow.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "whoopee.my", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "wi-wi.co.jp", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "wiccansupplies.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "wine.com.my", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "wine.money", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "wk.pl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "wordpressfalcon.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "wpfy.me", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "x-embed.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "xn--laclas-n0a.ro", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "xn--ll-yka.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "xn--pckl4ji.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "xn--wp9ha.ws", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "xrobot.vn", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "yukieda.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "zastenchivost.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "zelt.in", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "zeroknowledge.me", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "zhangda.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     // END OF 1-YEAR BULK HSTS ENTRIES
 
     // Only eTLD+1 domains can be submitted automatically to hstspreload.org,
@@ -114404,6 +114759,95 @@
     { "name": "briellenj.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
     { "name": "blainecosheriff-ok.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
     { "name": "almaarkansas.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "albanyca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "apachecountyaz.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "applevalleyca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "bentoncountyia.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "birminghamal911.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "bowmar.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "bullvalleyil.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "carsonca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "casscountyia.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "centervilleutah.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofcarsonca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofgigharborwa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofguttenbergia.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityoflakegeneva.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofpinebluff-ar.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "clintonoh.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "clintonohfire.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "coloradosos.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "corcoranmn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "coronavirusfortbend.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cranstonri.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "crestwoodky.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "custercounty-co.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "eaglecountyco.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "eastprovidenceri.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "gaoinnovation.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "gaoinnovationlab.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "gaoinnovations.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "gcwatx.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "gigharborwa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "glenbeulahwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "grandviewheights.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "grundycountyiowa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "gtcountymi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "gulfcoastwaterauthoritytx.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "harahanla.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "jacksoncountyfl.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "jeffersonvillepdin.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "jfklibrary.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "jonescountyiowa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "jonescountyiowaelections.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lakeclerkfl.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lakecountyclerkfl.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lakevotes.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lincolncountysheriffok.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lpcd-lafla.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lpcdops-lafla.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lucascountyohiovotes.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "maconcountymo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "mahaskacountyia.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "manitouspringsco.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "marinettecountywi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "marioncountyohio.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "mohave.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "newtoncountymo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "northoaksmn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "oceanviewde.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "okmulgeecounty.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "olmstedcounty.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "pelhamalrecreation.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "pelhamlibraryal.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "ponca-nsn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "popecountyar.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "portorchardwa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "portsmouthohpd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "reddingrancheria-nsn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "redrivernm.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "reentry.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "risecstate.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "rosemountmn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "santarosaca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "scvotes.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "seminolecountyoklahoma.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "sheriffwashingtoncountymaine.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "texashealthtrace.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "thomastonmaine.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "tombeantx.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townoftaycheedahwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "trentonoh.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "tryonnc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "unioncountyncelections.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "vanburencountyiowa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "vayavotarcolorado.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "virginiaabc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "votebrevard.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "waeldertexas.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "washingtoncountyar.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "worthcountyiowa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "zerodeathsmd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
     // END OF ETLD-OWNER REQUESTED ENTRIES
 
     // To avoid trailing comma changes from showing up in diffs, we place a
diff --git a/net/quic/mock_crypto_client_stream.cc b/net/quic/mock_crypto_client_stream.cc
index c2ef794..0d68be4 100644
--- a/net/quic/mock_crypto_client_stream.cc
+++ b/net/quic/mock_crypto_client_stream.cc
@@ -226,6 +226,11 @@
   return handshake_confirmed_;
 }
 
+bool MockCryptoClientStream::EarlyDataAccepted() const {
+  // This value is only used for logging. The return value doesn't matter.
+  return false;
+}
+
 const QuicCryptoNegotiatedParameters&
 MockCryptoClientStream::crypto_negotiated_params() const {
   return *crypto_negotiated_params_;
diff --git a/net/quic/mock_crypto_client_stream.h b/net/quic/mock_crypto_client_stream.h
index 038ec3f..5c86037 100644
--- a/net/quic/mock_crypto_client_stream.h
+++ b/net/quic/mock_crypto_client_stream.h
@@ -66,6 +66,7 @@
       const override;
   quic::CryptoMessageParser* crypto_message_parser() override;
   void OnOneRttPacketAcknowledged() override;
+  bool EarlyDataAccepted() const override;
 
   // Notify session that 1-RTT key is available.
   void NotifySessionOneRttKeyAvailable();
diff --git a/net/quic/quic_chromium_client_session.cc b/net/quic/quic_chromium_client_session.cc
index 05026d58..7d94b48 100644
--- a/net/quic/quic_chromium_client_session.cc
+++ b/net/quic/quic_chromium_client_session.cc
@@ -230,6 +230,13 @@
   NUM_HANDSHAKE_STATES = 4
 };
 
+enum class ZeroRttState {
+  kAttemptedAndSucceeded = 0,
+  kAttemptedAndRejected = 1,
+  kNotAttempted = 2,
+  kMaxValue = kNotAttempted,
+};
+
 void RecordHandshakeState(HandshakeState state) {
   UMA_HISTOGRAM_ENUMERATION("Net.QuicHandshakeState", state,
                             NUM_HANDSHAKE_STATES);
@@ -847,7 +854,8 @@
       ignore_read_error_(false),
       headers_include_h2_stream_dependency_(
           headers_include_h2_stream_dependency),
-      max_allowed_push_id_(max_allowed_push_id) {
+      max_allowed_push_id_(max_allowed_push_id),
+      attempted_zero_rtt_(false) {
   // Make sure connection migration and goaway on path degrading are not turned
   // on at the same time.
   DCHECK(!(migrate_session_early_v2_ && go_away_on_path_degrading_));
@@ -1076,16 +1084,6 @@
   quic::QuicSpdySession::UpdateStreamPriority(id, new_precedence);
 }
 
-void QuicChromiumClientSession::OnStreamFrame(
-    const quic::QuicStreamFrame& frame) {
-  // Record total number of stream frames.
-  UMA_HISTOGRAM_COUNTS_1M("Net.QuicNumStreamFramesInPacket", 1);
-
-  // Record number of frames per stream in packet.
-  UMA_HISTOGRAM_COUNTS_1M("Net.QuicNumStreamFramesPerStreamInPacket", 1);
-
-  return quic::QuicSpdySession::OnStreamFrame(frame);
-}
 
 void QuicChromiumClientSession::AddHandle(Handle* handle) {
   if (going_away_) {
@@ -1594,7 +1592,10 @@
   }
   if (level == quic::ENCRYPTION_FORWARD_SECURE) {
     OnCryptoHandshakeComplete();
+    LogZeroRttStats();
   }
+  if (level == quic::ENCRYPTION_ZERO_RTT)
+    attempted_zero_rtt_ = true;
   quic::QuicSpdySession::SetDefaultEncryptionLevel(level);
 }
 
@@ -1607,9 +1608,35 @@
     std::move(callback_).Run(OK);
   }
   OnCryptoHandshakeComplete();
+  LogZeroRttStats();
   quic::QuicSpdySession::OnOneRttKeysAvailable();
 }
 
+void QuicChromiumClientSession::OnNewEncryptionKeyAvailable(
+    quic::EncryptionLevel level,
+    std::unique_ptr<quic::QuicEncrypter> encrypter) {
+  if (level == quic::ENCRYPTION_ZERO_RTT)
+    attempted_zero_rtt_ = true;
+  QuicSpdySession::OnNewEncryptionKeyAvailable(level, std::move(encrypter));
+}
+
+void QuicChromiumClientSession::LogZeroRttStats() {
+  DCHECK(OneRttKeysAvailable());
+
+  ZeroRttState state;
+
+  if (attempted_zero_rtt_) {
+    if (crypto_stream_->EarlyDataAccepted()) {
+      state = ZeroRttState::kAttemptedAndSucceeded;
+    } else {
+      state = ZeroRttState::kAttemptedAndRejected;
+    }
+  } else {
+    state = ZeroRttState::kNotAttempted;
+  }
+  UMA_HISTOGRAM_ENUMERATION("Net.QuicSession.ZeroRttState", state);
+}
+
 void QuicChromiumClientSession::OnCryptoHandshakeMessageSent(
     const quic::CryptoHandshakeMessage& message) {
   logger_->OnCryptoHandshakeMessageSent(message);
@@ -1635,11 +1662,6 @@
       frame.error_code == quic::QUIC_ERROR_MIGRATING_PORT;
 }
 
-void QuicChromiumClientSession::OnRstStream(
-    const quic::QuicRstStreamFrame& frame) {
-  quic::QuicSession::OnRstStream(frame);
-}
-
 void QuicChromiumClientSession::OnConnectionClosed(
     const quic::QuicConnectionCloseFrame& frame,
     quic::ConnectionCloseSource source) {
diff --git a/net/quic/quic_chromium_client_session.h b/net/quic/quic_chromium_client_session.h
index 333ebf5..aff354aa 100644
--- a/net/quic/quic_chromium_client_session.h
+++ b/net/quic/quic_chromium_client_session.h
@@ -517,19 +517,20 @@
       const spdy::SpdyStreamPrecedence& new_precedence) override;
 
   // quic::QuicSession methods:
-  void OnStreamFrame(const quic::QuicStreamFrame& frame) override;
   QuicChromiumClientStream* CreateOutgoingBidirectionalStream() override;
   QuicChromiumClientStream* CreateOutgoingUnidirectionalStream() override;
   const quic::QuicCryptoClientStream* GetCryptoStream() const override;
   quic::QuicCryptoClientStream* GetMutableCryptoStream() override;
   void SetDefaultEncryptionLevel(quic::EncryptionLevel level) override;
   void OnOneRttKeysAvailable() override;
+  void OnNewEncryptionKeyAvailable(
+      quic::EncryptionLevel level,
+      std::unique_ptr<quic::QuicEncrypter> encrypter) override;
   void OnCryptoHandshakeMessageSent(
       const quic::CryptoHandshakeMessage& message) override;
   void OnCryptoHandshakeMessageReceived(
       const quic::CryptoHandshakeMessage& message) override;
   void OnGoAway(const quic::QuicGoAwayFrame& frame) override;
-  void OnRstStream(const quic::QuicRstStreamFrame& frame) override;
   void OnCanCreateNewOutgoingStream(bool unidirectional) override;
   bool ValidateStatelessReset(
       const quic::QuicSocketAddress& self_address,
@@ -807,6 +808,8 @@
   // Called when default encryption level switches to forward secure.
   void OnCryptoHandshakeComplete();
 
+  void LogZeroRttStats();
+
   QuicSessionKey session_key_;
   bool require_confirmation_;
   bool migrate_session_early_v2_;
@@ -904,6 +907,8 @@
 
   quic::QuicStreamId max_allowed_push_id_;
 
+  bool attempted_zero_rtt_;
+
   base::WeakPtrFactory<QuicChromiumClientSession> weak_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(QuicChromiumClientSession);
diff --git a/net/url_request/url_request.cc b/net/url_request/url_request.cc
index 5a7eeab..b09e02a1 100644
--- a/net/url_request/url_request.cc
+++ b/net/url_request/url_request.cc
@@ -403,7 +403,8 @@
   maybe_sent_cookies_ = std::move(cookies);
 }
 
-void URLRequest::set_maybe_stored_cookies(CookieAndLineStatusList cookies) {
+void URLRequest::set_maybe_stored_cookies(
+    CookieAndLineAccessResultList cookies) {
   maybe_stored_cookies_ = std::move(cookies);
 }
 
diff --git a/net/url_request/url_request.h b/net/url_request/url_request.h
index 3a94b8c..0a3d8dd 100644
--- a/net/url_request/url_request.h
+++ b/net/url_request/url_request.h
@@ -560,7 +560,7 @@
   bool disable_secure_dns() { return disable_secure_dns_; }
 
   void set_maybe_sent_cookies(CookieAccessResultList cookies);
-  void set_maybe_stored_cookies(CookieAndLineStatusList cookies);
+  void set_maybe_stored_cookies(CookieAndLineAccessResultList cookies);
 
   // These lists contain a list of cookies that are associated with the given
   // request, both those that were sent and accepted, and those that were
@@ -577,7 +577,7 @@
     return maybe_sent_cookies_;
   }
   // Populated after the response headers are received.
-  const CookieAndLineStatusList& maybe_stored_cookies() const {
+  const CookieAndLineAccessResultList& maybe_stored_cookies() const {
     return maybe_stored_cookies_;
   }
 
@@ -898,7 +898,7 @@
   bool disable_secure_dns_;
 
   CookieAccessResultList maybe_sent_cookies_;
-  CookieAndLineStatusList maybe_stored_cookies_;
+  CookieAndLineAccessResultList maybe_stored_cookies_;
 
 #if BUILDFLAG(ENABLE_REPORTING)
   int reporting_upload_depth_;
diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc
index efde22b..c69bc7c9 100644
--- a/net/url_request/url_request_http_job.cc
+++ b/net/url_request/url_request_http_job.cc
@@ -192,7 +192,7 @@
 }
 
 void MarkSameSiteCompatPairs(
-    std::vector<net::CookieAndLineWithStatus>& cookie_list,
+    std::vector<net::CookieAndLineWithAccessResult>& cookie_list,
     const net::CookieOptions& options) {
   for (size_t i = 0; i < cookie_list.size() - 1; ++i) {
     if (!cookie_list[i].cookie.has_value())
@@ -203,9 +203,9 @@
         continue;
       const net::CanonicalCookie& c2 = cookie_list[j].cookie.value();
       if (net::cookie_util::IsSameSiteCompatPair(c1, c2, options)) {
-        cookie_list[i].status.AddWarningReason(
+        cookie_list[i].access_result.status.AddWarningReason(
             net::CookieInclusionStatus::WARN_SAMESITE_COMPAT_PAIR);
-        cookie_list[j].status.AddWarningReason(
+        cookie_list[j].access_result.status.AddWarningReason(
             net::CookieInclusionStatus::WARN_SAMESITE_COMPAT_PAIR);
       }
     }
@@ -380,9 +380,9 @@
   ProcessStrictTransportSecurityHeader();
   ProcessExpectCTHeader();
 
-  // Clear |set_cookie_status_list_| after any processing in case
+  // Clear |set_cookie_access_result_list_| after any processing in case
   // SaveCookiesAndNotifyHeadersComplete is called again.
-  request_->set_maybe_stored_cookies(std::move(set_cookie_status_list_));
+  request_->set_maybe_stored_cookies(std::move(set_cookie_access_result_list_));
 
   // The HTTP transaction may be restarted several times for the purposes
   // of sending authorization information. Each time it restarts, we get
@@ -698,7 +698,7 @@
 }
 
 void URLRequestHttpJob::SaveCookiesAndNotifyHeadersComplete(int result) {
-  DCHECK(set_cookie_status_list_.empty());
+  DCHECK(set_cookie_access_result_list_.empty());
   DCHECK_EQ(0, num_cookie_lines_left_);
 
   // End of the call started in OnStartCompleted.
@@ -791,10 +791,11 @@
   num_cookie_lines_left_--;
 
   if (num_cookie_lines_left_ == 0) {
-    // Mark the CookieInclusionStatuses of items in |set_cookie_status_list_| if
-    // they are part of a presumed SameSite compatibility pair.
-    if (ShouldMarkSameSiteCompatPairs(set_cookie_status_list_, options))
-      MarkSameSiteCompatPairs(set_cookie_status_list_, options);
+    // Mark the CookieInclusionStatuses of items in
+    // |set_cookie_access_result_list_| if they are part of a presumed SameSite
+    // compatibility pair.
+    if (ShouldMarkSameSiteCompatPairs(set_cookie_access_result_list_, options))
+      MarkSameSiteCompatPairs(set_cookie_access_result_list_, options);
 
     NotifyHeadersComplete();
   }
@@ -816,19 +817,20 @@
                                        access_result.status, capture_mode);
                                  });
   }
-  set_cookie_status_list_.emplace_back(
-      std::move(cookie), std::move(cookie_string), access_result.status);
+  set_cookie_access_result_list_.emplace_back(
+      std::move(cookie), std::move(cookie_string), access_result);
 
   num_cookie_lines_left_--;
 
-  // If all the cookie lines have been handled, |set_cookie_status_list_| now
-  // reflects the result of all Set-Cookie lines, and the request can be
+  // If all the cookie lines have been handled, |set_cookie_access_result_list_|
+  // now reflects the result of all Set-Cookie lines, and the request can be
   // continued.
   if (num_cookie_lines_left_ == 0) {
-    // Mark the CookieInclusionStatuses of items in |set_cookie_status_list_| if
-    // they are part of a presumed SameSite compatibility pair.
-    if (ShouldMarkSameSiteCompatPairs(set_cookie_status_list_, options))
-      MarkSameSiteCompatPairs(set_cookie_status_list_, options);
+    // Mark the CookieInclusionStatuses of items in
+    // |set_cookie_access_result_list_| if they are part of a presumed SameSite
+    // compatibility pair.
+    if (ShouldMarkSameSiteCompatPairs(set_cookie_access_result_list_, options))
+      MarkSameSiteCompatPairs(set_cookie_access_result_list_, options);
 
     NotifyHeadersComplete();
   }
diff --git a/net/url_request/url_request_http_job.h b/net/url_request/url_request_http_job.h
index 6c74871..8ddb724 100644
--- a/net/url_request/url_request_http_job.h
+++ b/net/url_request/url_request_http_job.h
@@ -181,7 +181,7 @@
                          std::string cookie_string,
                          CookieAccessResult access_result);
   int num_cookie_lines_left_;
-  CookieAndLineStatusList set_cookie_status_list_;
+  CookieAndLineAccessResultList set_cookie_access_result_list_;
 
   // Some servers send the body compressed, but specify the content length as
   // the uncompressed size. If this is the case, we return true in order
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
index bb813f2..2357f06 100644
--- a/net/url_request/url_request_unittest.cc
+++ b/net/url_request/url_request_unittest.cc
@@ -6868,12 +6868,14 @@
     EXPECT_EQ("not_stored_cookie",
               req->maybe_stored_cookies()[0].cookie->Name());
     EXPECT_TRUE(req->maybe_stored_cookies()[0]
-                    .status.HasExactlyExclusionReasonsForTesting(
+                    .access_result.status.HasExactlyExclusionReasonsForTesting(
                         {CookieInclusionStatus::EXCLUDE_USER_PREFERENCES}));
     EXPECT_EQ("stored_cookie", req->maybe_stored_cookies()[1].cookie->Name());
-    EXPECT_TRUE(req->maybe_stored_cookies()[1].status.IsInclude());
+    EXPECT_TRUE(
+        req->maybe_stored_cookies()[1].access_result.status.IsInclude());
     EXPECT_EQ("stored_cookie", req->maybe_stored_cookies()[1].cookie->Name());
-    EXPECT_TRUE(req->maybe_stored_cookies()[2].status.IsInclude());
+    EXPECT_TRUE(
+        req->maybe_stored_cookies()[2].access_result.status.IsInclude());
     EXPECT_EQ("path_cookie", req->maybe_stored_cookies()[2].cookie->Name());
     auto entries =
         net_log.GetEntriesWithType(NetLogEventType::COOKIE_INCLUSION_STATUS);
@@ -7088,7 +7090,7 @@
         const std::string& cookie_name = cookie.cookie->Name();
         auto it = test.expected_cookies.find(cookie_name);
         ASSERT_NE(test.expected_cookies.end(), it);
-        bool included = cookie.status.IsInclude();
+        bool included = cookie.access_result.status.IsInclude();
         EXPECT_EQ(it->second, included);
 
         std::vector<CookieInclusionStatus::ExclusionReason> exclusions;
@@ -7103,8 +7105,11 @@
           warnings.push_back(CookieInclusionStatus::WARN_SAMESITE_COMPAT_PAIR);
         }
         EXPECT_TRUE(
-            cookie.status.HasExactlyExclusionReasonsForTesting(exclusions));
-        EXPECT_TRUE(cookie.status.HasExactlyWarningReasonsForTesting(warnings));
+            cookie.access_result.status.HasExactlyExclusionReasonsForTesting(
+                exclusions));
+        EXPECT_TRUE(
+            cookie.access_result.status.HasExactlyWarningReasonsForTesting(
+                warnings));
       }
     }
     {
@@ -7124,7 +7129,7 @@
         EXPECT_TRUE(
             base::Contains(test.expected_cookies, cookie.cookie->Name()));
         // Cookie was included and there are no warnings.
-        EXPECT_EQ(CookieInclusionStatus(), cookie.status);
+        EXPECT_EQ(CookieInclusionStatus(), cookie.access_result.status);
       }
     }
     {
@@ -7238,20 +7243,21 @@
     // It doesn't pick up the EXCLUDE_UNSPECIFIED_TREATED_AS_LAX because it
     // doesn't even make it to the cookie store (it is filtered out beforehand).
     EXPECT_TRUE(req->maybe_stored_cookies()[0]
-                    .status.HasExactlyExclusionReasonsForTesting(
+                    .access_result.status.HasExactlyExclusionReasonsForTesting(
                         {CookieInclusionStatus::EXCLUDE_USER_PREFERENCES}));
-    EXPECT_FALSE(req->maybe_stored_cookies()[0].status.ShouldWarn());
+    EXPECT_FALSE(
+        req->maybe_stored_cookies()[0].access_result.status.ShouldWarn());
 
     // Cookie that would be included had it not been for the new SameSite rules
     // is warned about.
     EXPECT_EQ("unspecifiedsamesite",
               req->maybe_stored_cookies()[1].cookie->Name());
     EXPECT_TRUE(req->maybe_stored_cookies()[1]
-                    .status.HasExactlyExclusionReasonsForTesting(
+                    .access_result.status.HasExactlyExclusionReasonsForTesting(
                         {CookieInclusionStatus::
                              EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX}));
     EXPECT_TRUE(req->maybe_stored_cookies()[1]
-                    .status.HasExactlyWarningReasonsForTesting(
+                    .access_result.status.HasExactlyWarningReasonsForTesting(
                         {CookieInclusionStatus::
                              WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT}));
 
@@ -7259,11 +7265,12 @@
     // about.
     EXPECT_EQ("invalidsecure", req->maybe_stored_cookies()[2].cookie->Name());
     EXPECT_TRUE(req->maybe_stored_cookies()[2]
-                    .status.HasExactlyExclusionReasonsForTesting(
+                    .access_result.status.HasExactlyExclusionReasonsForTesting(
                         {CookieInclusionStatus::EXCLUDE_SECURE_ONLY,
                          CookieInclusionStatus::
                              EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX}));
-    EXPECT_FALSE(req->maybe_stored_cookies()[2].status.ShouldWarn());
+    EXPECT_FALSE(
+        req->maybe_stored_cookies()[2].access_result.status.ShouldWarn());
   }
 
   // Get cookies (blocked by user preference)
@@ -7386,7 +7393,7 @@
   delegate.RunUntilAuthRequired();
   ASSERT_EQ(1u, request->maybe_stored_cookies().size());
   EXPECT_TRUE(request->maybe_stored_cookies()[0]
-                  .status.HasExactlyExclusionReasonsForTesting(
+                  .access_result.status.HasExactlyExclusionReasonsForTesting(
                       {net::CookieInclusionStatus::EXCLUDE_USER_PREFERENCES}));
   EXPECT_EQ("got_challenged=true",
             request->maybe_stored_cookies()[0].cookie_string);
@@ -7428,7 +7435,7 @@
     EXPECT_TRUE(
         request->maybe_stored_cookies()
             .front()
-            .status.HasExactlyExclusionReasonsForTesting(
+            .access_result.status.HasExactlyExclusionReasonsForTesting(
                 {net::CookieInclusionStatus::EXCLUDE_USER_PREFERENCES}));
 
     // Now check the second round trip
@@ -7832,7 +7839,7 @@
     EXPECT_TRUE(
         request->maybe_stored_cookies()
             .front()
-            .status.HasExactlyExclusionReasonsForTesting(
+            .access_result.status.HasExactlyExclusionReasonsForTesting(
                 {net::CookieInclusionStatus::EXCLUDE_USER_PREFERENCES}));
 
     // Check maybe_stored_cookies on second round trip (and clearing from the
@@ -7855,7 +7862,7 @@
     EXPECT_TRUE(
         request->maybe_stored_cookies()
             .front()
-            .status.HasExactlyExclusionReasonsForTesting(
+            .access_result.status.HasExactlyExclusionReasonsForTesting(
                 {net::CookieInclusionStatus::EXCLUDE_USER_PREFERENCES}));
   }
 
diff --git a/pdf/BUILD.gn b/pdf/BUILD.gn
index 60c0a80..555d46a 100644
--- a/pdf/BUILD.gn
+++ b/pdf/BUILD.gn
@@ -54,9 +54,12 @@
       "paint_manager.h",
       "pdf.cc",
       "pdf_engine.h",
-      "pdf_ppapi.cc",
+      "pdf_init.cc",
+      "pdf_init.h",
       "pdf_transform.cc",
       "pdf_transform.h",
+      "ppapi_migration/bitmap.cc",
+      "ppapi_migration/bitmap.h",
       "ppapi_migration/callback.cc",
       "ppapi_migration/callback.h",
       "ppapi_migration/geometry_conversions.cc",
@@ -74,14 +77,12 @@
 
     configs += [ ":pdf_common_config" ]
 
-    public = [
-      "pdf.h",
-      "pdf_ppapi.h",
-    ]
+    public = [ "pdf.h" ]
 
     friend = [
       ":pdf_unittests",
       ":pdf_test_utils",
+      ":pdf_ppapi",
     ]
 
     deps = [
@@ -91,6 +92,7 @@
       "//net",
       "//ppapi/cpp:objects",
       "//ppapi/cpp/private:internal_module",
+      "//skia",
       "//ui/base",
       "//ui/gfx/range",
     ]
@@ -140,6 +142,25 @@
     }
   }
 
+  component("pdf_ppapi") {
+    sources = [ "pdf_ppapi.cc" ]
+
+    configs += [ ":pdf_common_config" ]
+
+    defines = [ "IS_PDF_PPAPI_IMPL" ]
+
+    public = [ "pdf_ppapi.h" ]
+
+    deps = [
+      ":pdf",
+      "//base",
+      "//ppapi/cpp:objects",
+      "//ppapi/cpp/private:internal_module",
+      "//skia",
+      "//v8",
+    ]
+  }
+
   source_set("features") {
     sources = [ "pdf_features.cc" ]
 
@@ -203,6 +224,7 @@
       "//ppapi/c",
       "//ppapi/cpp:objects",
       "//printing",
+      "//skia",
       "//testing/gmock",
       "//testing/gtest",
       "//ui/gfx/geometry",
@@ -242,4 +264,6 @@
   }
   group("pdf") {
   }
+  group("pdf_ppapi") {
+  }
 }
diff --git a/pdf/DEPS b/pdf/DEPS
index 253bb53..038d46a 100644
--- a/pdf/DEPS
+++ b/pdf/DEPS
@@ -2,6 +2,7 @@
   "+net",
   "+ppapi",
   "+printing/units.h",
+  "+third_party/skia/include/core",
   "+ui/base/window_open_disposition.h",
   "+ui/events/keycodes/keyboard_codes.h",
   "+ui/gfx/geometry",
diff --git a/pdf/draw_utils/shadow.cc b/pdf/draw_utils/shadow.cc
index 6e4f4e71..df45172 100644
--- a/pdf/draw_utils/shadow.cc
+++ b/pdf/draw_utils/shadow.cc
@@ -10,8 +10,8 @@
 #include <algorithm>
 
 #include "base/check_op.h"
-#include "ppapi/cpp/image_data.h"
 #include "ppapi/cpp/rect.h"
+#include "third_party/skia/include/core/SkBitmap.h"
 
 namespace chrome_pdf {
 namespace draw_utils {
@@ -110,7 +110,7 @@
 
 namespace {
 
-void PaintShadow(pp::ImageData* image,
+void PaintShadow(SkBitmap& image,
                  const pp::Rect& clip_rc,
                  const pp::Rect& shadow_rc,
                  const ShadowMatrix& matrix) {
@@ -125,7 +125,7 @@
                                   depth - shadow_rc.right() + x);
       int32_t matrix_y = std::max(depth + shadow_rc.y() - y - 1,
                                   depth - shadow_rc.bottom() + y);
-      uint32_t* pixel = image->GetAddr32(pp::Point(x, y));
+      uint32_t* pixel = image.getAddr32(x, y);
 
       if (matrix_x < 0)
         matrix_x = 0;
@@ -144,7 +144,7 @@
 
 }  // namespace
 
-void DrawShadow(pp::ImageData* image,
+void DrawShadow(SkBitmap& image,
                 const pp::Rect& shadow_rc,
                 const pp::Rect& object_rc,
                 const pp::Rect& clip_rc,
diff --git a/pdf/draw_utils/shadow.h b/pdf/draw_utils/shadow.h
index 91f0b168ab..31fbe6298 100644
--- a/pdf/draw_utils/shadow.h
+++ b/pdf/draw_utils/shadow.h
@@ -9,8 +9,9 @@
 
 #include <vector>
 
+class SkBitmap;
+
 namespace pp {
-class ImageData;
 class Rect;
 }  // namespace pp
 
@@ -46,7 +47,7 @@
 // shadow_rc - rectangle occupied by shadow
 // object_rc - rectangle that drops the shadow
 // clip_rc - clipping region
-void DrawShadow(pp::ImageData* image,
+void DrawShadow(SkBitmap& image,
                 const pp::Rect& shadow_rc,
                 const pp::Rect& object_rc,
                 const pp::Rect& clip_rc,
diff --git a/pdf/out_of_process_instance.cc b/pdf/out_of_process_instance.cc
index a3d14b2..f66553f 100644
--- a/pdf/out_of_process_instance.cc
+++ b/pdf/out_of_process_instance.cc
@@ -30,8 +30,8 @@
 #include "pdf/accessibility.h"
 #include "pdf/document_layout.h"
 #include "pdf/document_metadata.h"
-#include "pdf/pdf.h"
 #include "pdf/pdf_features.h"
+#include "pdf/ppapi_migration/bitmap.h"
 #include "ppapi/c/dev/ppb_cursor_control_dev.h"
 #include "ppapi/c/pp_errors.h"
 #include "ppapi/c/private/ppb_pdf.h"
@@ -40,6 +40,7 @@
 #include "ppapi/cpp/dev/memory_dev.h"
 #include "ppapi/cpp/dev/text_input_dev.h"
 #include "ppapi/cpp/dev/url_util_dev.h"
+#include "ppapi/cpp/image_data.h"
 #include "ppapi/cpp/input_event.h"
 #include "ppapi/cpp/module.h"
 #include "ppapi/cpp/point.h"
@@ -671,6 +672,8 @@
     if (new_image_data_size != image_data_.size()) {
       image_data_ = pp::ImageData(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
                                   new_image_data_size, false);
+      skia_image_data_ =
+          SkBitmapFromPPImageData(std::make_unique<pp::ImageData>(image_data_));
       first_paint_ = true;
     }
 
@@ -978,7 +981,7 @@
 
       std::vector<pp::Rect> pdf_ready;
       std::vector<pp::Rect> pdf_pending;
-      engine_->Paint(pdf_rect, &image_data_, &pdf_ready, &pdf_pending);
+      engine_->Paint(pdf_rect, skia_image_data_, pdf_ready, pdf_pending);
       for (auto& ready_rect : pdf_ready) {
         ready_rect.Offset(available_area_.point());
         ready->push_back(
diff --git a/pdf/out_of_process_instance.h b/pdf/out_of_process_instance.h
index 413ee84..8b9a343 100644
--- a/pdf/out_of_process_instance.h
+++ b/pdf/out_of_process_instance.h
@@ -27,6 +27,7 @@
 #include "ppapi/cpp/private/find_private.h"
 #include "ppapi/cpp/url_loader.h"
 #include "ppapi/utility/completion_callback_factory.h"
+#include "third_party/skia/include/core/SkBitmap.h"
 
 namespace pp {
 class TextInput_Dev;
@@ -303,6 +304,8 @@
   void InvalidateAfterPaintDone(int32_t /*unused_but_required*/);
 
   pp::ImageData image_data_;
+  SkBitmap skia_image_data_;  // Must be kept in sync with |image_data_|.
+
   // Used when the plugin is embedded in a page and we have to create the loader
   // ourself.
   pp::URLLoader embed_loader_;
diff --git a/pdf/pdf.cc b/pdf/pdf.cc
index 9aef696..36d6ed6 100644
--- a/pdf/pdf.cc
+++ b/pdf/pdf.cc
@@ -8,9 +8,8 @@
 
 #include <utility>
 
-#include "base/macros.h"
-#include "pdf/out_of_process_instance.h"
-#include "pdf/pdf_ppapi.h"
+#include "pdf/pdf_engine.h"
+#include "pdf/pdf_init.h"
 
 namespace chrome_pdf {
 
@@ -22,13 +21,14 @@
     if (!IsSDKInitializedViaPepper())
       InitializeSDK(enable_v8);
   }
+
+  ScopedSdkInitializer(const ScopedSdkInitializer&) = delete;
+  ScopedSdkInitializer& operator=(const ScopedSdkInitializer&) = delete;
+
   ~ScopedSdkInitializer() {
     if (!IsSDKInitializedViaPepper())
       ShutdownSDK();
   }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ScopedSdkInitializer);
 };
 
 }  // namespace
diff --git a/pdf/pdf_engine.h b/pdf/pdf_engine.h
index dc91c36..b15c730 100644
--- a/pdf/pdf_engine.h
+++ b/pdf/pdf_engine.h
@@ -23,7 +23,6 @@
 #include "ppapi/c/dev/ppp_printing_dev.h"
 #include "ppapi/c/ppb_input_event.h"
 #include "ppapi/cpp/completion_callback.h"
-#include "ppapi/cpp/image_data.h"
 #include "ppapi/cpp/private/pdf.h"
 #include "ppapi/cpp/rect.h"
 #include "ppapi/cpp/size.h"
@@ -44,6 +43,7 @@
 
 struct PP_PdfAccessibilityActionData;
 struct PP_PdfPrintSettings_Dev;
+class SkBitmap;
 
 namespace gfx {
 class Rect;
@@ -335,9 +335,9 @@
   // is called once. After Paint is called n times, PostPaint is called once.
   virtual void PrePaint() = 0;
   virtual void Paint(const pp::Rect& rect,
-                     pp::ImageData* image_data,
-                     std::vector<pp::Rect>* ready,
-                     std::vector<pp::Rect>* pending) = 0;
+                     SkBitmap& image_data,
+                     std::vector<pp::Rect>& ready,
+                     std::vector<pp::Rect>& pending) = 0;
   virtual void PostPaint() = 0;
   virtual bool HandleDocumentLoad(const pp::URLLoader& loader) = 0;
   virtual bool HandleEvent(const pp::InputEvent& event) = 0;
diff --git a/pdf/pdf_init.cc b/pdf/pdf_init.cc
new file mode 100644
index 0000000..fdc1be3c
--- /dev/null
+++ b/pdf/pdf_init.cc
@@ -0,0 +1,23 @@
+// Copyright 2020 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 "pdf/pdf_init.h"
+
+namespace chrome_pdf {
+
+namespace {
+
+bool g_sdk_initialized_via_pepper = false;
+
+}  // namespace
+
+bool IsSDKInitializedViaPepper() {
+  return g_sdk_initialized_via_pepper;
+}
+
+void SetIsSDKInitializedViaPepper(bool initialized_via_pepper) {
+  g_sdk_initialized_via_pepper = initialized_via_pepper;
+}
+
+}  // namespace chrome_pdf
diff --git a/pdf/pdf_init.h b/pdf/pdf_init.h
new file mode 100644
index 0000000..652ed92
--- /dev/null
+++ b/pdf/pdf_init.h
@@ -0,0 +1,15 @@
+// Copyright 2020 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 PDF_PDF_INIT_H_
+#define PDF_PDF_INIT_H_
+
+namespace chrome_pdf {
+
+bool IsSDKInitializedViaPepper();
+void SetIsSDKInitializedViaPepper(bool initialized_via_pepper);
+
+}  // namespace chrome_pdf
+
+#endif  // PDF_PDF_INIT_H_
diff --git a/pdf/pdf_ppapi.cc b/pdf/pdf_ppapi.cc
index 48b58e3..718fc21 100644
--- a/pdf/pdf_ppapi.cc
+++ b/pdf/pdf_ppapi.cc
@@ -7,6 +7,8 @@
 #include <memory>
 
 #include "pdf/out_of_process_instance.h"
+#include "pdf/pdf.h"
+#include "pdf/pdf_init.h"
 #include "ppapi/c/ppp.h"
 #include "ppapi/cpp/private/internal_module.h"
 #include "ppapi/cpp/private/pdf.h"
@@ -16,8 +18,6 @@
 
 namespace {
 
-bool g_sdk_initialized_via_pepper = false;
-
 class PDFModule : public pp::Module {
  public:
   PDFModule();
@@ -31,9 +31,9 @@
 PDFModule::PDFModule() = default;
 
 PDFModule::~PDFModule() {
-  if (g_sdk_initialized_via_pepper) {
+  if (IsSDKInitializedViaPepper()) {
     ShutdownSDK();
-    g_sdk_initialized_via_pepper = false;
+    SetIsSDKInitializedViaPepper(false);
   }
 }
 
@@ -42,7 +42,7 @@
 }
 
 pp::Instance* PDFModule::CreateInstance(PP_Instance instance) {
-  if (!g_sdk_initialized_via_pepper) {
+  if (!IsSDKInitializedViaPepper()) {
     v8::StartupData snapshot;
     pp::PDF::GetV8ExternalSnapshotData(pp::InstanceHandle(instance),
                                        &snapshot.data, &snapshot.raw_size);
@@ -51,7 +51,7 @@
     }
 
     InitializeSDK(/*enable_v8=*/true);
-    g_sdk_initialized_via_pepper = true;
+    SetIsSDKInitializedViaPepper(true);
   }
 
   return new OutOfProcessInstance(instance);
@@ -79,8 +79,4 @@
   return module ? module->GetPluginInterface(interface_name) : nullptr;
 }
 
-bool IsSDKInitializedViaPepper() {
-  return g_sdk_initialized_via_pepper;
-}
-
 }  // namespace chrome_pdf
diff --git a/pdf/pdf_ppapi.h b/pdf/pdf_ppapi.h
index 816b17f..7342f0ab0 100644
--- a/pdf/pdf_ppapi.h
+++ b/pdf/pdf_ppapi.h
@@ -5,18 +5,19 @@
 #ifndef PDF_PDF_PPAPI_H_
 #define PDF_PDF_PPAPI_H_
 
+#include "base/component_export.h"
+#include "ppapi/c/pp_module.h"
 #include "ppapi/c/ppb.h"
-#include "ppapi/cpp/module.h"
 
 namespace chrome_pdf {
 
+COMPONENT_EXPORT(PDF_PPAPI)
 int PPP_InitializeModule(PP_Module module_id,
                          PPB_GetInterface get_browser_interface);
-void PPP_ShutdownModule();
+COMPONENT_EXPORT(PDF_PPAPI) void PPP_ShutdownModule();
+COMPONENT_EXPORT(PDF_PPAPI)
 const void* PPP_GetInterface(const char* interface_name);
 
-bool IsSDKInitializedViaPepper();
-
 }  // namespace chrome_pdf
 
 #endif  // PDF_PDF_PPAPI_H_
diff --git a/pdf/pdfium/pdfium_assert_matching_enums.cc b/pdf/pdfium/pdfium_assert_matching_enums.cc
index b7744534..61af29a 100644
--- a/pdf/pdfium/pdfium_assert_matching_enums.cc
+++ b/pdf/pdfium/pdfium_assert_matching_enums.cc
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 #include "build/build_config.h"
-#include "pdf/pdf.h"
 #include "pdf/pdf_engine.h"
 #include "pdf/ppapi_migration/input_event_conversions.h"
 #include "ppapi/c/pp_input_event.h"
@@ -16,6 +15,10 @@
 #include "third_party/pdfium/public/fpdfview.h"
 #include "ui/events/keycodes/keyboard_codes.h"
 
+#if defined(OS_WIN)
+#include "pdf/pdf.h"
+#endif
+
 #define STATIC_ASSERT_ENUM(a, b)                            \
   static_assert(static_cast<int>(a) == static_cast<int>(b), \
                 "mismatching enums: " #a)
diff --git a/pdf/pdfium/pdfium_engine.cc b/pdf/pdfium/pdfium_engine.cc
index 5c2fdee..d1cc3bb 100644
--- a/pdf/pdfium/pdfium_engine.cc
+++ b/pdf/pdfium/pdfium_engine.cc
@@ -41,6 +41,8 @@
 #include "pdf/pdfium/pdfium_mem_buffer_file_write.h"
 #include "pdf/pdfium/pdfium_permissions.h"
 #include "pdf/pdfium/pdfium_unsupported_features.h"
+#include "pdf/ppapi_migration/bitmap.h"
+#include "pdf/ppapi_migration/geometry_conversions.h"
 #include "pdf/ppapi_migration/input_event_conversions.h"
 #include "pdf/url_loader_wrapper_impl.h"
 #include "ppapi/c/ppb_input_event.h"
@@ -56,6 +58,7 @@
 #include "third_party/pdfium/public/fpdf_fwlevent.h"
 #include "third_party/pdfium/public/fpdf_ppo.h"
 #include "third_party/pdfium/public/fpdf_searchex.h"
+#include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/events/keycodes/keyboard_codes.h"
 #include "ui/gfx/geometry/rect.h"
 #include "v8/include/v8.h"
@@ -270,19 +273,18 @@
 
 // Normalize a MouseInputEvent. For Mac, this means transforming ctrl + left
 // button down events into a right button down events.
-pp::MouseInputEvent NormalizeMouseEvent(pp::Instance* instance,
-                                        const pp::MouseInputEvent& event) {
-  pp::MouseInputEvent normalized_event = event;
+MouseInputEvent NormalizeMouseEvent(const MouseInputEvent& event) {
+  MouseInputEvent normalized_event = event;
 #if defined(OS_MACOSX)
   uint32_t modifiers = event.GetModifiers();
-  if ((modifiers & PP_INPUTEVENT_MODIFIER_CONTROLKEY) &&
-      event.GetButton() == PP_INPUTEVENT_MOUSEBUTTON_LEFT &&
-      event.GetType() == PP_INPUTEVENT_TYPE_MOUSEDOWN) {
-    uint32_t new_modifiers = modifiers & ~PP_INPUTEVENT_MODIFIER_CONTROLKEY;
-    normalized_event = pp::MouseInputEvent(
-        instance, PP_INPUTEVENT_TYPE_MOUSEDOWN, event.GetTimeStamp(),
-        new_modifiers, PP_INPUTEVENT_MOUSEBUTTON_RIGHT, event.GetPosition(), 1,
-        event.GetMovement());
+  if ((event.GetModifiers() & kInputEventModifierControlKey) &&
+      event.GetButton() == InputEventMouseButtonType::kLeft &&
+      event.GetEventType() == InputEventType::kMouseDown) {
+    uint32_t new_modifiers = modifiers & ~kInputEventModifierControlKey;
+    normalized_event =
+        MouseInputEvent(InputEventType::kMouseDown, event.GetTimeStamp(),
+                        new_modifiers, InputEventMouseButtonType::kRight,
+                        event.GetPosition(), 1, event.GetMovement());
   }
 #endif
   return normalized_event;
@@ -531,13 +533,9 @@
 }
 
 void PDFiumEngine::Paint(const pp::Rect& rect,
-                         pp::ImageData* image_data,
-                         std::vector<pp::Rect>* ready,
-                         std::vector<pp::Rect>* pending) {
-  DCHECK(image_data);
-  DCHECK(ready);
-  DCHECK(pending);
-
+                         SkBitmap& image_data,
+                         std::vector<pp::Rect>& ready,
+                         std::vector<pp::Rect>& pending) {
   pp::Rect leftover = rect;
   for (size_t i = 0; i < visible_pages_.size(); ++i) {
     int index = visible_pages_[i];
@@ -579,7 +577,7 @@
           // happened, but it made scrolling up on complex PDFs very slow since
           // there would be a damaged rect at the top (from scroll) and at the
           // bottom (from toolbar).
-          pending->push_back(dirty_in_screen);
+          pending.push_back(dirty_in_screen);
           continue;
         }
       }
@@ -594,13 +592,13 @@
       progressive_paints_[progressive].set_painted(true);
       if (ContinuePaint(progressive, image_data)) {
         FinishPaint(progressive, image_data);
-        ready->push_back(dirty_in_screen);
+        ready.push_back(dirty_in_screen);
       } else {
-        pending->push_back(dirty_in_screen);
+        pending.push_back(dirty_in_screen);
       }
     } else {
       PaintUnavailablePage(index, dirty_in_screen, image_data);
-      ready->push_back(dirty_in_screen);
+      ready.push_back(dirty_in_screen);
     }
   }
 }
@@ -828,16 +826,16 @@
   bool rv = false;
   switch (event.GetType()) {
     case PP_INPUTEVENT_TYPE_MOUSEDOWN:
-      rv = OnMouseDown(pp::MouseInputEvent(event));
+      rv = OnMouseDown(GetMouseInputEvent(pp::MouseInputEvent(event)));
       break;
     case PP_INPUTEVENT_TYPE_MOUSEUP:
-      rv = OnMouseUp(pp::MouseInputEvent(event));
+      rv = OnMouseUp(GetMouseInputEvent(pp::MouseInputEvent(event)));
       break;
     case PP_INPUTEVENT_TYPE_MOUSEMOVE:
-      rv = OnMouseMove(pp::MouseInputEvent(event));
+      rv = OnMouseMove(GetMouseInputEvent(pp::MouseInputEvent(event)));
       break;
     case PP_INPUTEVENT_TYPE_MOUSEENTER:
-      OnMouseEnter(pp::MouseInputEvent(event));
+      OnMouseEnter(GetMouseInputEvent(pp::MouseInputEvent(event)));
       break;
     case PP_INPUTEVENT_TYPE_KEYDOWN:
       rv = OnKeyDown(GetKeyboardInputEvent(pp::KeyboardInputEvent(event)));
@@ -1079,14 +1077,13 @@
              : result;
 }
 
-bool PDFiumEngine::OnMouseDown(const pp::MouseInputEvent& event) {
-  pp::MouseInputEvent normalized_event =
-      NormalizeMouseEvent(client_->GetPluginInstance(), event);
-  if (normalized_event.GetButton() == PP_INPUTEVENT_MOUSEBUTTON_LEFT)
+bool PDFiumEngine::OnMouseDown(const MouseInputEvent& event) {
+  MouseInputEvent normalized_event = NormalizeMouseEvent(event);
+  if (normalized_event.GetButton() == InputEventMouseButtonType::kLeft)
     return OnLeftMouseDown(normalized_event);
-  if (normalized_event.GetButton() == PP_INPUTEVENT_MOUSEBUTTON_MIDDLE)
+  if (normalized_event.GetButton() == InputEventMouseButtonType::kMiddle)
     return OnMiddleMouseDown(normalized_event);
-  if (normalized_event.GetButton() == PP_INPUTEVENT_MOUSEBUTTON_RIGHT)
+  if (normalized_event.GetButton() == InputEventMouseButtonType::kRight)
     return OnRightMouseDown(normalized_event);
   return false;
 }
@@ -1128,7 +1125,7 @@
     client_->NotifyTouchSelectionOccurred();
 }
 
-bool PDFiumEngine::OnLeftMouseDown(const pp::MouseInputEvent& event) {
+bool PDFiumEngine::OnLeftMouseDown(const MouseInputEvent& event) {
   SetMouseLeftButtonDown(true);
 
   auto selection_invalidator =
@@ -1139,9 +1136,9 @@
   int char_index = -1;
   int form_type = FPDF_FORMFIELD_UNKNOWN;
   PDFiumPage::LinkTarget target;
-  pp::Point point = event.GetPosition();
-  PDFiumPage::Area area =
-      GetCharIndex(point, &page_index, &char_index, &form_type, &target);
+  gfx::Point point = event.GetPosition();
+  PDFiumPage::Area area = GetCharIndex(PPPointFromPoint(point), &page_index,
+                                       &char_index, &form_type, &target);
   DCHECK_GE(form_type, FPDF_FORMFIELD_UNKNOWN);
   mouse_down_state_.Set(area, target);
 
@@ -1155,7 +1152,7 @@
     last_focused_page_ = page_index;
     double page_x;
     double page_y;
-    DeviceToPage(page_index, point, &page_x, &page_y);
+    DeviceToPage(page_index, PPPointFromPoint(point), &page_x, &page_y);
 
     if (form_type != FPDF_FORMFIELD_UNKNOWN) {
       // FORM_OnLButton*() will trigger a callback to
@@ -1193,10 +1190,10 @@
   return true;
 }
 
-bool PDFiumEngine::OnMiddleMouseDown(const pp::MouseInputEvent& event) {
+bool PDFiumEngine::OnMiddleMouseDown(const MouseInputEvent& event) {
   SetMouseLeftButtonDown(false);
   mouse_middle_button_down_ = true;
-  mouse_middle_button_last_position_ = event.GetPosition();
+  mouse_middle_button_last_position_ = PPPointFromPoint(event.GetPosition());
 
   SelectionChangeInvalidator selection_invalidator(this);
   selection_.clear();
@@ -1206,8 +1203,8 @@
   int unused_form_type = FPDF_FORMFIELD_UNKNOWN;
   PDFiumPage::LinkTarget target;
   PDFiumPage::Area area =
-      GetCharIndex(event.GetPosition(), &unused_page_index, &unused_char_index,
-                   &unused_form_type, &target);
+      GetCharIndex(PPPointFromPoint(event.GetPosition()), &unused_page_index,
+                   &unused_char_index, &unused_form_type, &target);
   mouse_down_state_.Set(area, target);
 
   // Decide whether to open link or not based on user action in mouse up and
@@ -1224,10 +1221,10 @@
   return false;
 }
 
-bool PDFiumEngine::OnRightMouseDown(const pp::MouseInputEvent& event) {
-  DCHECK_EQ(PP_INPUTEVENT_MOUSEBUTTON_RIGHT, event.GetButton());
+bool PDFiumEngine::OnRightMouseDown(const MouseInputEvent& event) {
+  DCHECK_EQ(InputEventMouseButtonType::kRight, event.GetButton());
 
-  pp::Point point = event.GetPosition();
+  pp::Point point = PPPointFromPoint(event.GetPosition());
   int page_index = -1;
   int char_index = -1;
   int form_type = FPDF_FORMFIELD_UNKNOWN;
@@ -1316,22 +1313,22 @@
   return false;
 }
 
-bool PDFiumEngine::OnMouseUp(const pp::MouseInputEvent& event) {
-  if (event.GetButton() != PP_INPUTEVENT_MOUSEBUTTON_LEFT &&
-      event.GetButton() != PP_INPUTEVENT_MOUSEBUTTON_MIDDLE) {
+bool PDFiumEngine::OnMouseUp(const MouseInputEvent& event) {
+  if (event.GetButton() != InputEventMouseButtonType::kLeft &&
+      event.GetButton() != InputEventMouseButtonType::kMiddle) {
     return false;
   }
 
-  if (event.GetButton() == PP_INPUTEVENT_MOUSEBUTTON_LEFT)
+  if (event.GetButton() == InputEventMouseButtonType::kLeft)
     SetMouseLeftButtonDown(false);
-  else if (event.GetButton() == PP_INPUTEVENT_MOUSEBUTTON_MIDDLE)
+  else if (event.GetButton() == InputEventMouseButtonType::kMiddle)
     mouse_middle_button_down_ = false;
 
   int page_index = -1;
   int char_index = -1;
   int form_type = FPDF_FORMFIELD_UNKNOWN;
   PDFiumPage::LinkTarget target;
-  pp::Point point = event.GetPosition();
+  pp::Point point = PPPointFromPoint(event.GetPosition());
   PDFiumPage::Area area =
       GetCharIndex(point, &page_index, &char_index, &form_type, &target);
 
@@ -1352,7 +1349,7 @@
       return true;
   }
 
-  if (event.GetButton() == PP_INPUTEVENT_MOUSEBUTTON_MIDDLE) {
+  if (event.GetButton() == InputEventMouseButtonType::kMiddle) {
     if (kViewerImplementedPanning) {
       // Update the cursor when panning stops.
       client_->UpdateCursor(DetermineCursorType(area, form_type));
@@ -1377,12 +1374,12 @@
   return true;
 }
 
-bool PDFiumEngine::OnMouseMove(const pp::MouseInputEvent& event) {
+bool PDFiumEngine::OnMouseMove(const MouseInputEvent& event) {
   int page_index = -1;
   int char_index = -1;
   int form_type = FPDF_FORMFIELD_UNKNOWN;
   PDFiumPage::LinkTarget target;
-  pp::Point point = event.GetPosition();
+  pp::Point point = PPPointFromPoint(event.GetPosition());
   PDFiumPage::Area area =
       GetCharIndex(point, &page_index, &char_index, &form_type, &target);
 
@@ -1402,7 +1399,8 @@
                        page_y);
     }
 
-    UpdateLinkUnderCursor(GetLinkAtPosition(event.GetPosition()));
+    UpdateLinkUnderCursor(
+        GetLinkAtPosition(PPPointFromPoint(event.GetPosition())));
 
     // If in form text area while left mouse button is held down, check if form
     // text selection needs to be updated.
@@ -1416,11 +1414,12 @@
       // moving the page, rather than the delta the mouse moved.
       // GetMovement() does not work here, as small mouse movements are
       // considered zero.
-      pp::Point page_position_delta =
-          mouse_middle_button_last_position_ - event.GetPosition();
+      pp::Point page_position_delta = mouse_middle_button_last_position_ -
+                                      PPPointFromPoint(event.GetPosition());
       if (page_position_delta.x() != 0 || page_position_delta.y() != 0) {
         client_->ScrollBy(page_position_delta);
-        mouse_middle_button_last_position_ = event.GetPosition();
+        mouse_middle_button_last_position_ =
+            PPPointFromPoint(event.GetPosition());
       }
     }
 
@@ -1479,11 +1478,12 @@
   }
 }
 
-void PDFiumEngine::OnMouseEnter(const pp::MouseInputEvent& event) {
+void PDFiumEngine::OnMouseEnter(const MouseInputEvent& event) {
   if (event.GetModifiers() & PP_INPUTEVENT_MODIFIER_MIDDLEBUTTONDOWN) {
     if (!mouse_middle_button_down_) {
       mouse_middle_button_down_ = true;
-      mouse_middle_button_last_position_ = event.GetPosition();
+      mouse_middle_button_last_position_ =
+          PPPointFromPoint(event.GetPosition());
     }
   } else {
     if (mouse_middle_button_down_) {
@@ -2384,15 +2384,12 @@
   base::AutoReset<bool> handling_long_press_guard(&handling_long_press_, true);
   pp::FloatPoint fp =
       event.GetTouchByIndex(PP_TOUCHLIST_TYPE_TARGETTOUCHES, 0).position();
-  pp::Point point;
-  point.set_x(fp.x());
-  point.set_y(fp.y());
+  gfx::Point point(fp.x(), fp.y());
 
   // Send a fake mouse down to trigger the multi-click selection code.
-  pp::MouseInputEvent mouse_event(
-      client_->GetPluginInstance(), PP_INPUTEVENT_TYPE_MOUSEDOWN,
-      event.GetTimeStamp(), event.GetModifiers(),
-      PP_INPUTEVENT_MOUSEBUTTON_LEFT, point, 2, point);
+  MouseInputEvent mouse_event(
+      InputEventType::kMouseDown, event.GetTimeStamp(), event.GetModifiers(),
+      InputEventMouseButtonType::kLeft, point, 2, point);
 
   OnMouseDown(mouse_event);
 }
@@ -2962,11 +2959,9 @@
   return progressive_paints_.size() - 1;
 }
 
-bool PDFiumEngine::ContinuePaint(int progressive_index,
-                                 pp::ImageData* image_data) {
+bool PDFiumEngine::ContinuePaint(int progressive_index, SkBitmap& image_data) {
   DCHECK_GE(progressive_index, 0);
   DCHECK_LT(static_cast<size_t>(progressive_index), progressive_paints_.size());
-  DCHECK(image_data);
 
   last_progressive_start_time_ = base::Time::Now();
 #if defined(OS_LINUX)
@@ -2996,16 +2991,14 @@
         ToPDFiumRotation(layout_.options().default_page_orientation()),
         GetRenderingFlags(), this);
     progressive_paints_[progressive_index].SetBitmapAndImageData(
-        std::move(new_bitmap), *image_data);
+        std::move(new_bitmap), image_data);
   }
   return rv != FPDF_RENDER_TOBECONTINUED;
 }
 
-void PDFiumEngine::FinishPaint(int progressive_index,
-                               pp::ImageData* image_data) {
+void PDFiumEngine::FinishPaint(int progressive_index, SkBitmap& image_data) {
   DCHECK_GE(progressive_index, 0);
   DCHECK_LT(static_cast<size_t>(progressive_index), progressive_paints_.size());
-  DCHECK(image_data);
 
   int page_index = progressive_paints_[progressive_index].page_index();
   const pp::Rect& dirty_in_screen =
@@ -3108,10 +3101,9 @@
 }
 
 void PDFiumEngine::PaintPageShadow(int progressive_index,
-                                   pp::ImageData* image_data) {
+                                   SkBitmap& image_data) {
   DCHECK_GE(progressive_index, 0);
   DCHECK_LT(static_cast<size_t>(progressive_index), progressive_paints_.size());
-  DCHECK(image_data);
 
   int page_index = progressive_paints_[progressive_index].page_index();
   const pp::Rect& dirty_in_screen =
@@ -3134,10 +3126,9 @@
 }
 
 void PDFiumEngine::DrawSelections(int progressive_index,
-                                  pp::ImageData* image_data) const {
+                                  SkBitmap& image_data) const {
   DCHECK_GE(progressive_index, 0);
   DCHECK_LT(static_cast<size_t>(progressive_index), progressive_paints_.size());
-  DCHECK(image_data);
 
   int page_index = progressive_paints_[progressive_index].page_index();
   const pp::Rect& dirty_in_screen =
@@ -3145,7 +3136,7 @@
 
   void* region = nullptr;
   int stride;
-  GetRegion(dirty_in_screen.point(), image_data, &region, &stride);
+  GetRegion(dirty_in_screen.point(), image_data, region, stride);
 
   std::vector<pp::Rect> highlighted_rects;
   pp::Rect visible_rect = GetVisibleRect();
@@ -3182,7 +3173,7 @@
 
 void PDFiumEngine::PaintUnavailablePage(int page_index,
                                         const pp::Rect& dirty,
-                                        pp::ImageData* image_data) {
+                                        SkBitmap& image_data) {
   int start_x;
   int start_y;
   int size_x;
@@ -3207,10 +3198,10 @@
 }
 
 ScopedFPDFBitmap PDFiumEngine::CreateBitmap(const pp::Rect& rect,
-                                            pp::ImageData* image_data) const {
+                                            SkBitmap& image_data) const {
   void* region;
   int stride;
-  GetRegion(rect.point(), image_data, &region, &stride);
+  GetRegion(rect.point(), image_data, region, stride);
   if (!region)
     return nullptr;
   return ScopedFPDFBitmap(FPDFBitmap_CreateEx(rect.width(), rect.height(),
@@ -3477,7 +3468,7 @@
 void PDFiumEngine::DrawPageShadow(const pp::Rect& page_rc,
                                   const pp::Rect& shadow_rc,
                                   const pp::Rect& clip_rc,
-                                  pp::ImageData* image_data) {
+                                  SkBitmap& image_data) {
   pp::Rect page_rect(page_rc);
   page_rect.Offset(page_offset_);
 
@@ -3501,34 +3492,34 @@
         depth, factor, client_->GetBackgroundColor());
   }
 
-  DCHECK(!image_data->is_null());
+  DCHECK(!image_data.isNull());
   DrawShadow(image_data, shadow_rect, page_rect, clip_rect, *page_shadow_);
 }
 
 void PDFiumEngine::GetRegion(const pp::Point& location,
-                             pp::ImageData* image_data,
-                             void** region,
-                             int* stride) const {
-  if (image_data->is_null()) {
+                             SkBitmap& image_data,
+                             void*& region,
+                             int& stride) const {
+  if (image_data.isNull()) {
     DCHECK(plugin_size_.IsEmpty());
-    *stride = 0;
-    *region = nullptr;
+    stride = 0;
+    region = nullptr;
     return;
   }
-  char* buffer = static_cast<char*>(image_data->data());
-  *stride = image_data->stride();
+  char* buffer = static_cast<char*>(image_data.getPixels());
+  stride = image_data.rowBytes();
 
   pp::Point offset_location = location + page_offset_;
   // TODO: update this when we support BIDI and scrollbars can be on the left.
   if (!buffer ||
       !pp::Rect(page_offset_, plugin_size_).Contains(offset_location)) {
-    *region = nullptr;
+    region = nullptr;
     return;
   }
 
-  buffer += location.y() * (*stride);
+  buffer += location.y() * stride;
   buffer += (location.x() + page_offset_.x()) * 4;
-  *region = buffer;
+  region = buffer;
 }
 
 void PDFiumEngine::OnSelectionTextChanged() {
@@ -4089,7 +4080,7 @@
 
 void PDFiumEngine::ProgressivePaint::SetBitmapAndImageData(
     ScopedFPDFBitmap bitmap,
-    pp::ImageData image_data) {
+    SkBitmap image_data) {
   bitmap_ = std::move(bitmap);
   image_data_ = std::move(image_data);
 }
diff --git a/pdf/pdfium/pdfium_engine.h b/pdf/pdfium/pdfium_engine.h
index af32542..30ef2e1 100644
--- a/pdf/pdfium/pdfium_engine.h
+++ b/pdf/pdfium/pdfium_engine.h
@@ -30,7 +30,6 @@
 #include "ppapi/c/private/ppp_pdf.h"
 #include "ppapi/cpp/completion_callback.h"
 #include "ppapi/cpp/dev/buffer_dev.h"
-#include "ppapi/cpp/image_data.h"
 #include "ppapi/cpp/input_event.h"
 #include "ppapi/cpp/point.h"
 #include "ppapi/cpp/rect.h"
@@ -40,10 +39,12 @@
 #include "third_party/pdfium/public/fpdf_formfill.h"
 #include "third_party/pdfium/public/fpdf_progressive.h"
 #include "third_party/pdfium/public/fpdfview.h"
+#include "third_party/skia/include/core/SkBitmap.h"
 
 namespace chrome_pdf {
 
 class KeyboardInputEvent;
+class MouseInputEvent;
 class PDFiumDocument;
 class PDFiumPermissions;
 
@@ -83,9 +84,9 @@
   void ScrolledToYPosition(int position) override;
   void PrePaint() override;
   void Paint(const pp::Rect& rect,
-             pp::ImageData* image_data,
-             std::vector<pp::Rect>* ready,
-             std::vector<pp::Rect>* pending) override;
+             SkBitmap& image_data,
+             std::vector<pp::Rect>& ready,
+             std::vector<pp::Rect>& pending) override;
   void PostPaint() override;
   bool HandleDocumentLoad(const pp::URLLoader& loader) override;
   bool HandleEvent(const pp::InputEvent& event) override;
@@ -380,10 +381,10 @@
                       int current_page);
 
   // Input event handlers.
-  bool OnMouseDown(const pp::MouseInputEvent& event);
-  bool OnMouseUp(const pp::MouseInputEvent& event);
-  bool OnMouseMove(const pp::MouseInputEvent& event);
-  void OnMouseEnter(const pp::MouseInputEvent& event);
+  bool OnMouseDown(const MouseInputEvent& event);
+  bool OnMouseUp(const MouseInputEvent& event);
+  bool OnMouseMove(const MouseInputEvent& event);
+  void OnMouseEnter(const MouseInputEvent& event);
   bool OnKeyDown(const KeyboardInputEvent& event);
   bool OnKeyUp(const KeyboardInputEvent& event);
   bool OnChar(const KeyboardInputEvent& event);
@@ -422,9 +423,9 @@
 
   void OnSingleClick(int page_index, int char_index);
   void OnMultipleClick(int click_count, int page_index, int char_index);
-  bool OnLeftMouseDown(const pp::MouseInputEvent& event);
-  bool OnMiddleMouseDown(const pp::MouseInputEvent& event);
-  bool OnRightMouseDown(const pp::MouseInputEvent& event);
+  bool OnLeftMouseDown(const MouseInputEvent& event);
+  bool OnMiddleMouseDown(const MouseInputEvent& event);
+  bool OnRightMouseDown(const MouseInputEvent& event);
 
   // Starts a progressive paint operation given a rectangle in screen
   // coordinates. Returns the index in progressive_rects_.
@@ -432,11 +433,11 @@
 
   // Continues a paint operation that was started earlier.  Returns true if the
   // paint is done, or false if it needs to be continued.
-  bool ContinuePaint(int progressive_index, pp::ImageData* image_data);
+  bool ContinuePaint(int progressive_index, SkBitmap& image_data);
 
   // Called once PDFium is finished rendering a page so that we draw our
   // borders, highlighting etc.
-  void FinishPaint(int progressive_index, pp::ImageData* image_data);
+  void FinishPaint(int progressive_index, SkBitmap& image_data);
 
   // Stops any paints that are in progress.
   void CancelPaints();
@@ -449,15 +450,15 @@
   // with the page background.
   void FillPageSides(int progressive_index);
 
-  void PaintPageShadow(int progressive_index, pp::ImageData* image_data);
+  void PaintPageShadow(int progressive_index, SkBitmap& image_data);
 
   // Highlight visible find results and selections.
-  void DrawSelections(int progressive_index, pp::ImageData* image_data) const;
+  void DrawSelections(int progressive_index, SkBitmap& image_data) const;
 
   // Paints an page that hasn't finished downloading.
   void PaintUnavailablePage(int page_index,
                             const pp::Rect& dirty,
-                            pp::ImageData* image_data);
+                            SkBitmap& image_data);
 
   // Given a page index, returns the corresponding index in progressive_rects_,
   // or -1 if it doesn't exist.
@@ -465,7 +466,7 @@
 
   // Creates a FPDF_BITMAP from a rectangle in screen coordinates.
   ScopedFPDFBitmap CreateBitmap(const pp::Rect& rect,
-                                pp::ImageData* image_data) const;
+                                SkBitmap& image_data) const;
 
   // Given a rectangle in screen coordinates, returns the coordinates in the
   // units that PDFium rendering functions expect.
@@ -515,12 +516,12 @@
   void DrawPageShadow(const pp::Rect& page_rect,
                       const pp::Rect& shadow_rect,
                       const pp::Rect& clip_rect,
-                      pp::ImageData* image_data);
+                      SkBitmap& image_data);
 
   void GetRegion(const pp::Point& location,
-                 pp::ImageData* image_data,
-                 void** region,
-                 int* stride) const;
+                 SkBitmap& image_data,
+                 void*& region,
+                 int& stride) const;
 
   // Called when the selection changes.
   void OnSelectionTextChanged();
@@ -776,9 +777,8 @@
    public:
     ProgressivePaint(int page_index, const pp::Rect& rect);
     ProgressivePaint(ProgressivePaint&& that);
-    ~ProgressivePaint();
-
     ProgressivePaint& operator=(ProgressivePaint&& that);
+    ~ProgressivePaint();
 
     int page_index() const { return page_index_; }
     const pp::Rect& rect() const { return rect_; }
@@ -786,19 +786,16 @@
     bool painted() const { return painted_; }
 
     void set_painted(bool enable) { painted_ = enable; }
-    void SetBitmapAndImageData(ScopedFPDFBitmap bitmap,
-                               pp::ImageData image_data);
+    void SetBitmapAndImageData(ScopedFPDFBitmap bitmap, SkBitmap image_data);
 
    private:
     int page_index_;
     pp::Rect rect_;             // In screen coordinates.
-    pp::ImageData image_data_;  // Maintains reference while |bitmap_| exists.
+    SkBitmap image_data_;       // Maintains reference while |bitmap_| exists.
     ScopedFPDFBitmap bitmap_;   // Must come after |image_data_|.
     // Temporary used to figure out if in a series of Paint() calls whether this
     // pending paint was updated or not.
     bool painted_ = false;
-
-    DISALLOW_COPY_AND_ASSIGN(ProgressivePaint);
   };
   std::vector<ProgressivePaint> progressive_paints_;
 
diff --git a/pdf/ppapi_migration/bitmap.cc b/pdf/ppapi_migration/bitmap.cc
new file mode 100644
index 0000000..2522426
--- /dev/null
+++ b/pdf/ppapi_migration/bitmap.cc
@@ -0,0 +1,51 @@
+// Copyright 2020 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 "pdf/ppapi_migration/bitmap.h"
+
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/check_op.h"
+#include "ppapi/cpp/image_data.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkImageInfo.h"
+
+namespace chrome_pdf {
+
+namespace {
+
+// Releases pp::ImageData associated with the SkPixelRef. The pp::ImageData acts
+// like a shared pointer to memory provided by Pepper, and must be retained for
+// the life of the SkPixelRef.
+void ReleaseImageData(void* addr, void* context) {
+  pp::ImageData* image_data = static_cast<pp::ImageData*>(context);
+  DCHECK_EQ(addr, image_data->data());
+  delete image_data;
+}
+
+}  // namespace
+
+SkBitmap SkBitmapFromPPImageData(std::unique_ptr<pp::ImageData> image_data) {
+  if (image_data->is_null()) {
+    return SkBitmap();
+  }
+
+  // Note that we unconditionally use BGRA_PREMUL with PDFium.
+  DCHECK_EQ(image_data->format(), PP_IMAGEDATAFORMAT_BGRA_PREMUL);
+  SkImageInfo info =
+      SkImageInfo::Make(image_data->size().width(), image_data->size().height(),
+                        kBGRA_8888_SkColorType, kPremul_SkAlphaType);
+  void* data = image_data->data();
+  int32_t stride = image_data->stride();
+
+  SkBitmap bitmap;
+  bool success = bitmap.installPixels(info, data, stride, ReleaseImageData,
+                                      image_data.release());
+  DCHECK(success);
+  return bitmap;
+}
+
+}  // namespace chrome_pdf
diff --git a/pdf/ppapi_migration/bitmap.h b/pdf/ppapi_migration/bitmap.h
new file mode 100644
index 0000000..c819c4f
--- /dev/null
+++ b/pdf/ppapi_migration/bitmap.h
@@ -0,0 +1,30 @@
+// Copyright 2020 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 PDF_PPAPI_MIGRATION_BITMAP_H_
+#define PDF_PPAPI_MIGRATION_BITMAP_H_
+
+#include <memory>
+
+class SkBitmap;
+
+namespace pp {
+class ImageData;
+}  // namespace pp
+
+namespace chrome_pdf {
+
+// Creates an SkBitmap from a pp::ImageData. The SkBitmap takes ownership of the
+// pp::ImageData, and shares ownership of the underlying pixel memory. (Note
+// that it's easy to make a shallow copy of a pp::ImageData.)
+//
+// In case of an error, returns an empty SkBitmap.
+//
+// TODO(kmoon): Skia is trying to get rid of SkBitmap in favor of immutable
+// types like SkImage, so we should migrate once PDFium is ready for Skia.
+SkBitmap SkBitmapFromPPImageData(std::unique_ptr<pp::ImageData> image_data);
+
+}  // namespace chrome_pdf
+
+#endif  // PDF_PPAPI_MIGRATION_BITMAP_H_
diff --git a/pdf/ppapi_migration/geometry_conversions.cc b/pdf/ppapi_migration/geometry_conversions.cc
index a6399e0..bb7719e 100644
--- a/pdf/ppapi_migration/geometry_conversions.cc
+++ b/pdf/ppapi_migration/geometry_conversions.cc
@@ -4,8 +4,10 @@
 
 #include "pdf/ppapi_migration/geometry_conversions.h"
 
+#include "ppapi/c/pp_point.h"
 #include "ppapi/c/pp_rect.h"
 #include "ppapi/c/pp_size.h"
+#include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/size.h"
 
@@ -20,4 +22,12 @@
   return gfx::Size(pp_size.width, pp_size.height);
 }
 
+gfx::Point PointFromPPPoint(const PP_Point& pp_point) {
+  return gfx::Point(pp_point.x, pp_point.y);
+}
+
+PP_Point PPPointFromPoint(const gfx::Point& point) {
+  return {point.x(), point.y()};
+}
+
 }  // namespace chrome_pdf
diff --git a/pdf/ppapi_migration/geometry_conversions.h b/pdf/ppapi_migration/geometry_conversions.h
index 8806207..5133485 100644
--- a/pdf/ppapi_migration/geometry_conversions.h
+++ b/pdf/ppapi_migration/geometry_conversions.h
@@ -5,10 +5,12 @@
 #ifndef PDF_PPAPI_MIGRATION_GEOMETRY_CONVERSIONS_H_
 #define PDF_PPAPI_MIGRATION_GEOMETRY_CONVERSIONS_H_
 
+struct PP_Point;
 struct PP_Rect;
 struct PP_Size;
 
 namespace gfx {
+class Point;
 class Rect;
 class Size;
 }  // namespace gfx
@@ -17,6 +19,8 @@
 
 gfx::Rect RectFromPPRect(const PP_Rect& pp_rect);
 gfx::Size SizeFromPPSize(const PP_Size& pp_size);
+gfx::Point PointFromPPPoint(const PP_Point& pp_point);
+PP_Point PPPointFromPoint(const gfx::Point& point);
 
 }  // namespace chrome_pdf
 
diff --git a/pdf/ppapi_migration/input_event_conversions.cc b/pdf/ppapi_migration/input_event_conversions.cc
index 84f246c..bd17b91 100644
--- a/pdf/ppapi_migration/input_event_conversions.cc
+++ b/pdf/ppapi_migration/input_event_conversions.cc
@@ -5,6 +5,7 @@
 #include "pdf/ppapi_migration/input_event_conversions.h"
 
 #include "base/notreached.h"
+#include "pdf/ppapi_migration/geometry_conversions.h"
 #include "ppapi/cpp/input_event.h"
 #include "ppapi/cpp/var.h"
 
@@ -53,7 +54,21 @@
     default:
       NOTREACHED();
       return chrome_pdf::InputEventType::kNone;
-  };
+  }
+}
+
+chrome_pdf::InputEventMouseButtonType GetInputEventMouseButtonType(
+    const PP_InputEvent_MouseButton& mouse_button_type) {
+  switch (mouse_button_type) {
+    case PP_INPUTEVENT_MOUSEBUTTON_LEFT:
+      return chrome_pdf::InputEventMouseButtonType::kLeft;
+    case PP_INPUTEVENT_MOUSEBUTTON_MIDDLE:
+      return chrome_pdf::InputEventMouseButtonType::kMiddle;
+    case PP_INPUTEVENT_MOUSEBUTTON_RIGHT:
+      return chrome_pdf::InputEventMouseButtonType::kRight;
+    default:
+      return chrome_pdf::InputEventMouseButtonType::kNone;
+  }
 }
 
 }  // namespace
@@ -79,10 +94,40 @@
 
 KeyboardInputEvent::~KeyboardInputEvent() = default;
 
+MouseInputEvent::MouseInputEvent(InputEventType event_type,
+                                 double time_stamp,
+                                 uint32_t modifiers,
+                                 InputEventMouseButtonType mouse_button_type,
+                                 const gfx::Point& point,
+                                 int32_t click_count,
+                                 const gfx::Point& movement)
+    : event_type_(event_type),
+      time_stamp_(time_stamp),
+      modifiers_(modifiers),
+      mouse_button_type_(mouse_button_type),
+      point_(point),
+      click_count_(click_count),
+      movement_(movement) {}
+
+MouseInputEvent::MouseInputEvent(const MouseInputEvent& other) = default;
+
+MouseInputEvent& MouseInputEvent::operator=(const MouseInputEvent& other) =
+    default;
+
+MouseInputEvent::~MouseInputEvent() = default;
+
 KeyboardInputEvent GetKeyboardInputEvent(const pp::KeyboardInputEvent& event) {
   return KeyboardInputEvent(GetEventType(event.GetType()), event.GetTimeStamp(),
                             event.GetModifiers(), event.GetKeyCode(),
                             event.GetCharacterText().AsString());
 }
 
+MouseInputEvent GetMouseInputEvent(const pp::MouseInputEvent& event) {
+  return MouseInputEvent(
+      GetEventType(event.GetType()), event.GetTimeStamp(), event.GetModifiers(),
+      GetInputEventMouseButtonType(event.GetButton()),
+      PointFromPPPoint(event.GetPosition()), event.GetClickCount(),
+      PointFromPPPoint(event.GetMovement()));
+}
+
 }  // namespace chrome_pdf
diff --git a/pdf/ppapi_migration/input_event_conversions.h b/pdf/ppapi_migration/input_event_conversions.h
index 7caa138..950c200 100644
--- a/pdf/ppapi_migration/input_event_conversions.h
+++ b/pdf/ppapi_migration/input_event_conversions.h
@@ -8,8 +8,11 @@
 #include <stdint.h>
 #include <string>
 
+#include "ui/gfx/geometry/point.h"
+
 namespace pp {
 class KeyboardInputEvent;
+class MouseInputEvent;
 }  // namespace pp
 
 namespace chrome_pdf {
@@ -99,6 +102,13 @@
   kTouchCancel
 };
 
+enum class InputEventMouseButtonType {
+  kNone = 0,
+  kLeft,
+  kMiddle,
+  kRight,
+};
+
 class KeyboardInputEvent {
  public:
   KeyboardInputEvent(InputEventType event_type,
@@ -130,8 +140,51 @@
   std::string key_char_;
 };
 
+class MouseInputEvent {
+ public:
+  MouseInputEvent(InputEventType event_type,
+                  double time_stamp,
+                  uint32_t modifier,
+                  InputEventMouseButtonType mouse_button_type,
+                  const gfx::Point& point,
+                  int32_t click_count,
+                  const gfx::Point& movement);
+  MouseInputEvent(const MouseInputEvent& other);
+  MouseInputEvent& operator=(const MouseInputEvent& other);
+  ~MouseInputEvent();
+
+  const InputEventType& GetEventType() const { return event_type_; }
+
+  double GetTimeStamp() const { return time_stamp_; }
+
+  uint32_t GetModifiers() const { return modifiers_; }
+
+  const InputEventMouseButtonType& GetButton() const {
+    return mouse_button_type_;
+  }
+
+  const gfx::Point& GetPosition() const { return point_; }
+
+  int32_t GetClickCount() const { return click_count_; }
+
+  const gfx::Point& GetMovement() const { return movement_; }
+
+ private:
+  InputEventType event_type_ = InputEventType::kNone;
+  // The units are in seconds, but are not measured relative to any particular
+  // epoch, so the most you can do is compare two values.
+  double time_stamp_ = 0;
+  uint32_t modifiers_ = kInputEventModifierNone;
+  InputEventMouseButtonType mouse_button_type_;
+  gfx::Point point_;
+  int32_t click_count_ = 0;
+  gfx::Point movement_;
+};
+
 KeyboardInputEvent GetKeyboardInputEvent(const pp::KeyboardInputEvent& event);
 
+MouseInputEvent GetMouseInputEvent(const pp::MouseInputEvent& event);
+
 }  // namespace chrome_pdf
 
 #endif  // PDF_PPAPI_MIGRATION_INPUT_EVENT_CONVERSIONS_H_
diff --git a/printing/common/metafile_utils.cc b/printing/common/metafile_utils.cc
index 7e4cfa1..36b6a199 100644
--- a/printing/common/metafile_utils.cc
+++ b/printing/common/metafile_utils.cc
@@ -242,8 +242,7 @@
 }
 
 sk_sp<SkData> SerializeOopPicture(SkPicture* pic, void* ctx) {
-  const ContentToProxyIdMap* context =
-      reinterpret_cast<const ContentToProxyIdMap*>(ctx);
+  const auto* context = reinterpret_cast<const ContentToProxyTokenMap*>(ctx);
   uint32_t pic_id = pic->uniqueID();
   auto iter = context->find(pic_id);
   if (iter == context->end())
diff --git a/printing/common/metafile_utils.h b/printing/common/metafile_utils.h
index 15912ec5..56cb73d 100644
--- a/printing/common/metafile_utils.h
+++ b/printing/common/metafile_utils.h
@@ -9,6 +9,7 @@
 
 #include "base/containers/flat_map.h"
 #include "base/containers/flat_set.h"
+#include "base/unguessable_token.h"
 #include "skia/ext/platform_canvas.h"
 #include "third_party/skia/include/core/SkDocument.h"
 #include "third_party/skia/include/core/SkRefCnt.h"
@@ -18,7 +19,7 @@
 
 namespace printing {
 
-using ContentToProxyIdMap = base::flat_map<uint32_t, int>;
+using ContentToProxyTokenMap = base::flat_map<uint32_t, base::UnguessableToken>;
 using ContentProxySet = base::flat_set<uint32_t>;
 
 // Stores the mapping between a content's unique id and its actual content.
@@ -28,8 +29,8 @@
     base::flat_map<uint32_t, sk_sp<SkTypeface>>;
 
 // Stores the mapping between content's unique id and its corresponding frame
-// proxy id.
-using PictureSerializationContext = ContentToProxyIdMap;
+// proxy token.
+using PictureSerializationContext = ContentToProxyTokenMap;
 
 // Stores the set of typeface unique ids used by the picture frame content.
 using TypefaceSerializationContext = ContentProxySet;
diff --git a/printing/metafile_skia.cc b/printing/metafile_skia.cc
index 710c8a8c..a27896b 100644
--- a/printing/metafile_skia.cc
+++ b/printing/metafile_skia.cc
@@ -15,6 +15,7 @@
 #include "base/numerics/safe_conversions.h"
 #include "base/stl_util.h"
 #include "base/time/time.h"
+#include "base/unguessable_token.h"
 #include "cc/paint/paint_record.h"
 #include "cc/paint/paint_recorder.h"
 #include "cc/paint/skia_paint_canvas.h"
@@ -74,7 +75,7 @@
 
   std::vector<Page> pages;
   std::unique_ptr<SkStreamAsset> data_stream;
-  ContentToProxyIdMap subframe_content_info;
+  ContentToProxyTokenMap subframe_content_info;
   std::map<uint32_t, sk_sp<SkPicture>> subframe_pics;
   int document_cookie = 0;
   ContentProxySet* typeface_content_info = nullptr;
@@ -388,8 +389,9 @@
   return metafile;
 }
 
-uint32_t MetafileSkia::CreateContentForRemoteFrame(const gfx::Rect& rect,
-                                                   int render_proxy_id) {
+uint32_t MetafileSkia::CreateContentForRemoteFrame(
+    const gfx::Rect& rect,
+    const base::UnguessableToken& render_proxy_token) {
   // Create a place holder picture.
   sk_sp<SkPicture> pic = SkPicture::MakePlaceholder(
       SkRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height()));
@@ -397,7 +399,7 @@
   // Store the map between content id and the proxy id.
   uint32_t content_id = pic->uniqueID();
   DCHECK(!base::Contains(data_->subframe_content_info, content_id));
-  data_->subframe_content_info[content_id] = render_proxy_id;
+  data_->subframe_content_info[content_id] = render_proxy_token;
 
   // Store the picture content.
   data_->subframe_pics[content_id] = pic;
@@ -408,7 +410,7 @@
   return data_->document_cookie;
 }
 
-const ContentToProxyIdMap& MetafileSkia::GetSubframeContentInfo() const {
+const ContentToProxyTokenMap& MetafileSkia::GetSubframeContentInfo() const {
   return data_->subframe_content_info;
 }
 
@@ -418,9 +420,9 @@
 }
 
 void MetafileSkia::AppendSubframeInfo(uint32_t content_id,
-                                      int proxy_id,
+                                      const base::UnguessableToken& proxy_token,
                                       sk_sp<SkPicture> pic_holder) {
-  data_->subframe_content_info[content_id] = proxy_id;
+  data_->subframe_content_info[content_id] = proxy_token;
   data_->subframe_pics[content_id] = pic_holder;
 }
 
diff --git a/printing/metafile_skia.h b/printing/metafile_skia.h
index 7739e58..6521b8f5 100644
--- a/printing/metafile_skia.h
+++ b/printing/metafile_skia.h
@@ -23,6 +23,10 @@
 #include <windows.h>
 #endif
 
+namespace base {
+class UnguessableToken;
+}  // namespace base
+
 namespace printing {
 
 struct MetafileSkiaData;
@@ -99,11 +103,12 @@
   // For such a subframe, since the content is in another process, we create a
   // place holder picture now, and replace it with actual content by pdf
   // compositor service later.
-  uint32_t CreateContentForRemoteFrame(const gfx::Rect& rect,
-                                       int render_proxy_id);
+  uint32_t CreateContentForRemoteFrame(
+      const gfx::Rect& rect,
+      const base::UnguessableToken& render_proxy_token);
 
   int GetDocumentCookie() const;
-  const ContentToProxyIdMap& GetSubframeContentInfo() const;
+  const ContentToProxyTokenMap& GetSubframeContentInfo() const;
 
   void UtilizeTypefaceContext(ContentProxySet* typeface_content_info);
 
@@ -119,7 +124,7 @@
   // The following three functions are used for tests only.
   void AppendPage(const SkSize& page_size, sk_sp<cc::PaintRecord> record);
   void AppendSubframeInfo(uint32_t content_id,
-                          int proxy_id,
+                          const base::UnguessableToken& proxy_token,
                           sk_sp<SkPicture> subframe_pic_holder);
   SkStreamAsset* GetPdfData() const;
 
diff --git a/printing/metafile_skia_unittest.cc b/printing/metafile_skia_unittest.cc
index af7fe816..8ab8d73 100644
--- a/printing/metafile_skia_unittest.cc
+++ b/printing/metafile_skia_unittest.cc
@@ -37,7 +37,8 @@
   // Finish creating the entire metafile.
   MetafileSkia metafile(mojom::SkiaDocumentType::kMSKP, 1);
   metafile.AppendPage(page_size, std::move(record));
-  metafile.AppendSubframeInfo(content_id, 2, std::move(pic_holder));
+  metafile.AppendSubframeInfo(content_id, base::UnguessableToken::Create(),
+                              std::move(pic_holder));
   metafile.FinishFrameContent();
   SkStreamAsset* metafile_stream = metafile.GetPdfData();
   ASSERT_TRUE(metafile_stream);
@@ -85,7 +86,6 @@
   constexpr int kPictureSideLen = 100;
   constexpr int kPageSideLen = 150;
   constexpr int kDocumentCookie = 1;
-  constexpr int kProxyId = 2;
   constexpr int kNumDocumentPages = 2;
 
   // The content tracking for serialization/deserialization.
@@ -155,7 +155,8 @@
     record->push<cc::DrawTextBlobOp>(text_blob3, 0, 0, ++node_id, flags_text);
 
     metafile.AppendPage(page_size, std::move(record));
-    metafile.AppendSubframeInfo(content_id, kProxyId, std::move(pic_holder));
+    metafile.AppendSubframeInfo(content_id, base::UnguessableToken::Create(),
+                                std::move(pic_holder));
     metafile.FinishFrameContent();
     SkStreamAsset* metafile_stream = metafile.GetPdfData();
     ASSERT_TRUE(metafile_stream);
diff --git a/remoting/base/BUILD.gn b/remoting/base/BUILD.gn
index ace3aa9..a3ac59d 100644
--- a/remoting/base/BUILD.gn
+++ b/remoting/base/BUILD.gn
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//third_party/protobuf/proto_library.gni")
+
 source_set("base") {
   sources = [
     "auto_thread.cc",
@@ -23,6 +25,12 @@
     "leaky_bucket.cc",
     "leaky_bucket.h",
     "name_value_map.h",
+    "protobuf_http_client.cc",
+    "protobuf_http_client.h",
+    "protobuf_http_request.cc",
+    "protobuf_http_request.h",
+    "protobuf_http_status.cc",
+    "protobuf_http_status.h",
     "rate_counter.cc",
     "rate_counter.h",
     "result.h",
@@ -145,6 +153,10 @@
   ]
 }
 
+proto_library("test_proto") {
+  sources = [ "protobuf_http_client_test_messages.proto" ]
+}
+
 source_set("unit_tests") {
   testonly = true
 
@@ -157,6 +169,7 @@
     "compound_buffer_unittest.cc",
     "leaky_bucket_unittest.cc",
     "oauth_token_getter_proxy_unittest.cc",
+    "protobuf_http_client_unittest.cc",
     "rate_counter_unittest.cc",
     "result_unittest.cc",
     "rsa_key_pair_unittest.cc",
@@ -173,6 +186,7 @@
   ]
 
   deps = [
+    ":test_proto",
     ":test_support",
     "//base",
     "//mojo/core/embedder",
diff --git a/remoting/base/DEPS b/remoting/base/DEPS
index e561196..da6fdba 100644
--- a/remoting/base/DEPS
+++ b/remoting/base/DEPS
@@ -4,9 +4,11 @@
   "+mojo/core/embedder",
   "+net",
   "+services/network/public/cpp",
+  "+services/network/test",
   "+third_party/breakpad",
   "+third_party/google_trust_services",
   "+third_party/grpc",
+  "+third_party/protobuf",
   "+third_party/zlib",
   "+ui/base",
 ]
diff --git a/remoting/base/protobuf_http_client.cc b/remoting/base/protobuf_http_client.cc
new file mode 100644
index 0000000..36d9900
--- /dev/null
+++ b/remoting/base/protobuf_http_client.cc
@@ -0,0 +1,119 @@
+// Copyright 2020 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 "remoting/base/protobuf_http_client.h"
+
+#include "base/strings/stringprintf.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "remoting/base/oauth_token_getter.h"
+#include "remoting/base/protobuf_http_request.h"
+#include "remoting/base/protobuf_http_status.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "third_party/protobuf/src/google/protobuf/message_lite.h"
+#include "url/gurl.h"
+
+namespace {
+
+constexpr char kAuthorizationHeaderFormat[] = "Authorization: Bearer %s";
+constexpr int kMaxResponseSizeKb = 512;
+
+}  // namespace
+
+namespace remoting {
+
+ProtobufHttpClient::ProtobufHttpClient(
+    const std::string& server_endpoint,
+    OAuthTokenGetter* token_getter,
+    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
+    : server_endpoint_(server_endpoint),
+      token_getter_(token_getter),
+      url_loader_factory_(url_loader_factory) {}
+
+ProtobufHttpClient::~ProtobufHttpClient() = default;
+
+void ProtobufHttpClient::ExecuteRequest(
+    std::unique_ptr<ProtobufHttpRequest> request) {
+  DCHECK(request->request_message);
+  DCHECK(!request->path.empty());
+  DCHECK(request->response_callback_);
+
+  if (!request->authenticated) {
+    DoExecuteRequest(std::move(request), OAuthTokenGetter::Status::SUCCESS, {},
+                     {});
+    return;
+  }
+
+  DCHECK(token_getter_);
+  token_getter_->CallWithToken(
+      base::BindOnce(&ProtobufHttpClient::DoExecuteRequest,
+                     weak_factory_.GetWeakPtr(), std::move(request)));
+}
+
+void ProtobufHttpClient::CancelPendingRequests() {
+  weak_factory_.InvalidateWeakPtrs();
+}
+
+void ProtobufHttpClient::DoExecuteRequest(
+    std::unique_ptr<ProtobufHttpRequest> request,
+    OAuthTokenGetter::Status status,
+    const std::string& user_email,
+    const std::string& access_token) {
+  if (status != OAuthTokenGetter::Status::SUCCESS) {
+    LOG(ERROR) << "Failed to fetch access token. Status: " << status;
+    request->OnResponse(
+        ProtobufHttpStatus(net::HttpStatusCode::HTTP_UNAUTHORIZED), nullptr);
+    return;
+  }
+
+  auto resource_request = std::make_unique<network::ResourceRequest>();
+  resource_request->url = GURL("https://" + server_endpoint_ + request->path);
+  resource_request->load_flags =
+      net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
+  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+  resource_request->method = net::HttpRequestHeaders::kPostMethod;
+
+  if (status == OAuthTokenGetter::Status::SUCCESS && !access_token.empty()) {
+    resource_request->headers.AddHeaderFromString(
+        base::StringPrintf(kAuthorizationHeaderFormat, access_token.c_str()));
+  } else {
+    VLOG(1) << "Attempting to execute request without access token";
+  }
+
+  std::unique_ptr<network::SimpleURLLoader> send_url_loader =
+      network::SimpleURLLoader::Create(std::move(resource_request),
+                                       request->traffic_annotation);
+  send_url_loader->SetTimeoutDuration(request->timeout_duration);
+  send_url_loader->AttachStringForUpload(
+      request->request_message->SerializeAsString(), "application/x-protobuf");
+  send_url_loader->DownloadToString(
+      url_loader_factory_.get(),
+      base::BindOnce(&ProtobufHttpClient::OnResponse,
+                     weak_factory_.GetWeakPtr(), std::move(request),
+                     std::move(send_url_loader)),
+      kMaxResponseSizeKb);
+}
+
+void ProtobufHttpClient::OnResponse(
+    std::unique_ptr<ProtobufHttpRequest> request,
+    std::unique_ptr<network::SimpleURLLoader> url_loader,
+    std::unique_ptr<std::string> response_body) {
+  net::Error net_error = static_cast<net::Error>(url_loader->NetError());
+  if (net_error == net::Error::ERR_HTTP_RESPONSE_CODE_FAILURE &&
+      (!url_loader->ResponseInfo() || !url_loader->ResponseInfo()->headers)) {
+    LOG(ERROR) << "Can't find response header.";
+    net_error = net::Error::ERR_INVALID_RESPONSE;
+  }
+  ProtobufHttpStatus status =
+      (net_error == net::Error::ERR_HTTP_RESPONSE_CODE_FAILURE ||
+       net_error == net::Error::OK)
+          ? ProtobufHttpStatus(static_cast<net::HttpStatusCode>(
+                url_loader->ResponseInfo()->headers->response_code()))
+          : ProtobufHttpStatus(net_error);
+  request->OnResponse(status, std::move(response_body));
+}
+
+}  // namespace remoting
diff --git a/remoting/base/protobuf_http_client.h b/remoting/base/protobuf_http_client.h
new file mode 100644
index 0000000..c830ba4
--- /dev/null
+++ b/remoting/base/protobuf_http_client.h
@@ -0,0 +1,66 @@
+// Copyright 2020 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 REMOTING_BASE_PROTOBUF_HTTP_CLIENT_H_
+#define REMOTING_BASE_PROTOBUF_HTTP_CLIENT_H_
+
+#include <memory>
+#include <string>
+
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "remoting/base/oauth_token_getter.h"
+
+namespace network {
+class SharedURLLoaderFactory;
+class SimpleURLLoader;
+}  // namespace network
+
+namespace remoting {
+
+struct ProtobufHttpRequest;
+
+// Helper class for executing REST/Protobuf requests over HTTP.
+class ProtobufHttpClient final {
+ public:
+  // |server_endpoint| is the hostname of the server.
+  // |token_getter| is nullable if none of the requests are authenticated.
+  // |token_getter| must outlive |this|.
+  ProtobufHttpClient(
+      const std::string& server_endpoint,
+      OAuthTokenGetter* token_getter,
+      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
+  ~ProtobufHttpClient();
+  ProtobufHttpClient(const ProtobufHttpClient&) = delete;
+  ProtobufHttpClient& operator=(const ProtobufHttpClient&) = delete;
+
+  // Executes a unary request. Caller will not be notified of the result if
+  // CancelPendingRequests() is called or |this| is destroyed.
+  void ExecuteRequest(std::unique_ptr<ProtobufHttpRequest> request);
+
+  // Tries to cancel all pending requests. Note that this prevents request
+  // callbacks from being called but does not necessarily stop pending requests
+  // from being sent.
+  void CancelPendingRequests();
+
+ private:
+  void DoExecuteRequest(std::unique_ptr<ProtobufHttpRequest> request,
+                        OAuthTokenGetter::Status status,
+                        const std::string& user_email,
+                        const std::string& access_token);
+
+  void OnResponse(std::unique_ptr<ProtobufHttpRequest> request,
+                  std::unique_ptr<network::SimpleURLLoader> url_loader,
+                  std::unique_ptr<std::string> response_body);
+
+  std::string server_endpoint_;
+  OAuthTokenGetter* token_getter_;
+  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
+  base::WeakPtrFactory<ProtobufHttpClient> weak_factory_{this};
+};
+
+}  // namespace remoting
+
+#endif  // REMOTING_BASE_PROTOBUF_HTTP_CLIENT_H_
\ No newline at end of file
diff --git a/remoting/base/protobuf_http_client_test_messages.proto b/remoting/base/protobuf_http_client_test_messages.proto
new file mode 100644
index 0000000..73711ad
--- /dev/null
+++ b/remoting/base/protobuf_http_client_test_messages.proto
@@ -0,0 +1,17 @@
+// Copyright 2020 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.
+
+syntax = "proto3";
+
+option optimize_for = LITE_RUNTIME;
+
+package remoting.protobufhttpclienttest;
+
+message EchoRequest {
+  string text = 1;
+}
+
+message EchoResponse {
+  string text = 1;
+}
diff --git a/remoting/base/protobuf_http_client_unittest.cc b/remoting/base/protobuf_http_client_unittest.cc
new file mode 100644
index 0000000..ea1ca714
--- /dev/null
+++ b/remoting/base/protobuf_http_client_unittest.cc
@@ -0,0 +1,238 @@
+// Copyright 2020 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 "remoting/base/protobuf_http_client.h"
+
+#include <memory>
+
+#include "base/run_loop.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/gmock_callback_support.h"
+#include "base/test/mock_callback.h"
+#include "base/test/task_environment.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "remoting/base/protobuf_http_client_test_messages.pb.h"
+#include "remoting/base/protobuf_http_request.h"
+#include "remoting/base/protobuf_http_status.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace remoting {
+
+namespace {
+
+using protobufhttpclienttest::EchoRequest;
+using protobufhttpclienttest::EchoResponse;
+
+using ::base::test::RunOnceCallback;
+using ::testing::_;
+
+using MockEchoResponseCallback =
+    base::MockCallback<base::OnceCallback<void(const ProtobufHttpStatus&,
+                                               std::unique_ptr<EchoResponse>)>>;
+
+constexpr char kTestServerEndpoint[] = "test.com";
+constexpr char kTestRpcPath[] = "/v1/echo:echo";
+constexpr char kTestFullUrl[] = "https://test.com/v1/echo:echo";
+constexpr char kRequestText[] = "This is a request";
+constexpr char kResponseText[] = "This is a response";
+constexpr char kAuthorizationHeaderKey[] = "Authorization";
+constexpr char kFakeAccessToken[] = "fake_access_token";
+constexpr char kFakeAccessTokenHeaderValue[] = "Bearer fake_access_token";
+
+MATCHER_P(HasStatusCode, status_code, "") {
+  return arg.http_status_code() == status_code;
+}
+
+MATCHER_P(HasNetError, net_error, "") {
+  return arg.net_error() == net_error;
+}
+
+MATCHER(IsResponseText, "") {
+  return arg->text() == kResponseText;
+}
+
+MATCHER(IsNullResponse, "") {
+  return arg.get() == nullptr;
+}
+
+class MockOAuthTokenGetter : public OAuthTokenGetter {
+ public:
+  MOCK_METHOD1(CallWithToken, void(TokenCallback));
+  MOCK_METHOD0(InvalidateCache, void());
+};
+
+std::unique_ptr<ProtobufHttpRequest> CreateDefaultTestRequest() {
+  auto request =
+      std::make_unique<ProtobufHttpRequest>(TRAFFIC_ANNOTATION_FOR_TESTS);
+  auto request_message = std::make_unique<EchoRequest>();
+  request_message->set_text(kRequestText);
+  request->request_message = std::move(request_message);
+  request->SetResponseCallback(
+      base::DoNothing::Once<const ProtobufHttpStatus&,
+                            std::unique_ptr<EchoResponse>>());
+  request->path = kTestRpcPath;
+  return request;
+}
+
+std::string CreateDefaultResponseContent() {
+  EchoResponse response;
+  response.set_text(kResponseText);
+  return response.SerializeAsString();
+}
+
+}  // namespace
+
+class ProtobufHttpClientTest : public testing::Test {
+ protected:
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  MockOAuthTokenGetter mock_token_getter_;
+  network::TestURLLoaderFactory test_url_loader_factory_;
+  scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_ =
+      base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+          &test_url_loader_factory_);
+  ProtobufHttpClient client_{kTestServerEndpoint, &mock_token_getter_,
+                             test_shared_loader_factory_};
+};
+
+TEST_F(ProtobufHttpClientTest, SendRequestAndDecodeResponse) {
+  base::RunLoop run_loop;
+
+  EXPECT_CALL(mock_token_getter_, CallWithToken(_))
+      .WillOnce(RunOnceCallback<0>(OAuthTokenGetter::Status::SUCCESS, "",
+                                   kFakeAccessToken));
+
+  MockEchoResponseCallback response_callback;
+  EXPECT_CALL(response_callback,
+              Run(HasStatusCode(net::HTTP_OK), IsResponseText()))
+      .WillOnce([&]() { run_loop.Quit(); });
+
+  auto request = CreateDefaultTestRequest();
+  request->SetResponseCallback(response_callback.Get());
+  client_.ExecuteRequest(std::move(request));
+
+  // Verify request.
+  ASSERT_TRUE(test_url_loader_factory_.IsPending(kTestFullUrl));
+  ASSERT_EQ(1, test_url_loader_factory_.NumPending());
+  auto* pending_request = test_url_loader_factory_.GetPendingRequest(0);
+  std::string auth_header;
+  ASSERT_TRUE(pending_request->request.headers.GetHeader(
+      kAuthorizationHeaderKey, &auth_header));
+  ASSERT_EQ(kFakeAccessTokenHeaderValue, auth_header);
+  auto& data_element =
+      pending_request->request.request_body->elements()->front();
+  std::string request_body_data =
+      std::string(data_element.bytes(), data_element.length());
+  EchoRequest request_message;
+  ASSERT_TRUE(request_message.ParseFromString(request_body_data));
+  ASSERT_EQ(kRequestText, request_message.text());
+
+  // Respond.
+  test_url_loader_factory_.AddResponse(kTestFullUrl,
+                                       CreateDefaultResponseContent());
+  run_loop.Run();
+}
+
+TEST_F(ProtobufHttpClientTest,
+       SendUnauthenticatedRequest_TokenGetterNotCalled) {
+  EXPECT_CALL(mock_token_getter_, CallWithToken(_)).Times(0);
+
+  auto request = CreateDefaultTestRequest();
+  request->authenticated = false;
+  client_.ExecuteRequest(std::move(request));
+
+  // Verify that the request is sent with no auth header.
+  ASSERT_TRUE(test_url_loader_factory_.IsPending(kTestFullUrl));
+  ASSERT_EQ(1, test_url_loader_factory_.NumPending());
+  auto* pending_request = test_url_loader_factory_.GetPendingRequest(0);
+  ASSERT_FALSE(
+      pending_request->request.headers.HasHeader(kAuthorizationHeaderKey));
+}
+
+TEST_F(ProtobufHttpClientTest,
+       FailedToFetchAuthToken_RejectsWithUnauthorizedError) {
+  base::RunLoop run_loop;
+
+  EXPECT_CALL(mock_token_getter_, CallWithToken(_))
+      .WillOnce(
+          RunOnceCallback<0>(OAuthTokenGetter::Status::AUTH_ERROR, "", ""));
+
+  MockEchoResponseCallback response_callback;
+  EXPECT_CALL(response_callback,
+              Run(HasStatusCode(net::HTTP_UNAUTHORIZED), IsNullResponse()))
+      .WillOnce([&]() { run_loop.Quit(); });
+
+  auto request = CreateDefaultTestRequest();
+  request->SetResponseCallback(response_callback.Get());
+  client_.ExecuteRequest(std::move(request));
+
+  run_loop.Run();
+}
+
+TEST_F(ProtobufHttpClientTest, FailedToParseResponse_GetsInvalidResponseError) {
+  base::RunLoop run_loop;
+
+  EXPECT_CALL(mock_token_getter_, CallWithToken(_))
+      .WillOnce(RunOnceCallback<0>(OAuthTokenGetter::Status::SUCCESS, "",
+                                   kFakeAccessToken));
+
+  MockEchoResponseCallback response_callback;
+  EXPECT_CALL(
+      response_callback,
+      Run(HasNetError(net::Error::ERR_INVALID_RESPONSE), IsNullResponse()))
+      .WillOnce([&]() { run_loop.Quit(); });
+
+  auto request = CreateDefaultTestRequest();
+  request->SetResponseCallback(response_callback.Get());
+  client_.ExecuteRequest(std::move(request));
+
+  // Respond.
+  test_url_loader_factory_.AddResponse(kTestFullUrl, "Invalid content");
+  run_loop.Run();
+}
+
+TEST_F(ProtobufHttpClientTest, ServerRespondsWithError) {
+  base::RunLoop run_loop;
+
+  EXPECT_CALL(mock_token_getter_, CallWithToken(_))
+      .WillOnce(RunOnceCallback<0>(OAuthTokenGetter::Status::SUCCESS, "", ""));
+
+  MockEchoResponseCallback response_callback;
+  EXPECT_CALL(response_callback,
+              Run(HasStatusCode(net::HTTP_UNAUTHORIZED), IsNullResponse()))
+      .WillOnce([&]() { run_loop.Quit(); });
+
+  auto request = CreateDefaultTestRequest();
+  request->SetResponseCallback(response_callback.Get());
+  client_.ExecuteRequest(std::move(request));
+
+  test_url_loader_factory_.AddResponse(kTestFullUrl, "",
+                                       net::HttpStatusCode::HTTP_UNAUTHORIZED);
+  run_loop.Run();
+}
+
+TEST_F(ProtobufHttpClientTest, CancelPendingRequests_CallbackNotCalled) {
+  base::RunLoop run_loop;
+
+  OAuthTokenGetter::TokenCallback token_callback;
+  EXPECT_CALL(mock_token_getter_, CallWithToken(_))
+      .WillOnce([&](OAuthTokenGetter::TokenCallback callback) {
+        token_callback = std::move(callback);
+      });
+
+  auto request = CreateDefaultTestRequest();
+  client_.ExecuteRequest(std::move(request));
+  client_.CancelPendingRequests();
+  ASSERT_TRUE(token_callback);
+  std::move(token_callback)
+      .Run(OAuthTokenGetter::Status::SUCCESS, "", kFakeAccessToken);
+
+  // Verify no request.
+  ASSERT_FALSE(test_url_loader_factory_.IsPending(kTestFullUrl));
+}
+
+}  // namespace remoting
diff --git a/remoting/base/protobuf_http_request.cc b/remoting/base/protobuf_http_request.cc
new file mode 100644
index 0000000..1862dc7b
--- /dev/null
+++ b/remoting/base/protobuf_http_request.cc
@@ -0,0 +1,35 @@
+// Copyright 2020 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 "remoting/base/protobuf_http_request.h"
+
+namespace remoting {
+
+ProtobufHttpRequest::ProtobufHttpRequest(
+    const net::NetworkTrafficAnnotationTag& traffic_annotation)
+    : traffic_annotation(traffic_annotation) {}
+
+ProtobufHttpRequest::~ProtobufHttpRequest() = default;
+
+void ProtobufHttpRequest::OnResponse(
+    const ProtobufHttpStatus& status,
+    std::unique_ptr<std::string> response_body) {
+  std::move(response_callback_)
+      .Run(status.ok() ? ParseResponse(std::move(response_body)) : status);
+}
+
+ProtobufHttpStatus ProtobufHttpRequest::ParseResponse(
+    std::unique_ptr<std::string> response_body) {
+  if (!response_body) {
+    LOG(ERROR) << "Server returned no response body";
+    return ProtobufHttpStatus(net::ERR_EMPTY_RESPONSE);
+  }
+  if (!response_message_->ParseFromString(*response_body)) {
+    LOG(ERROR) << "Failed to parse response body";
+    return ProtobufHttpStatus(net::ERR_INVALID_RESPONSE);
+  }
+  return ProtobufHttpStatus::OK;
+}
+
+}  // namespace remoting
diff --git a/remoting/base/protobuf_http_request.h b/remoting/base/protobuf_http_request.h
new file mode 100644
index 0000000..61b80bb
--- /dev/null
+++ b/remoting/base/protobuf_http_request.h
@@ -0,0 +1,73 @@
+// Copyright 2020 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 REMOTING_BASE_PROTOBUF_HTTP_REQUEST_H_
+#define REMOTING_BASE_PROTOBUF_HTTP_REQUEST_H_
+
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/time/time.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "remoting/base/protobuf_http_status.h"
+#include "third_party/protobuf/src/google/protobuf/message_lite.h"
+
+namespace remoting {
+
+// A simple unary request. Caller needs to set all public members and call
+// SetResponseCallback() before passing it to ProtobufHttpClient.
+struct ProtobufHttpRequest final {
+  template <typename ResponseType>
+  using ResponseCallback =
+      base::OnceCallback<void(const ProtobufHttpStatus& status,
+                              std::unique_ptr<ResponseType> response)>;
+
+  explicit ProtobufHttpRequest(
+      const net::NetworkTrafficAnnotationTag& traffic_annotation);
+  ~ProtobufHttpRequest();
+
+  const net::NetworkTrafficAnnotationTag traffic_annotation;
+  std::unique_ptr<google::protobuf::MessageLite> request_message;
+  std::string path;
+  bool authenticated = true;
+  base::TimeDelta timeout_duration = base::TimeDelta::FromSeconds(30);
+
+  // Sets the response callback. |ResponseType| needs to be a protobuf message
+  // type.
+  template <typename ResponseType>
+  void SetResponseCallback(ResponseCallback<ResponseType> callback) {
+    auto response = std::make_unique<ResponseType>();
+    response_message_ = response.get();
+    response_callback_ = base::BindOnce(
+        [](std::unique_ptr<ResponseType> response,
+           ResponseCallback<ResponseType> callback,
+           const ProtobufHttpStatus& status) {
+          if (!status.ok()) {
+            response.reset();
+          }
+          std::move(callback).Run(status, std::move(response));
+        },
+        std::move(response), std::move(callback));
+  }
+
+ private:
+  friend class ProtobufHttpClient;
+
+  // To be called by ProtobufHttpClient.
+  void OnResponse(const ProtobufHttpStatus& status,
+                  std::unique_ptr<std::string> response_body);
+
+  // Parses |response_body| and writes it to |response_message_|.
+  ProtobufHttpStatus ParseResponse(std::unique_ptr<std::string> response_body);
+
+  // This is owned by |response_callback_|.
+  google::protobuf::MessageLite* response_message_;
+  base::OnceCallback<void(const ProtobufHttpStatus&)> response_callback_;
+};
+
+}  // namespace remoting
+
+#endif  // REMOTING_BASE_PROTOBUF_HTTP_REQUEST_H_
diff --git a/remoting/base/protobuf_http_status.cc b/remoting/base/protobuf_http_status.cc
new file mode 100644
index 0000000..7ea57ee6
--- /dev/null
+++ b/remoting/base/protobuf_http_status.cc
@@ -0,0 +1,36 @@
+// Copyright 2020 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 "remoting/base/protobuf_http_status.h"
+
+#include "net/http/http_status_code.h"
+
+namespace remoting {
+
+const ProtobufHttpStatus& ProtobufHttpStatus::OK =
+    ProtobufHttpStatus(net::HttpStatusCode::HTTP_OK);
+
+ProtobufHttpStatus::ProtobufHttpStatus(net::HttpStatusCode http_status_code)
+    : http_status_code_(http_status_code),
+      net_error_(net::Error::ERR_HTTP_RESPONSE_CODE_FAILURE),
+      error_message_(net::GetHttpReasonPhrase(http_status_code)) {
+  DCHECK_LE(0, http_status_code) << "Invalid http status code";
+}
+
+ProtobufHttpStatus::ProtobufHttpStatus(net::Error net_error)
+    : http_status_code_(-1),
+      net_error_(net_error),
+      error_message_(net::ErrorToString(net_error)) {
+  DCHECK_NE(net::Error::OK, net_error) << "Use the HttpStatusCode overload";
+  DCHECK_NE(net::Error::ERR_HTTP_RESPONSE_CODE_FAILURE, net_error)
+      << "Use the HttpStatusCode overload";
+}
+
+ProtobufHttpStatus::~ProtobufHttpStatus() = default;
+
+bool ProtobufHttpStatus::ok() const {
+  return http_status_code_ == net::HttpStatusCode::HTTP_OK;
+}
+
+}  // namespace remoting
diff --git a/remoting/base/protobuf_http_status.h b/remoting/base/protobuf_http_status.h
new file mode 100644
index 0000000..c75098a
--- /dev/null
+++ b/remoting/base/protobuf_http_status.h
@@ -0,0 +1,46 @@
+// Copyright 2020 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 REMOTING_BASE_PROTOBUF_HTTP_STATUS_H_
+#define REMOTING_BASE_PROTOBUF_HTTP_STATUS_H_
+
+#include <string>
+
+#include "net/base/net_errors.h"
+#include "net/http/http_status_code.h"
+
+namespace remoting {
+
+class ProtobufHttpStatus {
+ public:
+  // An OK pre-defined instance.
+  static const ProtobufHttpStatus& OK;
+
+  explicit ProtobufHttpStatus(net::HttpStatusCode http_status_code);
+  explicit ProtobufHttpStatus(net::Error net_error);
+  ~ProtobufHttpStatus();
+
+  // Indicates whether the http request was successful based on the status code.
+  bool ok() const;
+
+  // The http status code, or -1 if the request fails to make, in which case
+  // the underlying error can be found by calling net_error().
+  int http_status_code() const { return http_status_code_; }
+
+  // The net error. If the error is ERR_HTTP_RESPONSE_CODE_FAILURE, the status
+  // code can be retrieved by calling http_status_code().
+  net::Error net_error() const { return net_error_; }
+
+  // The message that describes the error.
+  const std::string& error_message() const { return error_message_; }
+
+ private:
+  int http_status_code_;
+  net::Error net_error_;
+  std::string error_message_;
+};
+
+}  // namespace remoting
+
+#endif  // REMOTING_BASE_PROTOBUF_HTTP_STATUS_H_
diff --git a/services/network/chunked_data_pipe_upload_data_stream.cc b/services/network/chunked_data_pipe_upload_data_stream.cc
index 305bfa2e..46f15bd 100644
--- a/services/network/chunked_data_pipe_upload_data_stream.cc
+++ b/services/network/chunked_data_pipe_upload_data_stream.cc
@@ -49,9 +49,13 @@
     return net::ERR_FAILED;
 
   // Get a new data pipe and start.
-  mojo::DataPipe data_pipe;
-  chunked_data_pipe_getter_->StartReading(std::move(data_pipe.producer_handle));
-  data_pipe_ = std::move(data_pipe.consumer_handle);
+  mojo::ScopedDataPipeProducerHandle data_pipe_producer;
+  mojo::ScopedDataPipeConsumerHandle data_pipe_consumer;
+  MojoResult result =
+      mojo::CreateDataPipe(nullptr, &data_pipe_producer, &data_pipe_consumer);
+  DCHECK_EQ(MOJO_RESULT_OK, result);
+  chunked_data_pipe_getter_->StartReading(std::move(data_pipe_producer));
+  data_pipe_ = std::move(data_pipe_consumer);
 
   return net::OK;
 }
diff --git a/services/network/cors/preflight_controller_unittest.cc b/services/network/cors/preflight_controller_unittest.cc
index c252df4..1a07727 100644
--- a/services/network/cors/preflight_controller_unittest.cc
+++ b/services/network/cors/preflight_controller_unittest.cc
@@ -300,7 +300,7 @@
       int32_t process_id,
       int32_t routing_id,
       const std::string& devtools_request_id,
-      const net::CookieAndLineStatusList& cookies_with_status,
+      const net::CookieAndLineAccessResultList& cookies_with_access_result,
       std::vector<network::mojom::HttpRawHeaderPairPtr> headers,
       const base::Optional<std::string>& raw_response_headers) override {
     on_raw_response_called_ = true;
diff --git a/services/network/public/cpp/cookie_manager_mojom_traits.cc b/services/network/public/cpp/cookie_manager_mojom_traits.cc
index 25380c44..72bad8b 100644
--- a/services/network/public/cpp/cookie_manager_mojom_traits.cc
+++ b/services/network/public/cpp/cookie_manager_mojom_traits.cc
@@ -408,37 +408,21 @@
   return out->IsValid();
 }
 
-bool StructTraits<
-    network::mojom::CookieWithStatusDataView,
-    net::CookieWithStatus>::Read(network::mojom::CookieWithStatusDataView c,
-                                 net::CookieWithStatus* out) {
-  net::CanonicalCookie cookie;
-  net::CookieInclusionStatus status;
-  if (!c.ReadCookie(&cookie))
-    return false;
-  if (!c.ReadStatus(&status))
-    return false;
-
-  *out = {cookie, status};
-
-  return true;
-}
-
-bool StructTraits<network::mojom::CookieAndLineWithStatusDataView,
-                  net::CookieAndLineWithStatus>::
-    Read(network::mojom::CookieAndLineWithStatusDataView c,
-         net::CookieAndLineWithStatus* out) {
+bool StructTraits<network::mojom::CookieAndLineWithAccessResultDataView,
+                  net::CookieAndLineWithAccessResult>::
+    Read(network::mojom::CookieAndLineWithAccessResultDataView c,
+         net::CookieAndLineWithAccessResult* out) {
   base::Optional<net::CanonicalCookie> cookie;
   std::string cookie_string;
-  net::CookieInclusionStatus status;
+  net::CookieAccessResult access_result;
   if (!c.ReadCookie(&cookie))
     return false;
   if (!c.ReadCookieString(&cookie_string))
     return false;
-  if (!c.ReadStatus(&status))
+  if (!c.ReadAccessResult(&access_result))
     return false;
 
-  *out = {cookie, cookie_string, status};
+  *out = {cookie, cookie_string, access_result};
 
   return true;
 }
diff --git a/services/network/public/cpp/cookie_manager_mojom_traits.h b/services/network/public/cpp/cookie_manager_mojom_traits.h
index 8b5243c..09175c0b 100644
--- a/services/network/public/cpp/cookie_manager_mojom_traits.h
+++ b/services/network/public/cpp/cookie_manager_mojom_traits.h
@@ -168,36 +168,22 @@
 };
 
 template <>
-struct StructTraits<network::mojom::CookieWithStatusDataView,
-                    net::CookieWithStatus> {
-  static const net::CanonicalCookie& cookie(const net::CookieWithStatus& c) {
-    return c.cookie;
-  }
-  static const net::CookieInclusionStatus& status(
-      const net::CookieWithStatus& c) {
-    return c.status;
-  }
-  static bool Read(network::mojom::CookieWithStatusDataView cookie,
-                   net::CookieWithStatus* out);
-};
-
-template <>
-struct StructTraits<network::mojom::CookieAndLineWithStatusDataView,
-                    net::CookieAndLineWithStatus> {
+struct StructTraits<network::mojom::CookieAndLineWithAccessResultDataView,
+                    net::CookieAndLineWithAccessResult> {
   static const base::Optional<net::CanonicalCookie>& cookie(
-      const net::CookieAndLineWithStatus& c) {
+      const net::CookieAndLineWithAccessResult& c) {
     return c.cookie;
   }
   static const std::string& cookie_string(
-      const net::CookieAndLineWithStatus& c) {
+      const net::CookieAndLineWithAccessResult& c) {
     return c.cookie_string;
   }
-  static const net::CookieInclusionStatus& status(
-      const net::CookieAndLineWithStatus& c) {
-    return c.status;
+  static const net::CookieAccessResult& access_result(
+      const net::CookieAndLineWithAccessResult& c) {
+    return c.access_result;
   }
-  static bool Read(network::mojom::CookieAndLineWithStatusDataView cookie,
-                   net::CookieAndLineWithStatus* out);
+  static bool Read(network::mojom::CookieAndLineWithAccessResultDataView cookie,
+                   net::CookieAndLineWithAccessResult* out);
 };
 
 template <>
diff --git a/services/network/public/cpp/cookie_manager_mojom_traits_unittest.cc b/services/network/public/cpp/cookie_manager_mojom_traits_unittest.cc
index b6d6168..4de3a452 100644
--- a/services/network/public/cpp/cookie_manager_mojom_traits_unittest.cc
+++ b/services/network/public/cpp/cookie_manager_mojom_traits_unittest.cc
@@ -92,19 +92,41 @@
           &invalid, &copied));
 }
 
-TEST(CookieManagerTraitsTest, Roundtrips_CookieWithStatus) {
+TEST(CookieManagerTraitsTest, Rountrips_CookieAccessResult) {
+  net::CookieAccessResult original = net::CookieAccessResult(
+      net::CookieEffectiveSameSite::LAX_MODE,
+      net::CookieInclusionStatus(
+          net::CookieInclusionStatus::
+              EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX,
+          net::CookieInclusionStatus::
+              WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT));
+  net::CookieAccessResult copied;
+
+  EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::CookieAccessResult>(
+      &original, &copied));
+
+  EXPECT_EQ(original.effective_same_site, copied.effective_same_site);
+  EXPECT_TRUE(copied.status.HasExactlyExclusionReasonsForTesting(
+      {net::CookieInclusionStatus::
+           EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX}));
+  EXPECT_TRUE(copied.status.HasExactlyWarningReasonsForTesting(
+      {net::CookieInclusionStatus::
+           WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT}));
+}
+
+TEST(CookieManagerTraitsTest, Rountrips_CookieWithAccessResult) {
   net::CanonicalCookie original_cookie(
       "A", "B", "x.y", "/path", base::Time(), base::Time(), base::Time(),
       /* secure = */ true, /* http_only = */ false,
       net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_LOW);
 
-  net::CookieWithStatus original = {original_cookie,
-                                    net::CookieInclusionStatus()};
+  net::CookieWithAccessResult original = {original_cookie,
+                                          net::CookieAccessResult()};
+  net::CookieWithAccessResult copied;
 
-  net::CookieWithStatus copied;
-
-  EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::CookieWithStatus>(
-      &original, &copied));
+  EXPECT_TRUE(
+      mojo::test::SerializeAndDeserialize<mojom::CookieWithAccessResult>(
+          &original, &copied));
 
   EXPECT_EQ(original.cookie.Name(), copied.cookie.Name());
   EXPECT_EQ(original.cookie.Value(), copied.cookie.Value());
@@ -117,7 +139,39 @@
   EXPECT_EQ(original.cookie.IsHttpOnly(), copied.cookie.IsHttpOnly());
   EXPECT_EQ(original.cookie.SameSite(), copied.cookie.SameSite());
   EXPECT_EQ(original.cookie.Priority(), copied.cookie.Priority());
-  EXPECT_EQ(original.status, copied.status);
+  EXPECT_EQ(original.access_result.effective_same_site,
+            copied.access_result.effective_same_site);
+  EXPECT_EQ(original.access_result.status, copied.access_result.status);
+}
+
+TEST(CookieManagerTraitsTest, Rountrips_CookieAndLineWithAccessResult) {
+  net::CanonicalCookie original_cookie(
+      "A", "B", "x.y", "/path", base::Time(), base::Time(), base::Time(),
+      /* secure = */ true, /* http_only = */ false,
+      net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_LOW);
+
+  net::CookieAndLineWithAccessResult original(original_cookie, "cookie-string",
+                                              net::CookieAccessResult());
+  net::CookieAndLineWithAccessResult copied;
+
+  EXPECT_TRUE(
+      mojo::test::SerializeAndDeserialize<mojom::CookieAndLineWithAccessResult>(
+          &original, &copied));
+
+  EXPECT_EQ(original.cookie->Name(), copied.cookie->Name());
+  EXPECT_EQ(original.cookie->Value(), copied.cookie->Value());
+  EXPECT_EQ(original.cookie->Domain(), copied.cookie->Domain());
+  EXPECT_EQ(original.cookie->Path(), copied.cookie->Path());
+  EXPECT_EQ(original.cookie->CreationDate(), copied.cookie->CreationDate());
+  EXPECT_EQ(original.cookie->LastAccessDate(), copied.cookie->LastAccessDate());
+  EXPECT_EQ(original.cookie->ExpiryDate(), copied.cookie->ExpiryDate());
+  EXPECT_EQ(original.cookie->IsSecure(), copied.cookie->IsSecure());
+  EXPECT_EQ(original.cookie->IsHttpOnly(), copied.cookie->IsHttpOnly());
+  EXPECT_EQ(original.cookie->SameSite(), copied.cookie->SameSite());
+  EXPECT_EQ(original.cookie->Priority(), copied.cookie->Priority());
+  EXPECT_EQ(original.access_result.effective_same_site,
+            copied.access_result.effective_same_site);
+  EXPECT_EQ(original.cookie_string, copied.cookie_string);
 }
 
 TEST(CookieManagerTraitsTest, Roundtrips_CookieSameSite) {
diff --git a/services/network/public/mojom/BUILD.gn b/services/network/public/mojom/BUILD.gn
index 942faadd..355d7e1 100644
--- a/services/network/public/mojom/BUILD.gn
+++ b/services/network/public/mojom/BUILD.gn
@@ -385,10 +385,6 @@
           move_only = true
         },
         {
-          mojom = "network.mojom.CookieWithStatus"
-          cpp = "::net::CookieWithStatus"
-        },
-        {
           mojom = "network.mojom.CookieAccessResult"
           cpp = "::net::CookieAccessResult"
           move_only = true
@@ -398,8 +394,8 @@
           cpp = "::net::CookieWithAccessResult"
         },
         {
-          mojom = "network.mojom.CookieAndLineWithStatus"
-          cpp = "::net::CookieAndLineWithStatus"
+          mojom = "network.mojom.CookieAndLineWithAccessResult"
+          cpp = "::net::CookieAndLineWithAccessResult"
         },
         {
           mojom = "network.mojom.CookieChangeCause"
diff --git a/services/network/public/mojom/cookie_access_observer.mojom b/services/network/public/mojom/cookie_access_observer.mojom
index 8cf5dd7..04dc1ae 100644
--- a/services/network/public/mojom/cookie_access_observer.mojom
+++ b/services/network/public/mojom/cookie_access_observer.mojom
@@ -23,7 +23,7 @@
   url.mojom.Url url;
 
   SiteForCookies site_for_cookies;
-  array<CookieWithStatus> cookie_list;
+  array<CookieWithAccessResult> cookie_list;
 
   // |devtools_request_id| contains the DevTools request id of the request
   // that triggered the cookie change, if the read was triggered by a request.
diff --git a/services/network/public/mojom/cookie_manager.mojom b/services/network/public/mojom/cookie_manager.mojom
index 1f76bc9..64251a7 100644
--- a/services/network/public/mojom/cookie_manager.mojom
+++ b/services/network/public/mojom/cookie_manager.mojom
@@ -142,15 +142,10 @@
   uint32 warning_reasons;
 };
 
-struct CookieWithStatus {
-  CanonicalCookie cookie;
-  CookieInclusionStatus status;
-};
-
-struct CookieAndLineWithStatus {
+struct CookieAndLineWithAccessResult {
   CanonicalCookie? cookie;
   string cookie_string;
-  CookieInclusionStatus status;
+  CookieAccessResult access_result;
 };
 
 struct CookieAccessResult {
diff --git a/services/network/public/mojom/network_service.mojom b/services/network/public/mojom/network_service.mojom
index 6a016cc..3d3ac9a 100644
--- a/services/network/public/mojom/network_service.mojom
+++ b/services/network/public/mojom/network_service.mojom
@@ -74,7 +74,7 @@
     int32 process_id,
     int32 routing_id,
     string devtool_request_id,
-    array<CookieAndLineWithStatus> cookies_with_status,
+    array<CookieAndLineWithAccessResult> cookies_with_access_result,
     array<HttpRawHeaderPair> headers,
     string? raw_response_headers);
 
diff --git a/services/network/public/mojom/network_service_test.mojom b/services/network/public/mojom/network_service_test.mojom
index 70a8683..895e88c 100644
--- a/services/network/public/mojom/network_service_test.mojom
+++ b/services/network/public/mojom/network_service_test.mojom
@@ -112,4 +112,9 @@
   // Activates the specified field trial. Intended for use verifying that the
   // network service informs the main process when a field trial is activated.
   ActivateFieldTrial(string field_trial_name);
+
+  // Instantiates a net::ScopedTestEVPolicy with the specified fingerprint and
+  // policy oid.
+  [Sync]
+  SetEVPolicy(array<uint8, 32> fingerprint_sha256, string policy_oid) => ();
 };
diff --git a/services/network/restricted_cookie_manager.cc b/services/network/restricted_cookie_manager.cc
index 2e2b4a0..9cd9f13 100644
--- a/services/network/restricted_cookie_manager.cc
+++ b/services/network/restricted_cookie_manager.cc
@@ -20,6 +20,7 @@
 #include "mojo/public/cpp/bindings/message.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "net/cookies/cookie_access_result.h"
 #include "net/cookies/cookie_constants.h"
 #include "net/cookies/cookie_options.h"
 #include "net/cookies/cookie_store.h"
@@ -84,8 +85,9 @@
   return options;
 }
 
-void MarkSameSiteCompatPairs(std::vector<net::CookieWithStatus>& cookie_list,
-                             const net::CookieOptions& options) {
+void MarkSameSiteCompatPairs(
+    std::vector<net::CookieWithAccessResult>& cookie_list,
+    const net::CookieOptions& options) {
   // If the context is same-site then there cannot be any SameSite-by-default
   // warnings, so the compat pair warning is irrelevant.
   if (options.same_site_cookie_context().GetContextForCookieInclusion() >
@@ -100,9 +102,9 @@
     for (size_t j = i + 1; j < cookie_list.size(); ++j) {
       const net::CanonicalCookie& c2 = cookie_list[j].cookie;
       if (net::cookie_util::IsSameSiteCompatPair(c1, c2, options)) {
-        cookie_list[i].status.AddWarningReason(
+        cookie_list[i].access_result.status.AddWarningReason(
             net::CookieInclusionStatus::WARN_SAMESITE_COMPAT_PAIR);
-        cookie_list[j].status.AddWarningReason(
+        cookie_list[j].access_result.status.AddWarningReason(
             net::CookieInclusionStatus::WARN_SAMESITE_COMPAT_PAIR);
       }
     }
@@ -269,20 +271,18 @@
       url, site_for_cookies.RepresentativeUrl(), top_frame_origin);
 
   std::vector<net::CookieWithAccessResult> result;
-  std::vector<net::CookieWithStatus> result_with_status;
+  std::vector<net::CookieWithAccessResult> on_cookies_accessed_result;
 
   // TODO(https://crbug.com/977040): Remove once samesite tightening up is
   // rolled out.
-  // |result_with_status| is populated with excluded cookies here based on
-  // warnings present before WARN_SAMESITE_COMPAT_PAIR can be applied by
+  // |on_cookies_accessed_result| is populated with excluded cookies here based
+  // on warnings present before WARN_SAMESITE_COMPAT_PAIR can be applied by
   // MarkSameSiteCompatPairs(). This is ok because WARN_SAMESITE_COMPAT_PAIR is
   // irrelevant unless WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT is already
   // present.
   for (const auto& cookie_and_access_result : excluded_cookies) {
     if (cookie_and_access_result.access_result.status.ShouldWarn()) {
-      result_with_status.push_back(
-          {cookie_and_access_result.cookie,
-           cookie_and_access_result.access_result.status});
+      on_cookies_accessed_result.push_back(cookie_and_access_result);
     }
   }
 
@@ -293,7 +293,7 @@
   // TODO(https://crbug.com/993843): Use the statuses passed in |cookie_list|.
   for (const net::CookieWithAccessResult& cookie_item : cookie_list) {
     const net::CanonicalCookie& cookie = cookie_item.cookie;
-    net::CookieInclusionStatus status = cookie_item.access_result.status;
+    net::CookieAccessResult access_result = cookie_item.access_result;
     const std::string& cookie_name = cookie.Name();
 
     if (match_type == mojom::CookieMatchType::EQUALS) {
@@ -309,22 +309,22 @@
     }
 
     if (blocked) {
-      status.AddExclusionReason(
+      access_result.status.AddExclusionReason(
           net::CookieInclusionStatus::EXCLUDE_USER_PREFERENCES);
     } else {
       result.push_back(cookie_item);
     }
-    result_with_status.push_back({cookie, status});
+    on_cookies_accessed_result.push_back({cookie, access_result});
   }
 
   if (cookie_observer_) {
-    // Mark the CookieInclusionStatuses of items in |result_with_status| if they
-    // are part of a presumed SameSite compatibility pair.
-    MarkSameSiteCompatPairs(result_with_status, net_options);
+    // Mark the CookieInclusionStatuses of items in |result_with_access_result|
+    // if they are part of a presumed SameSite compatibility pair.
+    MarkSameSiteCompatPairs(on_cookies_accessed_result, net_options);
 
     cookie_observer_->OnCookiesAccessed(mojom::CookieAccessDetails::New(
         mojom::CookieAccessDetails::Type::kRead, url, site_for_cookies,
-        result_with_status, base::nullopt));
+        on_cookies_accessed_result, base::nullopt));
   }
 
   if (blocked) {
@@ -376,11 +376,11 @@
 
   if (!status.IsInclude()) {
     if (cookie_observer_) {
-      std::vector<net::CookieWithStatus> result_with_status = {
-          {cookie, status}};
+      std::vector<net::CookieWithAccessResult> result_with_access_result = {
+          {cookie, net::CookieAccessResult(status)}};
       cookie_observer_->OnCookiesAccessed(mojom::CookieAccessDetails::New(
           mojom::CookieAccessDetails::Type::kChange, url, site_for_cookies,
-          result_with_status, base::nullopt));
+          result_with_access_result, base::nullopt));
     }
     std::move(callback).Run(false);
     return;
@@ -421,7 +421,7 @@
     const net::CookieOptions& net_options,
     SetCanonicalCookieCallback user_callback,
     net::CookieAccessResult access_result) {
-  std::vector<net::CookieWithStatus> notify;
+  std::vector<net::CookieWithAccessResult> notify;
   // TODO(https://crbug.com/977040): Only report pure INCLUDE once samesite
   // tightening up is rolled out.
   DCHECK(!access_result.status.HasExclusionReason(
@@ -429,7 +429,7 @@
 
   if (access_result.status.IsInclude() || access_result.status.ShouldWarn()) {
     if (cookie_observer_) {
-      notify.push_back({cookie, access_result.status});
+      notify.push_back({cookie, access_result});
       cookie_observer_->OnCookiesAccessed(mojom::CookieAccessDetails::New(
           mojom::CookieAccessDetails::Type::kChange, url, site_for_cookies,
           notify, base::nullopt));
diff --git a/services/network/restricted_cookie_manager_unittest.cc b/services/network/restricted_cookie_manager_unittest.cc
index 48e9f0d..dd78531 100644
--- a/services/network/restricted_cookie_manager_unittest.cc
+++ b/services/network/restricted_cookie_manager_unittest.cc
@@ -60,13 +60,13 @@
   }
 
   void OnCookiesAccessed(mojom::CookieAccessDetailsPtr details) override {
-    for (const auto& cookie_and_status : details->cookie_list) {
+    for (const auto& cookie_and_access_result : details->cookie_list) {
       CookieOp op;
       op.get = details->type == mojom::CookieAccessDetails::Type::kRead;
       op.url = details->url;
       op.site_for_cookies = details->site_for_cookies.RepresentativeUrl();
-      op.cookie.push_back(cookie_and_status.cookie);
-      op.status = cookie_and_status.status;
+      op.cookie.push_back(cookie_and_access_result.cookie);
+      op.status = cookie_and_access_result.access_result.status;
       op.devtools_request_id = details->devtools_request_id;
       recorded_activity_.push_back(op);
     }
diff --git a/services/network/test/test_network_service_client.cc b/services/network/test/test_network_service_client.cc
index 2ea8f96..5aacfbf68 100644
--- a/services/network/test/test_network_service_client.cc
+++ b/services/network/test/test_network_service_client.cc
@@ -40,7 +40,7 @@
     int32_t process_id,
     int32_t routing_id,
     const std::string& devtools_request_id,
-    const net::CookieAndLineStatusList& cookies_with_status,
+    const net::CookieAndLineAccessResultList& cookies_with_access_result,
     std::vector<network::mojom::HttpRawHeaderPairPtr> headers,
     const base::Optional<std::string>& raw_response_headers) {}
 
diff --git a/services/network/test/test_network_service_client.h b/services/network/test/test_network_service_client.h
index b43acef..373160e 100644
--- a/services/network/test/test_network_service_client.h
+++ b/services/network/test/test_network_service_client.h
@@ -42,7 +42,7 @@
       int32_t process_id,
       int32_t routing_id,
       const std::string& devtools_request_id,
-      const net::CookieAndLineStatusList& cookies_with_status,
+      const net::CookieAndLineAccessResultList& cookies_with_access_result,
       std::vector<network::mojom::HttpRawHeaderPairPtr> headers,
       const base::Optional<std::string>& raw_response_headers) override;
   void OnCorsPreflightRequest(int32_t process_id,
diff --git a/services/network/url_loader.cc b/services/network/url_loader.cc
index 1fa9021..ce02ee0 100644
--- a/services/network/url_loader.cc
+++ b/services/network/url_loader.cc
@@ -1795,14 +1795,13 @@
   }
 
   if (cookie_observer_) {
-    net::CookieStatusList reported_cookies;
+    net::CookieAccessResultList reported_cookies;
     for (const auto& cookie_with_access_result :
          url_request_->maybe_sent_cookies()) {
       if (ShouldNotifyAboutCookie(
               cookie_with_access_result.access_result.status)) {
-        reported_cookies.push_back(
-            {cookie_with_access_result.cookie,
-             cookie_with_access_result.access_result.status});
+        reported_cookies.push_back({cookie_with_access_result.cookie,
+                                    cookie_with_access_result.access_result});
       }
     }
 
@@ -2010,13 +2009,15 @@
   }
 
   if (cookie_observer_) {
-    net::CookieStatusList reported_cookies;
-    for (const auto& cookie_line_and_status :
+    net::CookieAccessResultList reported_cookies;
+    for (const auto& cookie_line_and_access_result :
          url_request_->maybe_stored_cookies()) {
-      if (ShouldNotifyAboutCookie(cookie_line_and_status.status) &&
-          cookie_line_and_status.cookie) {
-        reported_cookies.push_back({cookie_line_and_status.cookie.value(),
-                                    cookie_line_and_status.status});
+      if (ShouldNotifyAboutCookie(
+              cookie_line_and_access_result.access_result.status) &&
+          cookie_line_and_access_result.cookie) {
+        reported_cookies.push_back(
+            {cookie_line_and_access_result.cookie.value(),
+             cookie_line_and_access_result.access_result});
       }
     }
 
diff --git a/services/network/url_loader_unittest.cc b/services/network/url_loader_unittest.cc
index 9764d3df..ecbdf33 100644
--- a/services/network/url_loader_unittest.cc
+++ b/services/network/url_loader_unittest.cc
@@ -49,6 +49,7 @@
 #include "net/base/net_errors.h"
 #include "net/cert/internal/parse_name.h"
 #include "net/cert/test_root_certs.h"
+#include "net/cookies/cookie_access_result.h"
 #include "net/cookies/cookie_change_dispatcher.h"
 #include "net/cookies/cookie_inclusion_status.h"
 #include "net/cookies/cookie_util.h"
@@ -2631,13 +2632,13 @@
 
   struct CookieDetails {
     CookieDetails(const mojom::CookieAccessDetailsPtr& details,
-                  const net::CookieWithStatus cookie)
+                  const net::CookieWithAccessResult cookie)
         : type(details->type),
           name(cookie.cookie.Name()),
           value(cookie.cookie.Value()),
-          is_include(cookie.status.IsInclude()),
+          is_include(cookie.access_result.status.IsInclude()),
           url(details->url),
-          status(cookie.status) {}
+          status(cookie.access_result.status) {}
 
     CookieDetails(CookieAccessType type,
                   std::string name,
@@ -2735,12 +2736,12 @@
       int32_t process_id,
       int32_t routing_id,
       const std::string& devtools_request_id,
-      const net::CookieAndLineStatusList& cookies_with_status,
+      const net::CookieAndLineAccessResultList& cookies_with_access_result,
       std::vector<network::mojom::HttpRawHeaderPairPtr> headers,
       const base::Optional<std::string>& raw_response_headers) override {
     raw_response_cookies_.insert(raw_response_cookies_.end(),
-                                 cookies_with_status.begin(),
-                                 cookies_with_status.end());
+                                 cookies_with_access_result.begin(),
+                                 cookies_with_access_result.end());
 
     devtools_request_id_ = devtools_request_id;
 
@@ -2775,7 +2776,7 @@
     EXPECT_EQ(goal, raw_request_cookies_.size());
   }
 
-  const net::CookieAndLineStatusList& raw_response_cookies() const {
+  const net::CookieAndLineAccessResultList& raw_response_cookies() const {
     return raw_response_cookies_;
   }
 
@@ -2790,7 +2791,7 @@
   }
 
  private:
-  net::CookieAndLineStatusList raw_response_cookies_;
+  net::CookieAndLineAccessResultList raw_response_cookies_;
   base::OnceClosure wait_for_raw_response_;
   size_t wait_for_raw_response_goal_ = 0u;
   std::string devtools_request_id_;
@@ -4341,8 +4342,8 @@
               network_service_client.raw_response_cookies()[0].cookie->Name());
     EXPECT_EQ("b",
               network_service_client.raw_response_cookies()[0].cookie->Value());
-    EXPECT_TRUE(
-        network_service_client.raw_response_cookies()[0].status.IsInclude());
+    EXPECT_TRUE(network_service_client.raw_response_cookies()[0]
+                    .access_result.status.IsInclude());
 
     EXPECT_EQ("TEST", network_service_client.devtools_request_id());
 
@@ -4390,7 +4391,7 @@
     EXPECT_FALSE(network_service_client.raw_response_cookies()[0].cookie);
     EXPECT_TRUE(
         network_service_client.raw_response_cookies()[0]
-            .status.HasExactlyExclusionReasonsForTesting(
+            .access_result.status.HasExactlyExclusionReasonsForTesting(
                 {net::CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE}));
 
     EXPECT_EQ("TEST", network_service_client.devtools_request_id());
@@ -4446,8 +4447,8 @@
               network_service_client.raw_response_cookies()[0].cookie->Name());
     EXPECT_EQ("true",
               network_service_client.raw_response_cookies()[0].cookie->Value());
-    EXPECT_TRUE(
-        network_service_client.raw_response_cookies()[0].status.IsInclude());
+    EXPECT_TRUE(network_service_client.raw_response_cookies()[0]
+                    .access_result.status.IsInclude());
 
     EXPECT_EQ("TEST", network_service_client.devtools_request_id());
   }
@@ -4494,7 +4495,7 @@
     EXPECT_TRUE(
         network_service_client.raw_response_cookies()[0].cookie->IsSecure());
     EXPECT_TRUE(network_service_client.raw_response_cookies()[0]
-                    .status.HasExactlyExclusionReasonsForTesting(
+                    .access_result.status.HasExactlyExclusionReasonsForTesting(
                         {net::CookieInclusionStatus::EXCLUDE_SECURE_ONLY}));
   }
 }
@@ -4541,8 +4542,8 @@
               network_service_client.raw_response_cookies()[0].cookie->Name());
     EXPECT_EQ("true",
               network_service_client.raw_response_cookies()[0].cookie->Value());
-    EXPECT_TRUE(
-        network_service_client.raw_response_cookies()[0].status.IsInclude());
+    EXPECT_TRUE(network_service_client.raw_response_cookies()[0]
+                    .access_result.status.IsInclude());
 
     EXPECT_EQ("TEST", network_service_client.devtools_request_id());
   }
@@ -4587,7 +4588,7 @@
     EXPECT_TRUE(
         network_service_client.raw_response_cookies()[0].cookie->IsSecure());
     EXPECT_TRUE(network_service_client.raw_response_cookies()[0]
-                    .status.HasExactlyExclusionReasonsForTesting(
+                    .access_result.status.HasExactlyExclusionReasonsForTesting(
                         {net::CookieInclusionStatus::EXCLUDE_SECURE_ONLY}));
 
     EXPECT_EQ("TEST", network_service_client.devtools_request_id());
diff --git a/services/resource_coordinator/memory_instrumentation/OWNERS b/services/resource_coordinator/memory_instrumentation/OWNERS
index 62d73f2a..e13658d 100644
--- a/services/resource_coordinator/memory_instrumentation/OWNERS
+++ b/services/resource_coordinator/memory_instrumentation/OWNERS
@@ -1,3 +1,4 @@
 hjd@chromium.org
 primiano@chromium.org
+ssid@chromium.org
 # COMPONENT: Internals>Instrumentation>Memory
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index db567581..6c033b6 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -1813,6 +1813,12 @@
         "test_id_prefix": "ninja://components/paint_preview/player/android:paint_preview_junit_tests/"
       },
       {
+        "name": "password_check_junit_tests",
+        "swarming": {},
+        "test": "password_check_junit_tests",
+        "test_id_prefix": "ninja://chrome/browser/password_check/android:password_check_junit_tests/"
+      },
+      {
         "name": "service_junit_tests",
         "swarming": {},
         "test": "service_junit_tests",
@@ -20157,6 +20163,12 @@
         "test_id_prefix": "ninja://components/paint_preview/player/android:paint_preview_junit_tests/"
       },
       {
+        "name": "password_check_junit_tests",
+        "swarming": {},
+        "test": "password_check_junit_tests",
+        "test_id_prefix": "ninja://chrome/browser/password_check/android:password_check_junit_tests/"
+      },
+      {
         "name": "service_junit_tests",
         "swarming": {},
         "test": "service_junit_tests",
@@ -28359,6 +28371,13 @@
       },
       {
         "isolate_profile_data": true,
+        "name": "password_check_junit_tests",
+        "swarming": {},
+        "test": "password_check_junit_tests",
+        "test_id_prefix": "ninja://chrome/browser/password_check/android:password_check_junit_tests/"
+      },
+      {
+        "isolate_profile_data": true,
         "name": "service_junit_tests",
         "swarming": {},
         "test": "service_junit_tests",
diff --git a/testing/buildbot/chromium.ci.json b/testing/buildbot/chromium.ci.json
index d9927f2..50ffe13 100644
--- a/testing/buildbot/chromium.ci.json
+++ b/testing/buildbot/chromium.ci.json
@@ -13374,6 +13374,12 @@
         "test_id_prefix": "ninja://components/paint_preview/player/android:paint_preview_junit_tests/"
       },
       {
+        "name": "password_check_junit_tests",
+        "swarming": {},
+        "test": "password_check_junit_tests",
+        "test_id_prefix": "ninja://chrome/browser/password_check/android:password_check_junit_tests/"
+      },
+      {
         "name": "service_junit_tests",
         "swarming": {},
         "test": "service_junit_tests",
@@ -59941,7 +59947,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -59967,7 +59973,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -59994,7 +60000,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -60020,7 +60026,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -60045,7 +60051,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -60068,7 +60074,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -60091,7 +60097,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -60111,7 +60117,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -60135,7 +60141,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -60168,7 +60174,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -60199,7 +60205,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -60230,7 +60236,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -60261,7 +60267,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -60292,7 +60298,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -60327,7 +60333,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -60367,7 +60373,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -60407,7 +60413,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -60447,7 +60453,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -60487,7 +60493,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -60513,7 +60519,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -60544,7 +60550,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -60576,7 +60582,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -60607,7 +60613,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -60640,7 +60646,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -60674,7 +60680,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -60706,7 +60712,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -60739,7 +60745,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -60771,7 +60777,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -96874,6 +96880,12 @@
         "test_id_prefix": "ninja://components/paint_preview/player/android:paint_preview_junit_tests/"
       },
       {
+        "name": "password_check_junit_tests",
+        "swarming": {},
+        "test": "password_check_junit_tests",
+        "test_id_prefix": "ninja://chrome/browser/password_check/android:password_check_junit_tests/"
+      },
+      {
         "name": "service_junit_tests",
         "swarming": {},
         "test": "service_junit_tests",
@@ -163598,6 +163610,12 @@
         "test_id_prefix": "ninja://components/paint_preview/player/android:paint_preview_junit_tests/"
       },
       {
+        "name": "password_check_junit_tests",
+        "swarming": {},
+        "test": "password_check_junit_tests",
+        "test_id_prefix": "ninja://chrome/browser/password_check/android:password_check_junit_tests/"
+      },
+      {
         "name": "service_junit_tests",
         "swarming": {},
         "test": "service_junit_tests",
@@ -170303,6 +170321,13 @@
       },
       {
         "isolate_profile_data": true,
+        "name": "password_check_junit_tests",
+        "swarming": {},
+        "test": "password_check_junit_tests",
+        "test_id_prefix": "ninja://chrome/browser/password_check/android:password_check_junit_tests/"
+      },
+      {
+        "isolate_profile_data": true,
         "name": "service_junit_tests",
         "swarming": {},
         "test": "service_junit_tests",
@@ -180822,6 +180847,13 @@
       },
       {
         "isolate_profile_data": true,
+        "name": "password_check_junit_tests",
+        "swarming": {},
+        "test": "password_check_junit_tests",
+        "test_id_prefix": "ninja://chrome/browser/password_check/android:password_check_junit_tests/"
+      },
+      {
+        "isolate_profile_data": true,
         "name": "service_junit_tests",
         "swarming": {},
         "test": "service_junit_tests",
@@ -195289,6 +195321,1225 @@
       "cast_test_lists"
     ]
   },
+  "fuchsia-fyi-arm64-dbg": {
+    "additional_compile_targets": [
+      "all"
+    ],
+    "gtest_tests": [
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "absl_hardening_tests",
+        "test_id_prefix": "ninja://third_party/abseil-cpp:absl_hardening_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "accessibility_unittests",
+        "test_id_prefix": "ninja://ui/accessibility:accessibility_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "angle_unittests",
+        "test_id_prefix": "ninja://third_party/angle/src/tests:angle_unittests/"
+      },
+      {
+        "args": [
+          "--child-arg=--ozone-platform=headless",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "aura_unittests",
+        "test_id_prefix": "ninja://ui/aura:aura_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_unittests",
+        "test_id_prefix": "ninja://base:base_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_util_unittests",
+        "test_id_prefix": "ninja://base/util:base_util_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_common_unittests",
+        "test_id_prefix": "ninja://third_party/blink/common:blink_common_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_heap_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_platform_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_platform_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.blink_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/controller:blink_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "boringssl_crypto_tests",
+        "test_id_prefix": "ninja://third_party/boringssl:boringssl_crypto_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "boringssl_ssl_tests",
+        "test_id_prefix": "ninja://third_party/boringssl:boringssl_ssl_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "capture_unittests",
+        "test_id_prefix": "ninja://media/capture:capture_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_runner_browsertests",
+        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_browsertests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_runner_integration_tests",
+        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_integration_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_runner_unittests",
+        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 2
+        },
+        "test": "cc_unittests",
+        "test_id_prefix": "ninja://cc:cc_unittests/"
+      },
+      {
+        "args": [
+          "--child-arg=--ozone-platform=headless",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "compositor_unittests",
+        "test_id_prefix": "ninja://ui/compositor:compositor_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.content_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "content_unittests",
+        "test_id_prefix": "ninja://content/test:content_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cr_fuchsia_base_unittests",
+        "test_id_prefix": "ninja://fuchsia/base:cr_fuchsia_base_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cronet_tests",
+        "test_id_prefix": "ninja://components/cronet:cronet_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cronet_unittests",
+        "test_id_prefix": "ninja://components/cronet:cronet_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "crypto_unittests",
+        "test_id_prefix": "ninja://crypto:crypto_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "display_unittests",
+        "test_id_prefix": "ninja://ui/display:display_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "events_unittests",
+        "test_id_prefix": "ninja://ui/events:events_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gfx_unittests",
+        "test_id_prefix": "ninja://ui/gfx:gfx_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gin_unittests",
+        "test_id_prefix": "ninja://gin:gin_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "google_apis_unittests",
+        "test_id_prefix": "ninja://google_apis:google_apis_unittests/"
+      },
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gpu_unittests",
+        "test_id_prefix": "ninja://gpu:gpu_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.headless_browsertests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "headless_browsertests",
+        "test_id_prefix": "ninja://headless:headless_browsertests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "headless_unittests",
+        "test_id_prefix": "ninja://headless:headless_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "http_service_tests",
+        "test_id_prefix": "ninja://fuchsia/http:http_service_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ipc_tests",
+        "test_id_prefix": "ninja://ipc:ipc_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "latency_unittests",
+        "test_id_prefix": "ninja://ui/latency:latency_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "media_blink_unittests",
+        "test_id_prefix": "ninja://media/blink:media_blink_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "media_unittests",
+        "test_id_prefix": "ninja://media:media_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.mojo_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "mojo_unittests",
+        "test_id_prefix": "ninja://mojo:mojo_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "native_theme_unittests",
+        "test_id_prefix": "ninja://ui/native_theme:native_theme_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.net_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 4
+        },
+        "test": "net_unittests",
+        "test_id_prefix": "ninja://net:net_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "perfetto_unittests",
+        "test_id_prefix": "ninja://third_party/perfetto:perfetto_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "service_manager_unittests",
+        "test_id_prefix": "ninja://services/service_manager/tests:service_manager_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.services_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "services_unittests",
+        "test_id_prefix": "ninja://services:services_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "shell_dialogs_unittests",
+        "test_id_prefix": "ninja://ui/shell_dialogs:shell_dialogs_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "skia_unittests",
+        "test_id_prefix": "ninja://skia:skia_unittests/"
+      },
+      {
+        "args": [
+          "--child-arg=--ozone-platform=headless",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "snapshot_unittests",
+        "test_id_prefix": "ninja://ui/snapshot:snapshot_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "sql_unittests",
+        "test_id_prefix": "ninja://sql:sql_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.storage_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "storage_unittests",
+        "test_id_prefix": "ninja://storage:storage_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.ui_base_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ui_base_unittests",
+        "test_id_prefix": "ninja://ui/base:ui_base_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "url_unittests",
+        "test_id_prefix": "ninja://url:url_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.viz_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "viz_unittests",
+        "test_id_prefix": "ninja://components/viz:viz_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "web_engine_browsertests",
+        "test_id_prefix": "ninja://fuchsia/engine:web_engine_browsertests/"
+      },
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "web_engine_integration_tests",
+        "test_id_prefix": "ninja://fuchsia/engine:web_engine_integration_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "web_engine_unittests",
+        "test_id_prefix": "ninja://fuchsia/engine:web_engine_unittests/"
+      },
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "web_runner_integration_tests",
+        "test_id_prefix": "ninja://fuchsia/runners:web_runner_integration_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "wtf_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/wtf:wtf_unittests/"
+      }
+    ]
+  },
   "fuchsia-fyi-arm64-rel": {
     "additional_compile_targets": [
       "all"
diff --git a/testing/buildbot/chromium.clang.json b/testing/buildbot/chromium.clang.json
index e3562d3..8d8908be 100644
--- a/testing/buildbot/chromium.clang.json
+++ b/testing/buildbot/chromium.clang.json
@@ -8072,6 +8072,12 @@
         "test_id_prefix": "ninja://components/paint_preview/player/android:paint_preview_junit_tests/"
       },
       {
+        "name": "password_check_junit_tests",
+        "swarming": {},
+        "test": "password_check_junit_tests",
+        "test_id_prefix": "ninja://chrome/browser/password_check/android:password_check_junit_tests/"
+      },
+      {
         "name": "service_junit_tests",
         "swarming": {},
         "test": "service_junit_tests",
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index dc80f3e..9be915c 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -7812,6 +7812,13 @@
       },
       {
         "isolate_profile_data": true,
+        "name": "password_check_junit_tests",
+        "swarming": {},
+        "test": "password_check_junit_tests",
+        "test_id_prefix": "ninja://chrome/browser/password_check/android:password_check_junit_tests/"
+      },
+      {
+        "isolate_profile_data": true,
         "name": "service_junit_tests",
         "swarming": {},
         "test": "service_junit_tests",
@@ -10426,6 +10433,1225 @@
       "chrome"
     ]
   },
+  "fuchsia-fyi-arm64-dbg": {
+    "additional_compile_targets": [
+      "all"
+    ],
+    "gtest_tests": [
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "absl_hardening_tests",
+        "test_id_prefix": "ninja://third_party/abseil-cpp:absl_hardening_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "accessibility_unittests",
+        "test_id_prefix": "ninja://ui/accessibility:accessibility_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "angle_unittests",
+        "test_id_prefix": "ninja://third_party/angle/src/tests:angle_unittests/"
+      },
+      {
+        "args": [
+          "--child-arg=--ozone-platform=headless",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "aura_unittests",
+        "test_id_prefix": "ninja://ui/aura:aura_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_unittests",
+        "test_id_prefix": "ninja://base:base_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_util_unittests",
+        "test_id_prefix": "ninja://base/util:base_util_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_common_unittests",
+        "test_id_prefix": "ninja://third_party/blink/common:blink_common_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_heap_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_platform_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_platform_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.blink_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/controller:blink_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "boringssl_crypto_tests",
+        "test_id_prefix": "ninja://third_party/boringssl:boringssl_crypto_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "boringssl_ssl_tests",
+        "test_id_prefix": "ninja://third_party/boringssl:boringssl_ssl_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "capture_unittests",
+        "test_id_prefix": "ninja://media/capture:capture_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_runner_browsertests",
+        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_browsertests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_runner_integration_tests",
+        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_integration_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_runner_unittests",
+        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 2
+        },
+        "test": "cc_unittests",
+        "test_id_prefix": "ninja://cc:cc_unittests/"
+      },
+      {
+        "args": [
+          "--child-arg=--ozone-platform=headless",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "compositor_unittests",
+        "test_id_prefix": "ninja://ui/compositor:compositor_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.content_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "content_unittests",
+        "test_id_prefix": "ninja://content/test:content_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cr_fuchsia_base_unittests",
+        "test_id_prefix": "ninja://fuchsia/base:cr_fuchsia_base_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cronet_tests",
+        "test_id_prefix": "ninja://components/cronet:cronet_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cronet_unittests",
+        "test_id_prefix": "ninja://components/cronet:cronet_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "crypto_unittests",
+        "test_id_prefix": "ninja://crypto:crypto_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "display_unittests",
+        "test_id_prefix": "ninja://ui/display:display_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "events_unittests",
+        "test_id_prefix": "ninja://ui/events:events_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gfx_unittests",
+        "test_id_prefix": "ninja://ui/gfx:gfx_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gin_unittests",
+        "test_id_prefix": "ninja://gin:gin_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "google_apis_unittests",
+        "test_id_prefix": "ninja://google_apis:google_apis_unittests/"
+      },
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gpu_unittests",
+        "test_id_prefix": "ninja://gpu:gpu_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.headless_browsertests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "headless_browsertests",
+        "test_id_prefix": "ninja://headless:headless_browsertests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "headless_unittests",
+        "test_id_prefix": "ninja://headless:headless_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "http_service_tests",
+        "test_id_prefix": "ninja://fuchsia/http:http_service_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ipc_tests",
+        "test_id_prefix": "ninja://ipc:ipc_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "latency_unittests",
+        "test_id_prefix": "ninja://ui/latency:latency_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "media_blink_unittests",
+        "test_id_prefix": "ninja://media/blink:media_blink_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "media_unittests",
+        "test_id_prefix": "ninja://media:media_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.mojo_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "mojo_unittests",
+        "test_id_prefix": "ninja://mojo:mojo_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "native_theme_unittests",
+        "test_id_prefix": "ninja://ui/native_theme:native_theme_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.net_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 4
+        },
+        "test": "net_unittests",
+        "test_id_prefix": "ninja://net:net_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "perfetto_unittests",
+        "test_id_prefix": "ninja://third_party/perfetto:perfetto_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "service_manager_unittests",
+        "test_id_prefix": "ninja://services/service_manager/tests:service_manager_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.services_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "services_unittests",
+        "test_id_prefix": "ninja://services:services_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "shell_dialogs_unittests",
+        "test_id_prefix": "ninja://ui/shell_dialogs:shell_dialogs_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "skia_unittests",
+        "test_id_prefix": "ninja://skia:skia_unittests/"
+      },
+      {
+        "args": [
+          "--child-arg=--ozone-platform=headless",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "snapshot_unittests",
+        "test_id_prefix": "ninja://ui/snapshot:snapshot_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "sql_unittests",
+        "test_id_prefix": "ninja://sql:sql_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.storage_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "storage_unittests",
+        "test_id_prefix": "ninja://storage:storage_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.ui_base_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ui_base_unittests",
+        "test_id_prefix": "ninja://ui/base:ui_base_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "url_unittests",
+        "test_id_prefix": "ninja://url:url_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.viz_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "viz_unittests",
+        "test_id_prefix": "ninja://components/viz:viz_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "web_engine_browsertests",
+        "test_id_prefix": "ninja://fuchsia/engine:web_engine_browsertests/"
+      },
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "web_engine_integration_tests",
+        "test_id_prefix": "ninja://fuchsia/engine:web_engine_integration_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "web_engine_unittests",
+        "test_id_prefix": "ninja://fuchsia/engine:web_engine_unittests/"
+      },
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "web_runner_integration_tests",
+        "test_id_prefix": "ninja://fuchsia/runners:web_runner_integration_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "wtf_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/wtf:wtf_unittests/"
+      }
+    ]
+  },
   "fuchsia-fyi-arm64-rel": {
     "additional_compile_targets": [
       "all"
diff --git a/testing/buildbot/chromium.gpu.fyi.json b/testing/buildbot/chromium.gpu.fyi.json
index 74d2063..052e3575c 100644
--- a/testing/buildbot/chromium.gpu.fyi.json
+++ b/testing/buildbot/chromium.gpu.fyi.json
@@ -14353,7 +14353,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -14379,7 +14379,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -14406,7 +14406,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -14432,7 +14432,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -14457,7 +14457,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -14480,7 +14480,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -14503,7 +14503,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -14523,7 +14523,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -14547,7 +14547,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -14580,7 +14580,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -14611,7 +14611,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -14642,7 +14642,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -14673,7 +14673,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -14704,7 +14704,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -14739,7 +14739,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -14779,7 +14779,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -14819,7 +14819,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -14859,7 +14859,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -14899,7 +14899,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -14925,7 +14925,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -14956,7 +14956,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -14988,7 +14988,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -15019,7 +15019,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -15052,7 +15052,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -15086,7 +15086,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -15118,7 +15118,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -15151,7 +15151,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
@@ -15183,7 +15183,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.15.4"
+              "os": "Mac-10.15.5"
             }
           ],
           "expiration": 21600,
diff --git a/testing/buildbot/filters/android.emulator_m.content_shell_test_apk.filter b/testing/buildbot/filters/android.emulator_m.content_shell_test_apk.filter
index 06388b0..3fb8f1b3 100644
--- a/testing/buildbot/filters/android.emulator_m.content_shell_test_apk.filter
+++ b/testing/buildbot/filters/android.emulator_m.content_shell_test_apk.filter
@@ -2,7 +2,7 @@
 -org.chromium.content.browser.ContentViewScrollingTest.testFling
 
 # crbug.com/1081514
--org.chromium.content.browser.VideoFullscreenOrientationLockTest.testEnterExitFullscreenWithControlsButton
+-org.chromium.content.browser.VideoFullscreenOrientationLockTest.*
 
 # crbug.com/1081511
 -org.chromium.content.browser.MediaSessionTest.testShortVideoIsTransient
@@ -15,3 +15,6 @@
 
 # crbug.com/1099954
 -org.chromium.content.browser.MediaSessionTest.testDontStopEachOther
+
+# crbug.com/1105214
+-org.chromium.content.browser.input.ImeTest.testSelectActionBarClearedOnTappingInput
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index 398e613..dcb7a92 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -1336,6 +1336,10 @@
     "label": "//components/paint_preview/player/android:paint_preview_junit_tests",
     "type": "junit_test",
   },
+  "password_check_junit_tests": {
+    "label": "//chrome/browser/password_check/android:password_check_junit_tests",
+    "type": "junit_test",
+  },
   "pdf_fuzzers": {
     "label": "//pdf/pdfium/fuzzers:pdf_fuzzers",
     "type": "additional_compile_target",
diff --git a/testing/buildbot/manage.py b/testing/buildbot/manage.py
index 9257795..fd2a5c9 100755
--- a/testing/buildbot/manage.py
+++ b/testing/buildbot/manage.py
@@ -139,6 +139,7 @@
   'media_router_perf_tests',
   'net_junit_tests',
   'net_junit_tests',
+  'password_check_junit_tests',
   'service_junit_tests',
   'shipped_binaries',
   'system_webview_apk',
diff --git a/testing/buildbot/mixins.pyl b/testing/buildbot/mixins.pyl
index 9996524..27ec394 100644
--- a/testing/buildbot/mixins.pyl
+++ b/testing/buildbot/mixins.pyl
@@ -488,7 +488,7 @@
     'swarming': {
       'dimensions': {
         'gpu': '8086:0a2e',
-        'os': 'Mac-10.15.4',
+        'os': 'Mac-10.15.5',
       },
     },
   },
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index 17278ecc..49b0879 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -774,6 +774,7 @@
       'Fuchsia ARM64',  # https://crbug.com/961457
       'Fuchsia x64', # https://crbug.com/961457
       'android-code-coverage-native', # crbug/1018431
+      'fuchsia-fyi-arm64-dbg',  # https://crbug.com/961457
       'fuchsia-fyi-arm64-rel',  # https://crbug.com/961457
       'fuchsia-fyi-x64-dbg',  # https://crbug.com/961457
       'fuchsia-fyi-x64-rel',  # https://crbug.com/961457
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 0bb5d28..92c70de 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -809,6 +809,7 @@
       'module_installer_junit_tests': {},
       'net_junit_tests': {},
       'paint_preview_junit_tests': {},
+      'password_check_junit_tests': {},
       'service_junit_tests': {},
       'touch_to_fill_junit_tests': {},
       'ui_junit_tests': {},
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 8992b27..573a958 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -1939,6 +1939,19 @@
           'chrome',
         ],
       },
+      'fuchsia-fyi-arm64-dbg': {
+        'additional_compile_targets': [
+          'all',
+        ],
+        'test_suites': {
+          'gtest_tests': 'fuchsia_gtests',
+        },
+        'mixins': [
+          'arm64',
+          'docker',
+          'linux-xenial',
+        ],
+      },
       'fuchsia-fyi-arm64-rel': {
         'additional_compile_targets': [
           'all',
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 37b4246..e22507c 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -89,21 +89,6 @@
             ]
         }
     ],
-    "AndroidBlockIntentNonSafelistedHeaders": [
-        {
-            "platforms": [
-                "android"
-            ],
-            "experiments": [
-                {
-                    "name": "EnabledLaunch",
-                    "enable_features": [
-                        "AndroidBlockIntentNonSafelistedHeaders"
-                    ]
-                }
-            ]
-        }
-    ],
     "AndroidDarkSearch": [
         {
             "platforms": [
@@ -2569,6 +2554,21 @@
             ]
         }
     ],
+    "EnableSuggestedFiles": [
+        {
+            "platforms": [
+                "chromeos"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "EnableSuggestedFiles"
+                    ]
+                }
+            ]
+        }
+    ],
     "EnterpriseReportingInChromeOS": [
         {
             "platforms": [
@@ -3790,6 +3790,21 @@
             ]
         }
     ],
+    "LauncherSettingsSearch": [
+        {
+            "platforms": [
+                "chromeos"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "LauncherSettingsSearch"
+                    ]
+                }
+            ]
+        }
+    ],
     "LegacyTLSDeprecation2": [
         {
             "platforms": [
@@ -5403,6 +5418,21 @@
             ]
         }
     ],
+    "QRCodeGenerationStudy": [
+        {
+            "platforms": [
+                "ios"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "QRCodeGeneration"
+                    ]
+                }
+            ]
+        }
+    ],
     "QUIC": [
         {
             "platforms": [
@@ -6102,7 +6132,6 @@
     "SafeBrowsingRealTimeUrlLookupEnabledWithToken": [
         {
             "platforms": [
-                "android",
                 "chromeos",
                 "linux",
                 "mac",
@@ -6118,6 +6147,21 @@
             ]
         }
     ],
+    "SafeBrowsingRealTimeUrlLookupEnabledWithTokenAndroid": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "SafeBrowsingRealTimeUrlLookupEnabledWithToken"
+                    ]
+                }
+            ]
+        }
+    ],
     "SafeBrowsingSuspiciousSiteTrigger": [
         {
             "platforms": [
@@ -8019,21 +8063,6 @@
             ]
         }
     ],
-    "iOSQRCodeGenerator": [
-        {
-            "platforms": [
-                "ios"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "QRCodeGeneration"
-                    ]
-                }
-            ]
-        }
-    ],
     "libvpx_for_vp8": [
         {
             "platforms": [
diff --git a/third_party/PRESUBMIT.py b/third_party/PRESUBMIT.py
index 9d31b00e..12011a5 100644
--- a/third_party/PRESUBMIT.py
+++ b/third_party/PRESUBMIT.py
@@ -53,8 +53,6 @@
         not local_path.startswith('third_party' + input_api.os_path.sep +
                                   'boringssl' + input_api.os_path.sep) and
         not local_path.startswith('third_party' + input_api.os_path.sep +
-                                  'cacheinvalidation' + input_api.os_path.sep) and
-        not local_path.startswith('third_party' + input_api.os_path.sep +
                                   'closure_compiler' + input_api.os_path.sep +
                                   'externs' + input_api.os_path.sep) and
         not local_path.startswith('third_party' + input_api.os_path.sep +
diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD.gn
index dce14c4..88f08202 100644
--- a/third_party/android_deps/BUILD.gn
+++ b/third_party/android_deps/BUILD.gn
@@ -423,6 +423,17 @@
 }
 
 # This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
+android_aar_prebuilt("androidx_lifecycle_lifecycle_livedata_core_java") {
+  aar_path = "libs/androidx_lifecycle_lifecycle_livedata_core/lifecycle-livedata-core-2.2.0.aar"
+  info_path = "libs/androidx_lifecycle_lifecycle_livedata_core/androidx_lifecycle_lifecycle_livedata_core.info"
+  deps = [
+    ":androidx_arch_core_core_common_java",
+    ":androidx_arch_core_core_runtime_java",
+    ":androidx_lifecycle_lifecycle_common_java",
+  ]
+}
+
+# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
 android_aar_prebuilt("androidx_lifecycle_lifecycle_runtime_java") {
   aar_path =
       "libs/androidx_lifecycle_lifecycle_runtime/lifecycle-runtime-2.2.0.aar"
@@ -1790,21 +1801,6 @@
 }
 
 # This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-android_aar_prebuilt("androidx_lifecycle_lifecycle_livedata_core_java") {
-  aar_path = "libs/androidx_lifecycle_lifecycle_livedata_core/lifecycle-livedata-core-2.2.0.aar"
-  info_path = "libs/androidx_lifecycle_lifecycle_livedata_core/androidx_lifecycle_lifecycle_livedata_core.info"
-
-  # To remove visibility constraint, add this dependency to
-  # //third_party/android_deps/build.gradle.
-  visibility = [ ":*" ]
-  deps = [
-    ":androidx_arch_core_core_common_java",
-    ":androidx_arch_core_core_runtime_java",
-    ":androidx_lifecycle_lifecycle_common_java",
-  ]
-}
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
 android_aar_prebuilt("androidx_lifecycle_lifecycle_viewmodel_savedstate_java") {
   aar_path = "libs/androidx_lifecycle_lifecycle_viewmodel_savedstate/lifecycle-viewmodel-savedstate-2.2.0.aar"
   info_path = "libs/androidx_lifecycle_lifecycle_viewmodel_savedstate/androidx_lifecycle_lifecycle_viewmodel_savedstate.info"
diff --git a/third_party/android_deps/build.gradle b/third_party/android_deps/build.gradle
index 8db739c..725b84c 100644
--- a/third_party/android_deps/build.gradle
+++ b/third_party/android_deps/build.gradle
@@ -18,6 +18,7 @@
     compile "androidx.lifecycle:lifecycle-runtime:${androidXArchComponentsVersion}"
     compile "androidx.lifecycle:lifecycle-common:${androidXArchComponentsVersion}"
     compile "androidx.lifecycle:lifecycle-common-java8:${androidXArchComponentsVersion}"
+    compile "androidx.lifecycle:lifecycle-livedata-core:${androidXArchComponentsVersion}"
     compile "androidx.lifecycle:lifecycle-viewmodel:${androidXArchComponentsVersion}"
 
     // Support v4 libraries
diff --git a/third_party/blink/common/feature_policy/feature_policy.typemap b/third_party/blink/common/feature_policy/feature_policy.typemap
index f3c1a87..ce6eb91 100644
--- a/third_party/blink/common/feature_policy/feature_policy.typemap
+++ b/third_party/blink/common/feature_policy/feature_policy.typemap
@@ -4,8 +4,7 @@
 
 mojom = "//third_party/blink/public/mojom/feature_policy/feature_policy.mojom"
 public_headers = [
-  "//third_party/blink/public/common/feature_policy/feature_policy.h",
-  "//third_party/blink/public/common/feature_policy/policy_value.h",
+  "//third_party/blink/public/common/feature_policy/feature_policy_forward.h",
 ]
 traits_headers = [
   "//third_party/blink/common/feature_policy/feature_policy_mojom_traits.h",
diff --git a/third_party/blink/public/BUILD.gn b/third_party/blink/public/BUILD.gn
index 6ca7d29..c91bdf2 100644
--- a/third_party/blink/public/BUILD.gn
+++ b/third_party/blink/public/BUILD.gn
@@ -140,7 +140,6 @@
     "platform/modules/mediastream/web_media_stream_track.h",
     "platform/modules/mediastream/web_media_stream_video_renderer.h",
     "platform/modules/mediastream/web_platform_media_stream_source.h",
-    "platform/modules/mediastream/web_platform_media_stream_track.h",
     "platform/modules/remoteplayback/web_remote_playback_client.h",
     "platform/modules/service_worker/web_service_worker_error.h",
     "platform/modules/service_worker/web_service_worker_fetch_context.h",
diff --git a/third_party/blink/public/common/BUILD.gn b/third_party/blink/public/common/BUILD.gn
index 6b1ff8e..2ef840c 100644
--- a/third_party/blink/public/common/BUILD.gn
+++ b/third_party/blink/public/common/BUILD.gn
@@ -76,6 +76,7 @@
     "feature_policy/document_policy.h",
     "feature_policy/document_policy_features.h",
     "feature_policy/feature_policy.h",
+    "feature_policy/feature_policy_forward.h",
     "feature_policy/policy_value.h",
     "features.h",
     "fetch/fetch_api_request_headers_map.h",
@@ -157,7 +158,6 @@
     "prerender/prerender_rel_type.h",
     "scheduler/web_scheduler_tracked_feature.h",
     "screen_orientation/web_screen_orientation_lock_type.h",
-    "screen_orientation/web_screen_orientation_type.h",
     "security/security_style.h",
     "security_context/insecure_request_policy.h",
     "service_worker/service_worker_status_code.h",
diff --git a/third_party/blink/public/common/feature_policy/feature_policy_forward.h b/third_party/blink/public/common/feature_policy/feature_policy_forward.h
new file mode 100644
index 0000000..99e41e4
--- /dev/null
+++ b/third_party/blink/public/common/feature_policy/feature_policy_forward.h
@@ -0,0 +1,14 @@
+// Copyright 2020 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_PUBLIC_COMMON_FEATURE_POLICY_FEATURE_POLICY_FORWARD_H_
+#define THIRD_PARTY_BLINK_PUBLIC_COMMON_FEATURE_POLICY_FEATURE_POLICY_FORWARD_H_
+
+namespace blink {
+
+struct ParsedFeaturePolicyDeclaration;
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_PUBLIC_COMMON_FEATURE_POLICY_FEATURE_POLICY_FORWARD_H_
diff --git a/third_party/blink/public/common/screen_orientation/web_screen_orientation_type.h b/third_party/blink/public/common/screen_orientation/web_screen_orientation_type.h
deleted file mode 100644
index 08d0e2f..0000000
--- a/third_party/blink/public/common/screen_orientation/web_screen_orientation_type.h
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef THIRD_PARTY_BLINK_PUBLIC_COMMON_SCREEN_ORIENTATION_WEB_SCREEN_ORIENTATION_TYPE_H_
-#define THIRD_PARTY_BLINK_PUBLIC_COMMON_SCREEN_ORIENTATION_WEB_SCREEN_ORIENTATION_TYPE_H_
-
-namespace blink {
-
-enum WebScreenOrientationType {
-  kWebScreenOrientationUndefined = 0,
-  kWebScreenOrientationPortraitPrimary,
-  kWebScreenOrientationPortraitSecondary,
-  kWebScreenOrientationLandscapePrimary,
-  kWebScreenOrientationLandscapeSecondary,
-
-  WebScreenOrientationTypeLast = kWebScreenOrientationLandscapeSecondary
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_PUBLIC_COMMON_SCREEN_ORIENTATION_WEB_SCREEN_ORIENTATION_TYPE_H_
diff --git a/third_party/blink/public/devtools_protocol/browser_protocol.pdl b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
index 16263d0..65126c78 100644
--- a/third_party/blink/public/devtools_protocol/browser_protocol.pdl
+++ b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
@@ -5558,6 +5558,15 @@
   # Unique frame identifier.
   type FrameId extends string
 
+  # Indicates whether a frame has been identified as an ad.
+  experimental type AdFrameType extends string
+    enum
+      none
+      # This frame is a subframe of an ad frame.
+      child
+      # This frame is the root of an ad frame.
+      root
+
   # Information about the Frame on the page.
   type Frame extends object
     properties
@@ -5579,6 +5588,8 @@
       string mimeType
       # If the frame failed to load, this contains the URL that could not be loaded. Note that unlike url above, this URL may contain a fragment.
       experimental optional string unreachableUrl
+      # Indicates whether this frame was tagged as an ad.
+      experimental optional AdFrameType adFrameType
 
   # Information about the Resource on the page.
   experimental type FrameResource extends object
diff --git a/third_party/blink/public/mojom/BUILD.gn b/third_party/blink/public/mojom/BUILD.gn
index bc80823b..7bfdbb57 100644
--- a/third_party/blink/public/mojom/BUILD.gn
+++ b/third_party/blink/public/mojom/BUILD.gn
@@ -168,6 +168,7 @@
     "webdatabase/web_database.mojom",
     "websockets/websocket_connector.mojom",
     "webtransport/quic_transport_connector.mojom",
+    "widget/screen_orientation.mojom",
     "window_features/window_features.mojom",
     "worker/shared_worker_client.mojom",
     "worker/shared_worker_creation_context_type.mojom",
diff --git a/third_party/blink/public/mojom/frame/frame.mojom b/third_party/blink/public/mojom/frame/frame.mojom
index 35ce3615..994e24b 100644
--- a/third_party/blink/public/mojom/frame/frame.mojom
+++ b/third_party/blink/public/mojom/frame/frame.mojom
@@ -691,6 +691,15 @@
                     mojo_base.mojom.String16 source_origin,
                     mojo_base.mojom.String16 target_origin,
                     blink.mojom.TransferableMessage message);
+
+  // Ask the frame host to print a cross-process subframe.
+  // The printed content of this subframe belongs to the document specified by
+  // its document cookie. Document cookie is a unique id for a printed document
+  // associated with a print job.
+  // The content will be rendered in the specified rectangular area in its
+  // parent frame.
+  PrintCrossProcessSubframe(
+      gfx.mojom.Rect frame_content_rect, int32 document_cookie);
 };
 
 // Implemented in Blink, this interface defines frame-specific methods that will
diff --git a/third_party/blink/public/mojom/push_messaging/push_messaging.mojom b/third_party/blink/public/mojom/push_messaging/push_messaging.mojom
index 31ac74a..a2066075 100644
--- a/third_party/blink/public/mojom/push_messaging/push_messaging.mojom
+++ b/third_party/blink/public/mojom/push_messaging/push_messaging.mojom
@@ -6,6 +6,7 @@
 
 import "third_party/blink/public/mojom/push_messaging/push_messaging_status.mojom";
 import "url/mojom/url.mojom";
+import "mojo/public/mojom/base/time.mojom";
 
 // TODO(heke): The type-mapping struct and enums are duplicately defined. Need
 // to remove/replace those defined in content or blink namespace.
@@ -17,6 +18,7 @@
 
 struct PushSubscription {
   url.mojom.Url endpoint;
+  mojo_base.mojom.Time? expirationTime;
   PushSubscriptionOptions options;
   array<uint8> p256dh;
   array<uint8> auth;
diff --git a/third_party/blink/public/mojom/widget/OWNERS b/third_party/blink/public/mojom/widget/OWNERS
new file mode 100644
index 0000000..08850f4
--- /dev/null
+++ b/third_party/blink/public/mojom/widget/OWNERS
@@ -0,0 +1,2 @@
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
diff --git a/third_party/blink/public/mojom/widget/screen_orientation.mojom b/third_party/blink/public/mojom/widget/screen_orientation.mojom
new file mode 100644
index 0000000..afdb94e1
--- /dev/null
+++ b/third_party/blink/public/mojom/widget/screen_orientation.mojom
@@ -0,0 +1,16 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module blink.mojom;
+
+// The screen orientation. This is slightly different than the
+// device.mojom.ScreenOrientationLockType which is a request to lock to
+// a specific screen orientation.
+enum ScreenOrientation {
+  kUndefined,
+  kPortraitPrimary,
+  kPortraitSecondary,
+  kLandscapePrimary,
+  kLandscapeSecondary,
+};
\ No newline at end of file
diff --git a/third_party/blink/public/platform/modules/mediastream/web_media_stream_track.h b/third_party/blink/public/platform/modules/mediastream/web_media_stream_track.h
index 11f0899a..589dca15b 100644
--- a/third_party/blink/public/platform/modules/mediastream/web_media_stream_track.h
+++ b/third_party/blink/public/platform/modules/mediastream/web_media_stream_track.h
@@ -31,15 +31,12 @@
 #include "media/mojo/mojom/display_media_information.mojom-shared.h"
 #include "third_party/blink/public/platform/web_common.h"
 #include "third_party/blink/public/platform/web_private_ptr.h"
-#include "third_party/blink/public/platform/web_string.h"
 
 namespace blink {
 
 class MediaStreamComponent;
-class MediaStreamTrack;
 class WebAudioSourceProvider;
 class WebMediaStreamSource;
-class WebString;
 
 class WebMediaStreamTrack {
  public:
@@ -48,45 +45,6 @@
   BLINK_PLATFORM_EXPORT static const char kResizeModeNone[];
   BLINK_PLATFORM_EXPORT static const char kResizeModeRescale[];
 
-  struct Settings {
-    bool HasFrameRate() const { return frame_rate >= 0.0; }
-    bool HasWidth() const { return width >= 0; }
-    bool HasHeight() const { return height >= 0; }
-    bool HasAspectRatio() const { return aspect_ratio >= 0.0; }
-    bool HasFacingMode() const { return facing_mode != FacingMode::kNone; }
-    bool HasSampleRate() const { return sample_rate >= 0; }
-    bool HasSampleSize() const { return sample_size >= 0; }
-    bool HasChannelCount() const { return channel_count >= 0; }
-    bool HasLatency() const { return latency >= 0; }
-    bool HasVideoKind() const { return !video_kind.IsNull(); }
-    // The variables are read from
-    // MediaStreamTrack::GetSettings only.
-    double frame_rate = -1.0;
-    int32_t width = -1;
-    int32_t height = -1;
-    double aspect_ratio = -1.0;
-    WebString device_id;
-    WebString group_id;
-    FacingMode facing_mode = FacingMode::kNone;
-    WebString resize_mode;
-    base::Optional<bool> echo_cancellation;
-    base::Optional<bool> auto_gain_control;
-    base::Optional<bool> noise_supression;
-    WebString echo_cancellation_type;
-    int32_t sample_rate = -1;
-    int32_t sample_size = -1;
-    int32_t channel_count = -1;
-    double latency = -1.0;
-
-    // Media Capture Depth Stream Extensions.
-    WebString video_kind;
-
-    // Screen Capture extensions
-    base::Optional<media::mojom::DisplayCaptureSurfaceType> display_surface;
-    base::Optional<bool> logical_surface;
-    base::Optional<media::mojom::CursorCaptureType> cursor;
-  };
-
   enum class ContentHintType {
     kNone,
     kAudioSpeech,
diff --git a/third_party/blink/public/platform/modules/mediastream/web_platform_media_stream_track.h b/third_party/blink/public/platform/modules/mediastream/web_platform_media_stream_track.h
deleted file mode 100644
index 7d2dd36..0000000
--- a/third_party/blink/public/platform/modules/mediastream/web_platform_media_stream_track.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIASTREAM_WEB_PLATFORM_MEDIA_STREAM_TRACK_H_
-#define THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIASTREAM_WEB_PLATFORM_MEDIA_STREAM_TRACK_H_
-
-#include <string>
-
-#include "base/callback.h"
-#include "third_party/blink/public/platform/modules/mediastream/web_media_stream_track.h"
-#include "third_party/blink/public/platform/web_common.h"
-
-namespace blink {
-
-// WebPlatformMediaStreamTrack is a low-level object backing a
-// WebMediaStreamTrack.
-class BLINK_PLATFORM_EXPORT WebPlatformMediaStreamTrack {
- public:
-  explicit WebPlatformMediaStreamTrack(bool is_local_track);
-  virtual ~WebPlatformMediaStreamTrack();
-
-  static WebPlatformMediaStreamTrack* GetTrack(
-      const WebMediaStreamTrack& track);
-
-  virtual void SetEnabled(bool enabled) = 0;
-
-  virtual void SetContentHint(
-      WebMediaStreamTrack::ContentHintType content_hint) = 0;
-
-  // If |callback| is not null, it is invoked when the track has stopped.
-  virtual void StopAndNotify(base::OnceClosure callback) = 0;
-
-  void Stop() { StopAndNotify(base::OnceClosure()); }
-
-  // TODO(hta): Make method pure virtual when all tracks have the method.
-  virtual void GetSettings(WebMediaStreamTrack::Settings& settings) {}
-
-  bool is_local_track() const { return is_local_track_; }
-
- private:
-  const bool is_local_track_;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(WebPlatformMediaStreamTrack);
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIASTREAM_WEB_PLATFORM_MEDIA_STREAM_TRACK_H_
diff --git a/third_party/blink/public/platform/web_mixed_content.h b/third_party/blink/public/platform/web_mixed_content.h
index 902f582..ce1c06da 100644
--- a/third_party/blink/public/platform/web_mixed_content.h
+++ b/third_party/blink/public/platform/web_mixed_content.h
@@ -39,9 +39,11 @@
 // Helper functions related to mixed content checks.
 class WebMixedContent {
  public:
+  enum class CheckModeForPlugin { kStrict, kLax };
+
   BLINK_PLATFORM_EXPORT static WebMixedContentContextType
-  ContextTypeFromRequestContext(mojom::RequestContextType,
-                                bool strict_mixed_content_checking_for_plugin);
+      ContextTypeFromRequestContext(mojom::RequestContextType,
+                                    CheckModeForPlugin);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/public/platform/web_screen_info.h b/third_party/blink/public/platform/web_screen_info.h
index 3ff8ac7..4ae4d18 100644
--- a/third_party/blink/public/platform/web_screen_info.h
+++ b/third_party/blink/public/platform/web_screen_info.h
@@ -31,7 +31,7 @@
 #ifndef THIRD_PARTY_BLINK_PUBLIC_PLATFORM_WEB_SCREEN_INFO_H_
 #define THIRD_PARTY_BLINK_PUBLIC_PLATFORM_WEB_SCREEN_INFO_H_
 
-#include "third_party/blink/public/common/screen_orientation/web_screen_orientation_type.h"
+#include "third_party/blink/public/mojom/widget/screen_orientation.mojom-shared.h"
 #include "ui/gfx/color_space.h"
 #include "ui/gfx/geometry/rect.h"
 
@@ -74,8 +74,9 @@
 
   // This is the orientation 'type' or 'name', as in landscape-primary or
   // portrait-secondary for examples.
-  // See WebScreenOrientationType.h for the full list.
-  WebScreenOrientationType orientation_type = kWebScreenOrientationUndefined;
+  // See public/mojom/screen_orientation.mojom for the full list.
+  mojom::ScreenOrientation orientation_type =
+      mojom::ScreenOrientation::kUndefined;
 
   // This is the orientation angle of the displayed content in degrees.
   // It is the opposite of the physical rotation.
diff --git a/third_party/blink/public/web/web_device_emulation_params.h b/third_party/blink/public/web/web_device_emulation_params.h
index 8d1f51e..e38f2d0d 100644
--- a/third_party/blink/public/web/web_device_emulation_params.h
+++ b/third_party/blink/public/web/web_device_emulation_params.h
@@ -6,7 +6,7 @@
 #define THIRD_PARTY_BLINK_PUBLIC_WEB_WEB_DEVICE_EMULATION_PARAMS_H_
 
 #include "base/optional.h"
-#include "third_party/blink/public/common/screen_orientation/web_screen_orientation_type.h"
+#include "third_party/blink/public/mojom/widget/screen_orientation.mojom-shared.h"
 #include "third_party/blink/public/platform/web_rect.h"
 #include "third_party/blink/public/platform/web_size.h"
 #include "ui/gfx/geometry/point.h"
@@ -18,7 +18,7 @@
 struct WebDeviceEmulationParams {
   enum ScreenPosition { kDesktop, kMobile, kScreenPositionLast = kMobile };
 
-  ScreenPosition screen_position;
+  ScreenPosition screen_position = kDesktop;
 
   // Emulated screen size. Typically full / physical size of the device screen
   // in DIP. Empty size means using default value: original one for kDesktop
@@ -35,37 +35,31 @@
   WebSize view_size;
 
   // If zero, the original device scale factor is preserved.
-  float device_scale_factor;
+  float device_scale_factor = 0;
 
   // Scale the contents of the main frame. The view's size will be scaled by
   // this number when they are not specified in |view_size|.
-  float scale;
+  float scale = 1;
 
   // Forced viewport offset for screenshots during emulation, (-1, -1) for
   // disabled.
-  gfx::PointF viewport_offset;
+  gfx::PointF viewport_offset = gfx::PointF(-1, -1);
 
   // Viewport scale for screenshots during emulation, 0 for current.
-  float viewport_scale;
+  float viewport_scale = 0;
 
-  // Optional screen orientation type, with WebScreenOrientationUndefined
+  // Optional screen orientation type, with mojom::ScreenOrientation::kUndefined
   // value meaning no emulation necessary.
-  WebScreenOrientationType screen_orientation_type;
+  mojom::ScreenOrientation screen_orientation_type =
+      mojom::ScreenOrientation::kUndefined;
 
   // Screen orientation angle, used together with screenOrientationType.
-  int screen_orientation_angle;
+  int screen_orientation_angle = 0;
 
   // Screen window segments dimensions.
   std::vector<gfx::Rect> window_segments;
 
-  WebDeviceEmulationParams()
-      : screen_position(kDesktop),
-        device_scale_factor(0),
-        scale(1),
-        viewport_offset(-1, -1),
-        viewport_scale(0),
-        screen_orientation_type(kWebScreenOrientationUndefined),
-        screen_orientation_angle(0) {}
+  WebDeviceEmulationParams() = default;
 };
 
 inline bool operator==(const WebDeviceEmulationParams& a,
diff --git a/third_party/blink/public/web/web_remote_frame_client.h b/third_party/blink/public/web/web_remote_frame_client.h
index 99f3495..85e60e9d 100644
--- a/third_party/blink/public/web/web_remote_frame_client.h
+++ b/third_party/blink/public/web/web_remote_frame_client.h
@@ -61,15 +61,6 @@
     return base::UnguessableToken::Create();
   }
 
-  // Print out this frame.
-  // |rect| is the rectangular area where this frame resides in its parent
-  // frame.
-  // |canvas| is the canvas we are printing on.
-  // Returns the id of the placeholder content.
-  virtual uint32_t Print(const WebRect& rect, cc::PaintCanvas* canvas) {
-    return 0;
-  }
-
  protected:
   virtual ~WebRemoteFrameClient() = default;
 };
diff --git a/third_party/blink/renderer/bindings/BUILD.gn b/third_party/blink/renderer/bindings/BUILD.gn
index 5370b96..088b271 100644
--- a/third_party/blink/renderer/bindings/BUILD.gn
+++ b/third_party/blink/renderer/bindings/BUILD.gn
@@ -41,21 +41,26 @@
   ]
 }
 
-generate_global_constructors("global_constructors_idls") {
-  sources =
-      core_interface_idl_files_core_only +
-      core_interface_idl_files_modules_dependent +
-      core_buffer_source_type_idl_files + core_callback_interface_idl_files
-  global_objects_file =
-      "$bindings_modules_output_dir/global_objects_modules.pickle"
-  interfaces = modules_core_global_constructors_original_interfaces
-  basenames = modules_core_global_constructors_original_interface_basenames
-  component = "core"
-  output_dir = blink_modules_output_dir
-  deps = [
-    "//third_party/blink/renderer/bindings/modules:modules_global_constructors_idls",
-    "//third_party/blink/renderer/bindings/modules:modules_global_objects",
-  ]
+if (use_blink_v8_binding_new_idl_interface) {
+  group("global_constructors_idls") {
+  }
+} else {
+  generate_global_constructors("global_constructors_idls") {
+    sources =
+        core_interface_idl_files_core_only +
+        core_interface_idl_files_modules_dependent +
+        core_buffer_source_type_idl_files + core_callback_interface_idl_files
+    global_objects_file =
+        "$bindings_modules_output_dir/global_objects_modules.pickle"
+    interfaces = modules_core_global_constructors_original_interfaces
+    basenames = modules_core_global_constructors_original_interface_basenames
+    component = "core"
+    output_dir = blink_modules_output_dir
+    deps = [
+      "//third_party/blink/renderer/bindings/modules:modules_global_constructors_idls",
+      "//third_party/blink/renderer/bindings/modules:modules_global_objects",
+    ]
+  }
 }
 
 template("collect_idl_files") {
@@ -172,6 +177,10 @@
   action_with_pydeps(target_name) {
     script = "${bindings_scripts_dir}/generate_bindings.py"
 
+    if (defined(invoker.pool)) {
+      pool = invoker.pool
+    }
+
     web_idl_database_outputs = get_target_outputs(":web_idl_database")
     web_idl_database = web_idl_database_outputs[0]
 
@@ -179,18 +188,17 @@
     outputs = invoker.outputs
 
     args = [
-      invoker.target,
-      "--web_idl_database",
-      rebase_path(web_idl_database, root_build_dir),
-      "--root_src_dir",
-      rebase_path("//", root_build_dir),
-      "--root_gen_dir",
-      rebase_path(root_gen_dir, root_build_dir),
-      "--output_core_reldir",
-      rebase_path("${bindings_output_dir}/core/v8/", root_gen_dir),
-      "--output_modules_reldir",
-      rebase_path("${bindings_output_dir}/modules/v8/", root_gen_dir),
-    ]
+             "--web_idl_database",
+             rebase_path(web_idl_database, root_build_dir),
+             "--root_src_dir",
+             rebase_path("//", root_build_dir),
+             "--root_gen_dir",
+             rebase_path(root_gen_dir, root_build_dir),
+             "--output_core_reldir",
+             rebase_path("${bindings_output_dir}/core/v8/", root_gen_dir),
+             "--output_modules_reldir",
+             rebase_path("${bindings_output_dir}/modules/v8/", root_gen_dir),
+           ] + invoker.targets
 
     deps = [ ":web_idl_database" ]
   }
@@ -198,18 +206,30 @@
 
 if (use_v8_bind_gen_for_dictionary) {
   generate_bindings("generate_bindings_dictionary") {
-    target = "dictionary"
+    targets = [ "dictionary" ]
     outputs =
         generated_core_dictionary_files + generated_modules_dictionary_files +
         generated_core_testing_dictionary_files
   }
 }
 
-generate_bindings("generate_bindings_enumeration") {
-  target = "enumeration"
+generate_bindings("generate_bindings_all") {
+  # This is a very long running task, so let users know the progress.
+  if (current_toolchain == default_toolchain) {
+    pool = "//:console"
+  }
+
+  targets = [ "enumeration" ]
   outputs = generated_enumeration_sources_in_core +
             generated_enumeration_sources_in_modules +
             generated_enumeration_sources_for_testing_in_core
+  if (use_blink_v8_binding_new_idl_interface) {
+    targets += [ "interface" ]
+    outputs += generated_interface_sources_in_core +
+               generated_interface_sources_in_modules +
+               generated_interface_sources_for_testing_in_core +
+               generated_interface_sources_for_testing_in_modules
+  }
 }
 
 action_with_pydeps("generate_high_entropy_list") {
diff --git a/third_party/blink/renderer/bindings/core/BUILD.gn b/third_party/blink/renderer/bindings/core/BUILD.gn
index a6f4fe8..5d32b25 100644
--- a/third_party/blink/renderer/bindings/core/BUILD.gn
+++ b/third_party/blink/renderer/bindings/core/BUILD.gn
@@ -30,15 +30,20 @@
   deps = []
 }
 
-generate_global_constructors("core_global_constructors_idls") {
-  sources =
-      core_interface_idl_files_core_only +
-      core_interface_idl_files_modules_dependent +
-      core_buffer_source_type_idl_files + core_callback_interface_idl_files
-  global_objects_file = "$bindings_core_output_dir/global_objects_core.pickle"
-  interfaces = core_global_constructors_original_interfaces
-  basenames = core_global_constructors_original_interface_basenames
-  component = "core"
-  output_dir = blink_core_output_dir
-  deps = [ ":core_global_objects" ]
+if (use_blink_v8_binding_new_idl_interface) {
+  group("core_global_constructors_idls") {
+  }
+} else {
+  generate_global_constructors("core_global_constructors_idls") {
+    sources =
+        core_interface_idl_files_core_only +
+        core_interface_idl_files_modules_dependent +
+        core_buffer_source_type_idl_files + core_callback_interface_idl_files
+    global_objects_file = "$bindings_core_output_dir/global_objects_core.pickle"
+    interfaces = core_global_constructors_original_interfaces
+    basenames = core_global_constructors_original_interface_basenames
+    component = "core"
+    output_dir = blink_core_output_dir
+    deps = [ ":core_global_objects" ]
+  }
 }
diff --git a/third_party/blink/renderer/bindings/core/v8/BUILD.gn b/third_party/blink/renderer/bindings/core/v8/BUILD.gn
index 78f84ee..6360a3f 100644
--- a/third_party/blink/renderer/bindings/core/v8/BUILD.gn
+++ b/third_party/blink/renderer/bindings/core/v8/BUILD.gn
@@ -12,29 +12,86 @@
 
 visibility = [ "//third_party/blink/renderer/*" ]
 
-blink_core_sources("generated") {
-  sources = generated_enumeration_sources_in_core
-  deps =
-      [ "//third_party/blink/renderer/bindings:generate_bindings_enumeration" ]
+if (use_blink_v8_binding_new_idl_interface) {
+  blink_core_sources("v8") {
+    visibility = []
+    visibility = [ "//third_party/blink/renderer/core" ]
+
+    sources = generated_enumeration_sources_in_core
+    if (use_blink_v8_binding_new_idl_interface) {
+      sources += generated_interface_sources_in_core
+    }
+
+    deps = [
+      ":generated",
+      "//third_party/blink/renderer/platform",
+      "//v8",
+    ]
+  }
+} else {
+  group("v8") {
+  }
 }
 
-blink_core_sources("generated_for_testing") {
-  sources = generated_enumeration_sources_for_testing_in_core
-  deps =
-      [ "//third_party/blink/renderer/bindings:generate_bindings_enumeration" ]
+if (use_blink_v8_binding_new_idl_interface) {
+  jumbo_source_set("testing") {
+    testonly = true
+
+    visibility = []
+    visibility = [ "//third_party/blink/renderer/core/*" ]
+
+    configs += [
+      "//third_party/blink/renderer:config",
+      "//third_party/blink/renderer:inside_blink",
+    ]
+
+    sources = generated_enumeration_sources_for_testing_in_core
+    if (use_blink_v8_binding_new_idl_interface) {
+      sources += generated_interface_sources_for_testing_in_core
+    }
+
+    deps = [
+      ":generated",
+      ":testing_internal",
+      "//third_party/blink/renderer/platform",
+      "//v8",
+    ]
+  }
+} else {
+  group("testing") {
+    testonly = true
+
+    deps = [ ":testing_internal" ]
+  }
 }
 
-generate_origin_trial_features("bindings_core_origin_trial_features") {
-  sources = core_interface_idl_files_core_only +
-            core_interface_idl_files_modules_dependent +
-            core_partial_definition_idl_files +
-            core_global_constructors_generated_idl_files
-  component = "core"
-  output_dir = bindings_core_output_dir + "/v8"
-  deps = [
-    "//third_party/blink/renderer/bindings/core:core_global_constructors_idls",
-    "//third_party/blink/renderer/bindings/core:interfaces_info_core",
+group("generated") {
+  visibility = []
+  visibility = [
+    "//third_party/blink/renderer/bindings/core/v8/*",
+    "//third_party/blink/renderer/core/*",
   ]
+
+  public_deps =
+      [ "//third_party/blink/renderer/bindings:generate_bindings_all" ]
+}
+
+if (use_blink_v8_binding_new_idl_interface) {
+  group("bindings_core_origin_trial_features") {
+  }
+} else {
+  generate_origin_trial_features("bindings_core_origin_trial_features") {
+    sources = core_interface_idl_files_core_only +
+              core_interface_idl_files_modules_dependent +
+              core_partial_definition_idl_files +
+              core_global_constructors_generated_idl_files
+    component = "core"
+    output_dir = bindings_core_output_dir + "/v8"
+    deps = [
+      "//third_party/blink/renderer/bindings/core:core_global_constructors_idls",
+      "//third_party/blink/renderer/bindings/core:interfaces_info_core",
+    ]
+  }
 }
 
 group("bindings_core_v8_generated") {
@@ -54,6 +111,13 @@
 idl_compiler("generate_bindings_core_v8_interfaces") {
   sources = core_definition_idl_files + core_testing_definition_idl_files +
             generated_webcore_testing_idl_files
+  if (use_blink_v8_binding_new_idl_interface) {
+    sources -=
+        core_interface_idl_files_core_only +
+        core_interface_idl_files_modules_dependent + webcore_testing_idl_files +
+        webcore_testing_idl_with_modules_dependency_files +
+        generated_webcore_testing_idl_files
+  }
   if (use_v8_bind_gen_for_dictionary) {
     sources -= core_dictionary_idl_files
     sources -= core_testing_dictionary_idl_files
@@ -96,8 +160,11 @@
     _non_testing_sources -= generated_core_testing_dictionary_files
   }
 
-  sources = _non_testing_sources + bindings_core_generated_interface_files +
-            get_target_outputs(":bindings_core_origin_trial_features")
+  sources = _non_testing_sources + bindings_core_generated_interface_files
+
+  if (!use_blink_v8_binding_new_idl_interface) {
+    sources += get_target_outputs(":bindings_core_origin_trial_features")
+  }
 
   deps = [
     ":bindings_core_origin_trial_features",
@@ -109,7 +176,9 @@
 # Compile the test sources generated above. This test target doesn't count
 # as part of the "core" component so shouldn't use the blink_core_sources for
 # linking on Windows.
-jumbo_source_set("testing") {
+jumbo_source_set("testing_internal") {
+  testonly = true
+
   sources = generated_core_testing_dictionary_files +
             generated_core_testing_callback_function_files
 
@@ -119,8 +188,14 @@
   if (!use_v8_bind_gen_for_dictionary) {
     testing_idl_interface_files += core_testing_dictionary_idl_files
   }
+
+  if (use_blink_v8_binding_new_idl_interface) {
+    idl_files = core_testing_dictionary_idl_files
+  } else {
+    idl_files = testing_idl_interface_files
+  }
   sources += process_file_template(
-          testing_idl_interface_files,
+          idl_files,
           [
             "$bindings_core_v8_output_dir/v8_{{source_name_part}}.cc",
             "$bindings_core_v8_output_dir/v8_{{source_name_part}}.h",
@@ -134,7 +209,7 @@
     ":bindings_core_v8_generated",
     "//skia",
     "//third_party/blink/renderer/bindings:generate_v8_bindings",
-    "//third_party/blink/renderer/core:all_generators",
+    "//third_party/blink/renderer/core",
     "//third_party/blink/renderer/platform",
     "//v8",
   ]
diff --git a/third_party/blink/renderer/bindings/core/v8/generated.gni b/third_party/blink/renderer/bindings/core/v8/generated.gni
index c7044b7b..045c931 100644
--- a/third_party/blink/renderer/bindings/core/v8/generated.gni
+++ b/third_party/blink/renderer/bindings/core/v8/generated.gni
@@ -208,6 +208,17 @@
             "$bindings_core_v8_output_dir/v8_{{source_name_part}}.h",
           ])
 }
+if (use_blink_v8_binding_new_idl_interface) {
+  bindings_core_generated_interface_files = []
+  bindings_core_generated_interface_files =
+      process_file_template(
+          core_buffer_source_type_idl_files +
+              core_callback_interface_idl_files + core_dictionary_idl_files,
+          [
+            "$bindings_core_v8_output_dir/v8_{{source_name_part}}.cc",
+            "$bindings_core_v8_output_dir/v8_{{source_name_part}}.h",
+          ])
+}
 
 generated_core_dictionary_files = []
 
diff --git a/third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_deserializer.cc b/third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_deserializer.cc
index c4550b57..9c1d157 100644
--- a/third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_deserializer.cc
+++ b/third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_deserializer.cc
@@ -4,6 +4,8 @@
 
 #include "third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_deserializer.h"
 
+#include <limits>
+
 #include "base/numerics/checked_math.h"
 #include "base/optional.h"
 #include "base/time/time.h"
@@ -574,6 +576,7 @@
         return nullptr;
       uint32_t index = 0;
       if (!ReadUint32(&index) || !transferred_stream_ports_ ||
+          index == std::numeric_limits<decltype(index)>::max() ||
           index + 1 >= transferred_stream_ports_->size()) {
         return nullptr;
       }
diff --git a/third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_serializer_test.cc b/third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_serializer_test.cc
index 0025284..7f5de79 100644
--- a/third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_serializer_test.cc
+++ b/third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_serializer_test.cc
@@ -50,6 +50,7 @@
 #include "third_party/blink/renderer/core/mojo/mojo_handle.h"
 #include "third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.h"
 #include "third_party/blink/renderer/core/streams/readable_stream.h"
+#include "third_party/blink/renderer/core/streams/transform_stream.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/file_metadata.h"
 #include "third_party/blink/renderer/platform/graphics/unaccelerated_static_bitmap_image.h"
@@ -1906,6 +1907,63 @@
   EXPECT_FALSE(transferred->locked());
 }
 
+TEST(V8ScriptValueSerializerTest, TransformStreamIntegerOverflow) {
+  ScopedTransferableStreamsForTest enable_transferable_streams(true);
+
+  V8TestingScope scope;
+  auto* isolate = scope.GetIsolate();
+  auto* script_state = scope.GetScriptState();
+
+  // Create a real SerializedScriptValue so that the MessagePorts are set up
+  // properly.
+  auto* ts = TransformStream::Create(script_state, ASSERT_NO_EXCEPTION);
+  v8::Local<v8::Value> wrapper = ToV8(ts, script_state);
+  HeapVector<ScriptValue> transferable_array = {ScriptValue(isolate, wrapper)};
+  Transferables transferables;
+  ASSERT_TRUE(SerializedScriptValue::ExtractTransferables(
+      isolate, transferable_array, transferables, ASSERT_NO_EXCEPTION));
+
+  // Extract message ports and disentangle them.
+  Vector<MessagePortChannel> channels = MessagePort::DisentanglePorts(
+      scope.GetExecutionContext(), transferables.message_ports,
+      ASSERT_NO_EXCEPTION);
+
+  V8ScriptValueSerializer::Options serialize_options;
+  serialize_options.transferables = &transferables;
+  V8ScriptValueSerializer serializer(script_state, serialize_options);
+  scoped_refptr<SerializedScriptValue> serialized_script_value =
+      serializer.Serialize(wrapper, ASSERT_NO_EXCEPTION);
+  ASSERT_TRUE(serialized_script_value);
+
+  // Now create a corrupted SerializedScriptValue using the same message ports.
+  // The final 5 bytes is the offset of the two message ports inside the
+  // transferred message port array. In order to trigger integer overflow this
+  // is set to 0xffffffff, encoded as a varint.
+  char serialized_value[] = {0xff, 0x14, 0xff, 0x0d, 0x5c, 0x6d,
+                             0xff, 0xff, 0xff, 0xff, 0x0f};
+
+  auto corrupted_serialized_script_value =
+      SerializedScriptValue::Create(serialized_value, sizeof(serialized_value));
+  corrupted_serialized_script_value->GetStreamChannels() =
+      serialized_script_value->GetStreamChannels();
+
+  // Entangle the message ports.
+  MessagePortArray* transferred_message_ports = MessagePort::EntanglePorts(
+      *scope.GetExecutionContext(), std::move(channels));
+
+  UnpackedSerializedScriptValue* unpacked = SerializedScriptValue::Unpack(
+      std::move(corrupted_serialized_script_value));
+  V8ScriptValueDeserializer::Options deserialize_options;
+  deserialize_options.message_ports = transferred_message_ports;
+  V8ScriptValueDeserializer deserializer(script_state, unpacked,
+                                         deserialize_options);
+  // If this doesn't crash then the test succeeded.
+  v8::Local<v8::Value> result = deserializer.Deserialize();
+
+  // Deserialization should have failed.
+  EXPECT_TRUE(result->IsNull());
+}
+
 TEST(V8ScriptValueSerializerTest, RoundTripDOMException) {
   V8TestingScope scope;
   DOMException* exception =
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_string_resource.h b/third_party/blink/renderer/bindings/core/v8/v8_string_resource.h
index cbf0530..387ac155 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_string_resource.h
+++ b/third_party/blink/renderer/bindings/core/v8/v8_string_resource.h
@@ -99,7 +99,7 @@
                                mode_);
     }
 
-    return g_null_atom;
+    return string_;
   }
 
  private:
diff --git a/third_party/blink/renderer/bindings/modules/BUILD.gn b/third_party/blink/renderer/bindings/modules/BUILD.gn
index 2849943e..5df6401 100644
--- a/third_party/blink/renderer/bindings/modules/BUILD.gn
+++ b/third_party/blink/renderer/bindings/modules/BUILD.gn
@@ -127,15 +127,20 @@
   deps = [ "//third_party/blink/renderer/bindings/core:core_global_objects" ]
 }
 
-generate_global_constructors("modules_global_constructors_idls") {
-  sources = modules_idl_files
-  global_objects_file =
-      "$bindings_modules_output_dir/global_objects_modules.pickle"
-  interfaces = modules_global_constructors_original_interfaces
-  basenames = modules_global_constructors_original_interface_basenames
-  component = "modules"
-  output_dir = blink_modules_output_dir
-  deps = [ ":modules_global_objects" ]
+if (use_blink_v8_binding_new_idl_interface) {
+  group("modules_global_constructors_idls") {
+  }
+} else {
+  generate_global_constructors("modules_global_constructors_idls") {
+    sources = modules_idl_files
+    global_objects_file =
+        "$bindings_modules_output_dir/global_objects_modules.pickle"
+    interfaces = modules_global_constructors_original_interfaces
+    basenames = modules_global_constructors_original_interface_basenames
+    component = "modules"
+    output_dir = blink_modules_output_dir
+    deps = [ ":modules_global_objects" ]
+  }
 }
 
 # Compile the sources produced above. This will get linked into "modules".
diff --git a/third_party/blink/renderer/bindings/modules/v8/BUILD.gn b/third_party/blink/renderer/bindings/modules/v8/BUILD.gn
index 4788ad8..afaec7d 100644
--- a/third_party/blink/renderer/bindings/modules/v8/BUILD.gn
+++ b/third_party/blink/renderer/bindings/modules/v8/BUILD.gn
@@ -11,10 +11,81 @@
 
 visibility = [ "//third_party/blink/renderer/*" ]
 
-blink_modules_sources("generated") {
-  sources = generated_enumeration_sources_in_modules
-  deps =
-      [ "//third_party/blink/renderer/bindings:generate_bindings_enumeration" ]
+if (use_blink_v8_binding_new_idl_interface) {
+  blink_modules_sources("v8") {
+    visibility = []
+    visibility = [ "//third_party/blink/renderer/modules" ]
+
+    sources = generated_enumeration_sources_in_modules
+    if (use_blink_v8_binding_new_idl_interface) {
+      sources += generated_interface_sources_in_modules
+    }
+
+    deps = [
+      ":generated",
+      "//third_party/blink/renderer/platform",
+      "//v8",
+    ]
+  }
+} else {
+  group("v8") {
+  }
+}
+
+if (use_blink_v8_binding_new_idl_interface) {
+  jumbo_source_set("testing") {
+    testonly = true
+
+    visibility = []
+    visibility = [ "//third_party/blink/renderer/modules/*" ]
+
+    configs += [
+      "//third_party/blink/renderer:config",
+      "//third_party/blink/renderer:inside_blink",
+    ]
+
+    sources = []
+    if (use_blink_v8_binding_new_idl_interface) {
+      sources += generated_interface_sources_for_testing_in_modules
+    }
+
+    deps = [
+      ":generated",
+      "//third_party/blink/renderer/platform",
+      "//v8",
+    ]
+  }
+} else {
+  jumbo_source_set("testing") {
+    testonly = true
+
+    configs += [
+      "//third_party/blink/renderer:config",
+      "//third_party/blink/renderer:inside_blink",
+    ]
+
+    sources = [
+      "$bindings_modules_v8_output_dir/v8_internals_partial.cc",
+      "$bindings_modules_v8_output_dir/v8_internals_partial.h",
+    ]
+
+    deps = [
+      ":generate_bindings_modules_v8_partial_interfaces_for_testing",
+      "//third_party/blink/renderer/platform",
+      "//v8",
+    ]
+  }
+}
+
+group("generated") {
+  visibility = []
+  visibility = [
+    "//third_party/blink/renderer/bindings/modules/v8/*",
+    "//third_party/blink/renderer/modules/*",
+  ]
+
+  public_deps =
+      [ "//third_party/blink/renderer/bindings:generate_bindings_all" ]
 }
 
 group("bindings_modules_v8_generated") {
@@ -30,7 +101,20 @@
 }
 
 idl_compiler("generate_bindings_modules_v8_interfaces") {
+  if (use_blink_v8_binding_new_idl_interface) {
+    # modules_idl_files = interfaces + callback interfaces
+    # modules_non_callback_idl_files = interfaces only
+    set_sources_assignment_filter([ "*_callback.idl" ])
+    sources = modules_idl_files
+    modules_non_callback_idl_files = sources
+    set_sources_assignment_filter([])
+    sources = []
+  }
+
   sources = modules_definition_idl_files
+  if (use_blink_v8_binding_new_idl_interface) {
+    sources -= modules_non_callback_idl_files
+  }
   if (use_v8_bind_gen_for_dictionary) {
     sources -= modules_dictionary_idl_files
   }
@@ -51,51 +135,71 @@
   target_component = "modules"
 }
 
-idl_compiler("generate_bindings_modules_v8_partial_interfaces") {
-  sources = core_interface_idl_files_modules_dependent
-  output_dir = bindings_modules_v8_output_dir
-  output_name_suffix = "_partial"
-  target_component = "modules"
+if (use_blink_v8_binding_new_idl_interface) {
+  group("generate_bindings_modules_v8_partial_interfaces") {
+  }
+} else {
+  idl_compiler("generate_bindings_modules_v8_partial_interfaces") {
+    sources = core_interface_idl_files_modules_dependent
+    output_dir = bindings_modules_v8_output_dir
+    output_name_suffix = "_partial"
+    target_component = "modules"
+  }
 }
 
-idl_compiler("generate_bindings_modules_v8_partial_interfaces_for_testing") {
-  sources = webcore_testing_idl_with_modules_dependency_files
-  output_dir = bindings_modules_v8_output_dir
-  output_name_suffix = "_partial"
-  target_component = "modules"
+if (use_blink_v8_binding_new_idl_interface) {
+  group("generate_bindings_modules_v8_partial_interfaces_for_testing") {
+  }
+} else {
+  idl_compiler("generate_bindings_modules_v8_partial_interfaces_for_testing") {
+    sources = webcore_testing_idl_with_modules_dependency_files
+    output_dir = bindings_modules_v8_output_dir
+    output_name_suffix = "_partial"
+    target_component = "modules"
+  }
 }
 
-generate_origin_trial_features("bindings_modules_origin_trial_features") {
-  sources = modules_idl_files + modules_generated_dependency_idl_files +
-            modules_dependency_idl_files
-  component = "modules"
-  output_dir = bindings_modules_output_dir + "/v8"
-  deps = [
-    "//third_party/blink/renderer/bindings:global_constructors_idls",
-    "//third_party/blink/renderer/bindings/modules:interfaces_info_modules",
-    "//third_party/blink/renderer/bindings/modules:modules_global_constructors_idls",
-  ]
+if (use_blink_v8_binding_new_idl_interface) {
+  group("bindings_modules_origin_trial_features") {
+  }
+} else {
+  generate_origin_trial_features("bindings_modules_origin_trial_features") {
+    sources = modules_idl_files + modules_generated_dependency_idl_files +
+              modules_dependency_idl_files
+    component = "modules"
+    output_dir = bindings_modules_output_dir + "/v8"
+    deps = [
+      "//third_party/blink/renderer/bindings:global_constructors_idls",
+      "//third_party/blink/renderer/bindings/modules:interfaces_info_modules",
+      "//third_party/blink/renderer/bindings/modules:modules_global_constructors_idls",
+    ]
+  }
 }
 
-action("bindings_modules_v8_generated_init_partial") {
-  script = "$bindings_scripts_dir/generate_init_partial_interfaces.py"
+if (use_blink_v8_binding_new_idl_interface) {
+  group("bindings_modules_v8_generated_init_partial") {
+  }
+} else {
+  action("bindings_modules_v8_generated_init_partial") {
+    script = "$bindings_scripts_dir/generate_init_partial_interfaces.py"
 
-  inputs = [ "$bindings_output_dir/interfaces_info.pickle" ]
-  outputs = [ bindings_modules_generated_init_partial_interfaces_file ]
+    inputs = [ "$bindings_output_dir/interfaces_info.pickle" ]
+    outputs = [ bindings_modules_generated_init_partial_interfaces_file ]
 
-  # Put the IDL file list in a response file to avoid command-line limits.
-  response_file_contents =
-      rebase_path(core_interface_idl_files_modules_dependent, root_build_dir)
+    # Put the IDL file list in a response file to avoid command-line limits.
+    response_file_contents =
+        rebase_path(core_interface_idl_files_modules_dependent, root_build_dir)
 
-  args = [
-    "--idl-files-list",
-    "{{response_file_name}}",
-    "--output",
-    rebase_path(bindings_modules_generated_init_partial_interfaces_file,
-                root_build_dir),
-  ]
+    args = [
+      "--idl-files-list",
+      "{{response_file_name}}",
+      "--output",
+      rebase_path(bindings_modules_generated_init_partial_interfaces_file,
+                  root_build_dir),
+    ]
 
-  deps = [ "//third_party/blink/renderer/bindings:interfaces_info" ]
+    deps = [ "//third_party/blink/renderer/bindings:interfaces_info" ]
+  }
 }
 
 # Note that this intentionally depends on the generator target of the mojom
@@ -124,12 +228,14 @@
 blink_modules_sources("bindings_modules_impl") {
   # ":generate_bindings_modules_v8_partial_interfaces_for_testing" is not
   # included here.
-  sources =
-      get_target_outputs(":generate_bindings_modules_v8_interfaces") +
-      get_target_outputs(":bindings_modules_impl_generated") +
-      get_target_outputs(":generate_bindings_modules_v8_partial_interfaces") +
-      get_target_outputs(":bindings_modules_v8_generated_init_partial") +
-      get_target_outputs(":bindings_modules_origin_trial_features")
+  sources = get_target_outputs(":generate_bindings_modules_v8_interfaces") +
+            get_target_outputs(":bindings_modules_impl_generated")
+  if (!use_blink_v8_binding_new_idl_interface) {
+    sources +=
+        get_target_outputs(":generate_bindings_modules_v8_partial_interfaces") +
+        get_target_outputs(":bindings_modules_v8_generated_init_partial") +
+        get_target_outputs(":bindings_modules_origin_trial_features")
+  }
 
   sources += generated_modules_dictionary_files
 
diff --git a/third_party/blink/renderer/bindings/modules/v8/v8.gni b/third_party/blink/renderer/bindings/modules/v8/v8.gni
index 3bc8ad79..fc13510 100644
--- a/third_party/blink/renderer/bindings/modules/v8/v8.gni
+++ b/third_party/blink/renderer/bindings/modules/v8/v8.gni
@@ -2,20 +2,21 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//third_party/blink/renderer/config.gni")
 import("custom/custom.gni")
 
 # Make the files absolute so they can be imported to anywhere.
 bindings_modules_v8_files =
     get_path_info(
         [
+          "module_bindings_initializer.cc",
+          "module_bindings_initializer.h",
           "serialization/serialized_script_value_for_modules_factory.cc",
           "serialization/serialized_script_value_for_modules_factory.h",
           "serialization/v8_script_value_deserializer_for_modules.cc",
           "serialization/v8_script_value_deserializer_for_modules.h",
           "serialization/v8_script_value_serializer_for_modules.cc",
           "serialization/v8_script_value_serializer_for_modules.h",
-          "module_bindings_initializer.cc",
-          "module_bindings_initializer.h",
           "to_v8_for_modules.h",
           "v8_binding_for_modules.cc",
           "v8_binding_for_modules.h",
@@ -24,6 +25,13 @@
           "webgl_any.h",
         ],
         "abspath") + bindings_modules_v8_custom_files
+if (use_blink_v8_binding_new_idl_interface) {
+  bindings_modules_v8_files += get_path_info([
+                                               "v8_context_snapshot_impl.cc",
+                                               "v8_context_snapshot_impl.h",
+                                             ],
+                                             "abspath")
+}
 
 bindings_modules_v8_unittest_files =
     get_path_info(
diff --git a/third_party/blink/renderer/bindings/modules/v8/v8_context_snapshot_impl.cc b/third_party/blink/renderer/bindings/modules/v8/v8_context_snapshot_impl.cc
index 5b39b333..3b03006 100644
--- a/third_party/blink/renderer/bindings/modules/v8/v8_context_snapshot_impl.cc
+++ b/third_party/blink/renderer/bindings/modules/v8/v8_context_snapshot_impl.cc
@@ -130,6 +130,7 @@
 
  public:
   v8::Isolate* isolate;
+  const DOMWrapperWorld& world;
   HTMLDocument* html_document;
 };
 
@@ -206,13 +207,19 @@
       reinterpret_cast<DeserializerData*>(data);
 
   switch (static_cast<InternalFieldSerializedValue>(value)) {
-    case InternalFieldSerializedValue::kSwHTMLDocument:
+    case InternalFieldSerializedValue::kSwHTMLDocument: {
       CHECK_EQ(index, kV8DOMWrapperObjectIndex);
       CHECK(deserializer_data->html_document);
+      CHECK(deserializer_data->world.IsMainWorld());
       V8DOMWrapper::SetNativeInfo(deserializer_data->isolate, object,
                                   V8HTMLDocument::GetWrapperTypeInfo(),
                                   deserializer_data->html_document);
+      bool result = deserializer_data->html_document->SetWrapper(
+          deserializer_data->isolate, V8HTMLDocument::GetWrapperTypeInfo(),
+          object);
+      CHECK(result);
       break;
+    }
     case InternalFieldSerializedValue::kSwWindow:
       CHECK_EQ(index, kV8DOMWrapperObjectIndex);
       // The global object's internal fields will be set in LocalWindowProxy.
@@ -220,9 +227,9 @@
     case InternalFieldSerializedValue::kWtiHTMLDocument:
       CHECK_EQ(index, kV8DOMWrapperTypeIndex);
       CHECK(deserializer_data->html_document);
-      V8DOMWrapper::SetNativeInfo(deserializer_data->isolate, object,
-                                  V8HTMLDocument::GetWrapperTypeInfo(),
-                                  deserializer_data->html_document);
+      CHECK(deserializer_data->world.IsMainWorld());
+      // The internal field of WrapperTypeInfo must be filled in
+      // kSwHTMLDocument case.
       break;
     case InternalFieldSerializedValue::kWtiWindow:
       CHECK_EQ(index, kV8DOMWrapperTypeIndex);
@@ -313,7 +320,7 @@
     html_document = nullptr;
   }
 
-  DeserializerData deserializer_data = {isolate, html_document};
+  DeserializerData deserializer_data = {isolate, world, html_document};
   v8::DeserializeInternalFieldsCallback internal_field_desrializer(
       DeserializeInternalFieldCallback, &deserializer_data);
   return v8::Context::FromSnapshot(
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index ae240dd..b403390 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -73,7 +73,6 @@
     "//skia",
     "//third_party/blink/renderer/bindings/core/v8:bindings_core_origin_trial_features",
     "//third_party/blink/renderer/bindings/core/v8:bindings_core_v8_generated",
-    "//third_party/blink/renderer/bindings/core/v8:generated",
     "//third_party/blink/renderer/core/inspector:generated",
     "//third_party/blink/renderer/core/probe:generated",
     "//third_party/iccjpeg",
@@ -100,6 +99,7 @@
     "//skia",
     "//third_party/angle:translator",
     "//third_party/blink/public/mojom:mojom_broadcastchannel_bindings_blink",
+    "//third_party/blink/renderer/bindings/core/v8:generated",
     "//third_party/blink/renderer/core/animation:buildflags",
     "//third_party/blink/renderer/core/inspector:generated",
     "//third_party/blink/renderer/core/probe:generated",
@@ -163,6 +163,7 @@
     ":core_generated",
     "//skia",
     "//third_party/blink/public/mojom:mojom_broadcastchannel_bindings_blink",
+    "//third_party/blink/renderer/bindings/core/v8",
     "//third_party/blink/renderer/core/accessibility",
     "//third_party/blink/renderer/core/animation",
     "//third_party/blink/renderer/core/animation_frame",
diff --git a/third_party/blink/renderer/core/core_idl_files.gni b/third_party/blink/renderer/core/core_idl_files.gni
index 10115e9a..f1b511ee 100644
--- a/third_party/blink/renderer/core/core_idl_files.gni
+++ b/third_party/blink/renderer/core/core_idl_files.gni
@@ -19,12 +19,16 @@
 ]
 
 # The size and the order in the following list must match to the previous one.
-core_global_constructors_original_interface_basenames = [
-  "dedicated_worker_global_scope",
-  "layout_worklet_global_scope",
-  "shared_worker_global_scope",
-  "window",
-]
+if (use_blink_v8_binding_new_idl_interface) {
+  core_global_constructors_original_interface_basenames = []
+} else {
+  core_global_constructors_original_interface_basenames = [
+    "dedicated_worker_global_scope",
+    "layout_worklet_global_scope",
+    "shared_worker_global_scope",
+    "window",
+  ]
+}
 
 # The interfaces aren't technically files, but we can treat them as file names
 # to get process_file_template to generate lists of IDL files corresponding
diff --git a/third_party/blink/renderer/core/css/css_font_face.h b/third_party/blink/renderer/core/css/css_font_face.h
index 90c2b68f..e340060 100644
--- a/third_party/blink/renderer/core/css/css_font_face.h
+++ b/third_party/blink/renderer/core/css/css_font_face.h
@@ -53,6 +53,10 @@
     DCHECK(font_face_);
   }
 
+  // Front source is the first successfully loaded source.
+  const CSSFontFaceSource* FrontSource() const {
+    return sources_.IsEmpty() ? nullptr : sources_.front();
+  }
   FontFace* GetFontFace() const { return font_face_; }
 
   scoped_refptr<UnicodeRangeSet> Ranges() { return ranges_; }
diff --git a/third_party/blink/renderer/core/css/css_font_face_source.h b/third_party/blink/renderer/core/css/css_font_face_source.h
index c3cc2a3f..d38eef5 100644
--- a/third_party/blink/renderer/core/css/css_font_face_source.h
+++ b/third_party/blink/renderer/core/css/css_font_face_source.h
@@ -40,6 +40,7 @@
 
 class FontDescription;
 class SimpleFontData;
+class FontCustomPlatformData;
 
 class CORE_EXPORT CSSFontFaceSource
     : public GarbageCollected<CSSFontFaceSource> {
@@ -56,6 +57,14 @@
   virtual bool IsLoaded() const { return true; }
   virtual bool IsValid() const { return true; }
 
+  // Returns nullptr unless the source is a loaded RemoteFontFaceSource.
+  virtual String GetURL() const { return g_null_atom; }
+
+  // Returns nullptr unless the source is a loaded RemoteFontFaceSource.
+  virtual const FontCustomPlatformData* GetCustomPlaftormData() const {
+    return nullptr;
+  }
+
   scoped_refptr<SimpleFontData> GetFontData(const FontDescription&,
                                             const FontSelectionCapabilities&);
 
diff --git a/third_party/blink/renderer/core/css/font_update_invalidation_test.cc b/third_party/blink/renderer/core/css/font_update_invalidation_test.cc
index 1bf60fc..7ceb7a0 100644
--- a/third_party/blink/renderer/core/css/font_update_invalidation_test.cc
+++ b/third_party/blink/renderer/core/css/font_update_invalidation_test.cc
@@ -180,4 +180,86 @@
   main_resource.Finish();
 }
 
+// https://crbug.com/1101483
+TEST_F(FontUpdateInvalidationTest, FallbackBetweenPendingAndLoadedCustomFonts) {
+  SimRequest main_resource("https://example.com", "text/html");
+  SimRequest slow_font_resource("https://example.com/nonexist.woff2",
+                                "font/woff2");
+  SimRequest fast_font_resource("https://example.com/Ahem.woff2", "font/woff2");
+
+  LoadURL("https://example.com");
+  main_resource.Complete(R"HTML(
+    <!doctype html>
+    <link rel="preload" href="https://example.com/Ahem.woff2" as="font" crossorigin>
+    <style>
+      @font-face {
+        font-family: slow-font;
+        src: url(https://example.com/nonexist.woff2) format("woff2");
+      }
+      @font-face {
+        font-family: fast-font;
+        src: url(https://example.com/Ahem.woff2) format("woff2");
+      }
+      #target {
+        font: 25px/1 slow-font, fast-font, monospace;
+      }
+    </style>
+    <span id=target>0123456789</span>
+  )HTML");
+
+  fast_font_resource.Complete(ReadAhemWoff2());
+
+  // While slow-font is pending and fast-font is already available, we should
+  // use it to render the page.
+  Compositor().BeginFrame();
+  Element* target = GetDocument().getElementById("target");
+  DCHECK_EQ(250, target->OffsetWidth());
+
+  slow_font_resource.Finish();
+
+  Compositor().BeginFrame();
+  EXPECT_EQ(250, target->OffsetWidth());
+}
+
+// https://crrev.com/1397423004
+TEST_F(FontUpdateInvalidationTest, NoRedundantLoadingForSegmentedFont) {
+  SimRequest main_resource("https://example.com", "text/html");
+  SimRequest font_resource("https://example.com/font2.woff2", "font/woff2");
+
+  LoadURL("https://example.com");
+  main_resource.Complete(R"HTML(
+    <!doctype html>
+    <style>
+      @font-face {
+        font-family: custom-font;
+        /* We intentionally leave it unmocked, so that the test fails if it
+         * attempts to load font1.woff. */
+        src: url(https://example.com/font1.woff2) format("woff2");
+        unicode-range: 0x00-0xFF;
+      }
+      @font-face {
+        font-family: custom-font;
+        src: url(https://example.com/font2.woff2) format("woff2");
+        unicode-range: 0x30-0x39; /* '0' to '9' */
+      }
+      #target {
+        font: 25px/1 custom-font, monospace;
+      }
+    </style>
+    <span id=target>0123456789</span>
+  )HTML");
+
+  // Trigger frame to start font loading
+  Compositor().BeginFrame();
+  Element* target = GetDocument().getElementById("target");
+  DCHECK_GT(250, target->OffsetWidth());
+
+  font_resource.Complete(ReadAhemWoff2());
+
+  Compositor().BeginFrame();
+  DCHECK_EQ(250, target->OffsetWidth());
+
+  // Test finishes without triggering a redundant load of font1.woff.
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/remote_font_face_source.cc b/third_party/blink/renderer/core/css/remote_font_face_source.cc
index de65348..2e97ac1 100644
--- a/third_party/blink/renderer/core/css/remote_font_face_source.cc
+++ b/third_party/blink/renderer/core/css/remote_font_face_source.cc
@@ -196,6 +196,7 @@
   histograms_.RecordRemoteFont(font);
 
   custom_font_data_ = font->GetCustomFontData();
+  url_ = resource->Url().GetString();
 
   // FIXME: Provide more useful message such as OTS rejection reason.
   // See crbug.com/97467
diff --git a/third_party/blink/renderer/core/css/remote_font_face_source.h b/third_party/blink/renderer/core/css/remote_font_face_source.h
index 545ce57a..0da2853 100644
--- a/third_party/blink/renderer/core/css/remote_font_face_source.h
+++ b/third_party/blink/renderer/core/css/remote_font_face_source.h
@@ -33,6 +33,12 @@
   bool IsLoaded() const override;
   bool IsValid() const override;
 
+  String GetURL() const override { return url_; }
+
+  const FontCustomPlatformData* GetCustomPlaftormData() const override {
+    return custom_font_data_.get();
+  }
+
   void BeginLoadIfNeeded() override;
   void SetDisplay(FontDisplay) override;
 
@@ -143,6 +149,8 @@
 
   // |nullptr| if font is not loaded or failed to decode.
   scoped_refptr<FontCustomPlatformData> custom_font_data_;
+  // |nullptr| if font is not loaded or failed to decode.
+  String url_;
 
   FontDisplay display_;
   Phase phase_;
diff --git a/third_party/blink/renderer/core/css/resolver/style_cascade.h b/third_party/blink/renderer/core/css/resolver/style_cascade.h
index 07fc53f..e9be592 100644
--- a/third_party/blink/renderer/core/css/resolver/style_cascade.h
+++ b/third_party/blink/renderer/core/css/resolver/style_cascade.h
@@ -114,14 +114,14 @@
                           CascadeOrigin,
                           CascadeResolver&);
 
- private:
-  friend class TestCascade;
-
   // The maximum number of tokens that may be produced by a var()
   // reference.
   //
   // https://drafts.csswg.org/css-variables/#long-variables
-  static const size_t kMaxSubstitutionTokens = 16384;
+  static const size_t kMaxSubstitutionTokens = 65536;
+
+ private:
+  friend class TestCascade;
 
   // Before we can Apply the cascade, the MatchResult and CascadeInterpolations
   // must be Analyzed. This means going through all the declarations, and
diff --git a/third_party/blink/renderer/core/css/resolver/style_cascade_test.cc b/third_party/blink/renderer/core/css/resolver/style_cascade_test.cc
index 550bdf24..14176e1 100644
--- a/third_party/blink/renderer/core/css/resolver/style_cascade_test.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_cascade_test.cc
@@ -3308,4 +3308,32 @@
   EXPECT_EQ(Color::kWhite, style->VisitedDependentColor(GetCSSPropertyColor()));
 }
 
+TEST_F(StyleCascadeTest, MaxSubstitutionTokens) {
+  StringBuilder builder;
+  for (size_t i = 0; i < StyleCascade::kMaxSubstitutionTokens; ++i)
+    builder.Append(':');  // <colon-token>
+
+  String at_limit = builder.ToString();
+  String above_limit = builder.ToString() + ":";
+
+  TestCascade cascade(GetDocument());
+  cascade.Add("--at-limit", at_limit);
+  cascade.Add("--above-limit", above_limit);
+  cascade.Add("--at-limit-reference", "var(--at-limit)");
+  cascade.Add("--above-limit-reference", "var(--above-limit)");
+  cascade.Add("--at-limit-reference-fallback",
+              "var(--unknown,var(--at-limit))");
+  cascade.Add("--above-limit-reference-fallback",
+              "var(--unknown,var(--above-limit))");
+  cascade.Apply();
+
+  EXPECT_EQ(at_limit, cascade.ComputedValue("--at-limit"));
+  EXPECT_EQ(above_limit, cascade.ComputedValue("--above-limit"));
+  EXPECT_EQ(at_limit, cascade.ComputedValue("--at-limit-reference"));
+  EXPECT_EQ(g_null_atom, cascade.ComputedValue("--above-limit-reference"));
+  EXPECT_EQ(at_limit, cascade.ComputedValue("--at-limit-reference-fallback"));
+  EXPECT_EQ(g_null_atom,
+            cascade.ComputedValue("--above-limit-reference-fallback"));
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index b4e80c6..b451687 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -2187,11 +2187,10 @@
   GetDocument().IncDOMTreeVersion();
 
   if (name == html_names::kIdAttr) {
-    AtomicString old_id = GetElementData()->IdForStyleResolution();
     AtomicString new_id = MakeIdForStyleResolution(
         params.new_value, GetDocument().InQuirksMode());
-    if (new_id != old_id) {
-      GetElementData()->SetIdForStyleResolution(new_id);
+    if (new_id != GetElementData()->IdForStyleResolution()) {
+      AtomicString old_id = GetElementData()->SetIdForStyleResolution(new_id);
       GetDocument().GetStyleEngine().IdChangedForElement(old_id, new_id, *this);
     }
   } else if (name == html_names::kClassAttr) {
diff --git a/third_party/blink/renderer/core/dom/element_data.h b/third_party/blink/renderer/core/dom/element_data.h
index 38a2d3cda..4e40808 100644
--- a/third_party/blink/renderer/core/dom/element_data.h
+++ b/third_party/blink/renderer/core/dom/element_data.h
@@ -64,8 +64,8 @@
   const AtomicString& IdForStyleResolution() const {
     return id_for_style_resolution_;
   }
-  void SetIdForStyleResolution(const AtomicString& new_id) const {
-    id_for_style_resolution_ = new_id;
+  AtomicString SetIdForStyleResolution(AtomicString new_id) const {
+    return std::exchange(id_for_style_resolution_, std::move(new_id));
   }
 
   const CSSPropertyValueSet* InlineStyle() const { return inline_style_.Get(); }
diff --git a/third_party/blink/renderer/core/editing/selection_modifier_test.cc b/third_party/blink/renderer/core/editing/selection_modifier_test.cc
index 01b4857..166d141 100644
--- a/third_party/blink/renderer/core/editing/selection_modifier_test.cc
+++ b/third_party/blink/renderer/core/editing/selection_modifier_test.cc
@@ -104,6 +104,18 @@
       GetSelectionTextFromBody(modifier.Selection().AsSelection()));
 }
 
+// For http://crbug.com/1104582
+TEST_F(SelectionModifierTest, PreviousSentenceWithNull) {
+  InsertStyleElement("b {display:inline-block}");
+  const SelectionInDOMTree selection =
+      SetSelectionTextToBody("<b><ruby><a>|</a></ruby></b>");
+  SelectionModifier modifier(GetFrame(), selection);
+  // We call |PreviousSentence()| with null-position.
+  EXPECT_FALSE(modifier.Modify(SelectionModifyAlteration::kMove,
+                               SelectionModifyDirection::kBackward,
+                               TextGranularity::kSentence));
+}
+
 // For http://crbug.com/1100971
 TEST_F(SelectionModifierTest, StartOfSentenceWithNull) {
   InsertStyleElement("b {display:inline-block}");
diff --git a/third_party/blink/renderer/core/editing/suggestion/text_suggestion_controller_test.cc b/third_party/blink/renderer/core/editing/suggestion/text_suggestion_controller_test.cc
index 6646cf0..724fe7c2 100644
--- a/third_party/blink/renderer/core/editing/suggestion/text_suggestion_controller_test.cc
+++ b/third_party/blink/renderer/core/editing/suggestion/text_suggestion_controller_test.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/core/editing/suggestion/text_suggestion_controller.h"
 
+#include "build/build_config.h"
 #include "third_party/blink/renderer/core/editing/ephemeral_range.h"
 #include "third_party/blink/renderer/core/editing/frame_selection.h"
 #include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h"
@@ -81,6 +82,12 @@
   EXPECT_EQ(10, selection.End().ComputeOffsetInContainerNode());
 }
 
+// Flaky on Android: http://crbug.com/1104700
+#if defined(OS_ANDROID)
+#define MAYBE_ApplyTextSuggestion DISABLED_ApplyTextSuggestion
+#else
+#define MAYBE_ApplyTextSuggestion ApplyTextSuggestion
+#endif
 TEST_F(TextSuggestionControllerTest, ApplyTextSuggestion) {
   SetBodyContent(
       "<div contenteditable>"
diff --git a/third_party/blink/renderer/core/editing/visible_units_sentence.cc b/third_party/blink/renderer/core/editing/visible_units_sentence.cc
index 18ad1aa0..8a328f5 100644
--- a/third_party/blink/renderer/core/editing/visible_units_sentence.cc
+++ b/third_party/blink/renderer/core/editing/visible_units_sentence.cc
@@ -270,6 +270,8 @@
 
 PositionInFlatTree PreviousSentencePosition(
     const PositionInFlatTree& position) {
+  if (position.IsNull())
+    return position;
   const PositionInFlatTree result = PreviousSentencePositionInternal(position);
   return AdjustBackwardPositionToAvoidCrossingEditingBoundaries(
              PositionInFlatTreeWithAffinity(result), position)
diff --git a/third_party/blink/renderer/core/execution_context/execution_context.cc b/third_party/blink/renderer/core/execution_context/execution_context.cc
index 39b56978..7a0cb6c2 100644
--- a/third_party/blink/renderer/core/execution_context/execution_context.cc
+++ b/third_party/blink/renderer/core/execution_context/execution_context.cc
@@ -68,7 +68,8 @@
       is_context_destroyed_(false),
       csp_delegate_(MakeGarbageCollected<ExecutionContextCSPDelegate>(*this)),
       window_interaction_tokens_(0),
-      referrer_policy_(network::mojom::ReferrerPolicy::kDefault) {
+      referrer_policy_(network::mojom::ReferrerPolicy::kDefault),
+      address_space_(network::mojom::blink::IPAddressSpace::kUnknown) {
   DCHECK(agent_);
 }
 
diff --git a/third_party/blink/renderer/core/execution_context/execution_context.h b/third_party/blink/renderer/core/execution_context/execution_context.h
index 51317f1..467f1a65 100644
--- a/third_party/blink/renderer/core/execution_context/execution_context.h
+++ b/third_party/blink/renderer/core/execution_context/execution_context.h
@@ -31,13 +31,9 @@
 #include <bitset>
 #include <memory>
 
-#include "base/location.h"
 #include "base/macros.h"
-#include "base/optional.h"
-#include "base/unguessable_token.h"
-#include "services/metrics/public/cpp/ukm_recorder.h"
 #include "services/metrics/public/cpp/ukm_source_id.h"
-#include "services/network/public/mojom/ip_address_space.mojom-blink.h"
+#include "services/network/public/mojom/ip_address_space.mojom-blink-forward.h"
 #include "services/network/public/mojom/referrer_policy.mojom-blink-forward.h"
 #include "third_party/blink/public/mojom/devtools/inspector_issue.mojom-blink-forward.h"
 #include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom-blink-forward.h"
@@ -63,8 +59,13 @@
 
 namespace base {
 class SingleThreadTaskRunner;
+class UnguessableToken;
 }
 
+namespace ukm {
+class UkmRecorder;
+}  // namespace ukm
+
 namespace blink {
 
 class Agent;
@@ -416,8 +417,7 @@
 
   network::mojom::ReferrerPolicy referrer_policy_;
 
-  network::mojom::blink::IPAddressSpace address_space_ =
-      network::mojom::blink::IPAddressSpace::kUnknown;
+  network::mojom::blink::IPAddressSpace address_space_;
 
   // Tracks which feature policies have already been parsed, so as not to count
   // them multiple times.
diff --git a/third_party/blink/renderer/core/execution_context/security_context.cc b/third_party/blink/renderer/core/execution_context/security_context.cc
index a6c696e5..ebb066f 100644
--- a/third_party/blink/renderer/core/execution_context/security_context.cc
+++ b/third_party/blink/renderer/core/execution_context/security_context.cc
@@ -28,10 +28,12 @@
 
 #include "base/metrics/histogram_macros.h"
 #include "services/network/public/cpp/web_sandbox_flags.h"
+#include "third_party/blink/public/common/feature_policy/document_policy.h"
 #include "third_party/blink/public/common/feature_policy/document_policy_features.h"
 #include "third_party/blink/public/common/feature_policy/feature_policy.h"
 #include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom-blink.h"
 #include "third_party/blink/public/mojom/feature_policy/policy_value.mojom-blink.h"
+#include "third_party/blink/public/mojom/security_context/insecure_request_policy.mojom-blink.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/core/execution_context/security_context_init.h"
 #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
@@ -56,7 +58,11 @@
 }
 
 SecurityContext::SecurityContext(SecurityContextType context_type)
-    : context_type_for_asserts_(context_type) {}
+    : insecure_request_policy_(
+          mojom::blink::InsecureRequestPolicy::kLeaveInsecureRequestsAlone),
+      context_type_for_asserts_(context_type) {}
+
+SecurityContext::~SecurityContext() = default;
 
 void SecurityContext::Initialize(const SecurityContextInit& init) {
   if (security_origin_) {
@@ -75,10 +81,6 @@
   }
   security_origin_ = init.GetSecurityOrigin();
   secure_context_mode_ = init.GetSecureContextMode();
-  feature_policy_ = init.CreateFeaturePolicy();
-  report_only_feature_policy_ = init.CreateReportOnlyFeaturePolicy();
-  document_policy_ = init.CreateDocumentPolicy();
-  report_only_document_policy_ = init.CreateReportOnlyDocumentPolicy();
   origin_trial_context_ = init.GetOriginTrialContext();
 }
 
@@ -158,9 +160,19 @@
   feature_policy_ = std::move(feature_policy);
 }
 
-void SecurityContext::SetDocumentPolicyForTesting(
-    std::unique_ptr<DocumentPolicy> document_policy) {
-  document_policy_ = std::move(document_policy);
+void SecurityContext::SetReportOnlyFeaturePolicy(
+    std::unique_ptr<FeaturePolicy> feature_policy) {
+  report_only_feature_policy_ = std::move(feature_policy);
+}
+
+void SecurityContext::SetDocumentPolicy(
+    std::unique_ptr<DocumentPolicy> policy) {
+  document_policy_ = std::move(policy);
+}
+
+void SecurityContext::SetReportOnlyDocumentPolicy(
+    std::unique_ptr<DocumentPolicy> policy) {
+  report_only_document_policy_ = std::move(policy);
 }
 
 bool SecurityContext::IsFeatureEnabled(
diff --git a/third_party/blink/renderer/core/execution_context/security_context.h b/third_party/blink/renderer/core/execution_context/security_context.h
index dc03dfee..3413461 100644
--- a/third_party/blink/renderer/core/execution_context/security_context.h
+++ b/third_party/blink/renderer/core/execution_context/security_context.h
@@ -31,12 +31,11 @@
 
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
-#include "services/network/public/mojom/web_sandbox_flags.mojom-blink.h"
-#include "third_party/blink/public/common/feature_policy/document_policy.h"
+#include "services/network/public/mojom/web_sandbox_flags.mojom-blink-forward.h"
 #include "third_party/blink/public/mojom/feature_policy/document_policy_feature.mojom-blink-forward.h"
 #include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom-blink-forward.h"
 #include "third_party/blink/public/mojom/feature_policy/feature_policy_feature.mojom-blink-forward.h"
-#include "third_party/blink/public/mojom/security_context/insecure_request_policy.mojom-blink.h"
+#include "third_party/blink/public/mojom/security_context/insecure_request_policy.mojom-blink-forward.h"
 #include "third_party/blink/public/platform/web_vector.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
@@ -47,6 +46,7 @@
 namespace blink {
 
 class ContentSecurityPolicy;
+class DocumentPolicy;
 class FeaturePolicy;
 class PolicyValue;
 class OriginTrialContext;
@@ -78,7 +78,7 @@
   enum SecurityContextType { kWindow, kWorker, kRemoteFrame };
 
   explicit SecurityContext(SecurityContextType context_type);
-  virtual ~SecurityContext() = default;
+  virtual ~SecurityContext();
 
   void Initialize(const SecurityContextInit&);
 
@@ -142,18 +142,18 @@
   const FeaturePolicy* GetFeaturePolicy() const {
     return feature_policy_.get();
   }
-  void SetFeaturePolicy(std::unique_ptr<FeaturePolicy> feature_policy);
+  void SetFeaturePolicy(std::unique_ptr<FeaturePolicy>);
+  void SetReportOnlyFeaturePolicy(std::unique_ptr<FeaturePolicy>);
 
   const DocumentPolicy* GetDocumentPolicy() const {
     return document_policy_.get();
   }
+  void SetDocumentPolicy(std::unique_ptr<DocumentPolicy> policy);
 
   const DocumentPolicy* GetReportOnlyDocumentPolicy() const {
     return report_only_document_policy_.get();
   }
-
-  void SetDocumentPolicyForTesting(
-      std::unique_ptr<DocumentPolicy> document_policy);
+  void SetReportOnlyDocumentPolicy(std::unique_ptr<DocumentPolicy> policy);
 
   // Tests whether the policy-controlled feature is enabled in this frame.
   // Use ExecutionContext::IsFeatureEnabled if a failure should be reported.
@@ -194,8 +194,7 @@
 
  private:
   Member<ContentSecurityPolicy> content_security_policy_;
-  mojom::blink::InsecureRequestPolicy insecure_request_policy_ =
-      mojom::blink::InsecureRequestPolicy::kLeaveInsecureRequestsAlone;
+  mojom::blink::InsecureRequestPolicy insecure_request_policy_;
   InsecureNavigationsSet insecure_navigations_to_upgrade_;
   bool require_safe_types_ = false;
   const SecurityContextType context_type_for_asserts_;
diff --git a/third_party/blink/renderer/core/execution_context/security_context_init.cc b/third_party/blink/renderer/core/execution_context/security_context_init.cc
index 910c56e..8f7d951c 100644
--- a/third_party/blink/renderer/core/execution_context/security_context_init.cc
+++ b/third_party/blink/renderer/core/execution_context/security_context_init.cc
@@ -106,8 +106,8 @@
   return origin_trials_->IsFeatureEnabled(feature);
 }
 
-void SecurityContextInit::CalculateDocumentPolicy(
-    const DocumentPolicy::ParsedDocumentPolicy& document_policy,
+void SecurityContextInit::ApplyDocumentPolicy(
+    DocumentPolicy::ParsedDocumentPolicy& document_policy,
     const String& report_only_document_policy_header) {
   DCHECK(origin_trials_);
   if (!RuntimeEnabledFeatures::DocumentPolicyEnabled(this))
@@ -117,14 +117,16 @@
   // when origin trial context is not initialized yet.
   // Needs to filter out features that are not in origin trial after
   // we have origin trial information available.
-  document_policy_ = FilterByOriginTrial(document_policy, this);
-  if (!document_policy_.feature_state.empty()) {
+  document_policy = FilterByOriginTrial(document_policy, this);
+  if (!document_policy.feature_state.empty()) {
     UseCounter::Count(execution_context_, WebFeature::kDocumentPolicyHeader);
-    for (const auto& policy_entry : document_policy_.feature_state) {
+    for (const auto& policy_entry : document_policy.feature_state) {
       UMA_HISTOGRAM_ENUMERATION("Blink.UseCounter.DocumentPolicy.Header",
                                 policy_entry.first);
     }
   }
+  execution_context_->GetSecurityContext().SetDocumentPolicy(
+      DocumentPolicy::CreateWithHeaderPolicy(document_policy));
 
   // Handle Report-Only-Document-Policy HTTP header.
   // Console messages generated from logger are discarded, because currently
@@ -133,35 +135,38 @@
   // |SecurityContextInit::ApplyPendingDataToDocument| will have no effect,
   // because when the function is called, the document is not fully initialized
   // yet (|document_| field in current frame is not yet initialized yet).
+  DocumentPolicy::ParsedDocumentPolicy report_only_document_policy;
   PolicyParserMessageBuffer logger("%s", /* discard_message */ true);
   base::Optional<DocumentPolicy::ParsedDocumentPolicy>
       report_only_parsed_policy = DocumentPolicyParser::Parse(
           report_only_document_policy_header, logger);
   if (report_only_parsed_policy) {
-    report_only_document_policy_ =
+    report_only_document_policy =
         FilterByOriginTrial(*report_only_parsed_policy, this);
-    if (!report_only_document_policy_.feature_state.empty()) {
+    if (!report_only_document_policy.feature_state.empty()) {
       UseCounter::Count(execution_context_,
                         WebFeature::kDocumentPolicyReportOnlyHeader);
+      execution_context_->GetSecurityContext().SetReportOnlyDocumentPolicy(
+          DocumentPolicy::CreateWithHeaderPolicy(report_only_document_policy));
     }
   }
 }
 
-void SecurityContextInit::CalculateFeaturePolicy(
+void SecurityContextInit::ApplyFeaturePolicy(
     LocalFrame* frame,
     const ResourceResponse& response,
     const base::Optional<WebOriginPolicy>& origin_policy,
     const FramePolicy& frame_policy) {
   DCHECK(origin_trials_);
-  initialized_feature_policy_state_ = true;
+
   // If we are a HTMLViewSourceDocument we use container, header or
   // inherited policies. https://crbug.com/898688.
-  if (frame && frame->InViewSourceMode())
+  if (frame->InViewSourceMode()) {
+    execution_context_->GetSecurityContext().SetFeaturePolicy(
+        FeaturePolicy::CreateFromParentPolicy(nullptr, {},
+                                              security_origin_->ToUrlOrigin()));
     return;
-
-  // For a main frame, get inherited feature policy from the opener if any.
-  if (frame && frame->IsMainFrame() && !frame->OpenerFeatureState().empty())
-    frame_for_opener_feature_state_ = frame;
+  }
 
   const String& permissions_policy_header =
       RuntimeEnabledFeatures::PermissionsPolicyHeaderEnabled()
@@ -177,11 +182,11 @@
   PolicyParserMessageBuffer report_only_feature_policy_logger(
       "Error with Report-Only-Feature-Policy header: ");
 
-  WTF::StringBuilder feature_policy;
-  feature_policy.Append(response.HttpHeaderField(http_names::kFeaturePolicy));
+  WTF::StringBuilder policy_builder;
+  policy_builder.Append(response.HttpHeaderField(http_names::kFeaturePolicy));
   if (origin_policy.has_value())
-    MergeFeaturesFromOriginPolicy(feature_policy, origin_policy.value());
-  String feature_policy_header = feature_policy.ToString();
+    MergeFeaturesFromOriginPolicy(policy_builder, origin_policy.value());
+  String feature_policy_header = policy_builder.ToString();
   if (!feature_policy_header.IsEmpty())
     UseCounter::Count(execution_context_, WebFeature::kFeaturePolicyHeader);
 
@@ -189,30 +194,26 @@
       feature_policy_header, permissions_policy_header, security_origin_,
       feature_policy_logger, this);
 
-  report_only_feature_policy_header_ = FeaturePolicyParser::ParseHeader(
-      response.HttpHeaderField(http_names::kFeaturePolicyReportOnly),
-      report_only_permissions_policy_header, security_origin_,
-      report_only_feature_policy_logger, this);
+  ParsedFeaturePolicy report_only_feature_policy_header =
+      FeaturePolicyParser::ParseHeader(
+          response.HttpHeaderField(http_names::kFeaturePolicyReportOnly),
+          report_only_permissions_policy_header, security_origin_,
+          report_only_feature_policy_logger, this);
 
-  if (!report_only_feature_policy_header_.empty()) {
+  if (!report_only_feature_policy_header.empty()) {
     UseCounter::Count(execution_context_,
                       WebFeature::kFeaturePolicyReportOnlyHeader);
   }
 
-  if (execution_context_) {
-    for (const auto& message : feature_policy_logger.GetMessages()) {
-      execution_context_->AddConsoleMessage(
-          MakeGarbageCollected<ConsoleMessage>(
-              mojom::blink::ConsoleMessageSource::kSecurity, message.level,
-              message.content));
-    }
-    for (const auto& message :
-         report_only_feature_policy_logger.GetMessages()) {
-      execution_context_->AddConsoleMessage(
-          MakeGarbageCollected<ConsoleMessage>(
-              mojom::blink::ConsoleMessageSource::kSecurity, message.level,
-              message.content));
-    }
+  for (const auto& message : feature_policy_logger.GetMessages()) {
+    execution_context_->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
+        mojom::blink::ConsoleMessageSource::kSecurity, message.level,
+        message.content));
+  }
+  for (const auto& message : report_only_feature_policy_logger.GetMessages()) {
+    execution_context_->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
+        mojom::blink::ConsoleMessageSource::kSecurity, message.level,
+        message.content));
   }
 
   // DocumentLoader applied the sandbox flags before calling this function, so
@@ -228,8 +229,9 @@
                                            feature_policy_header_);
   }
 
+  ParsedFeaturePolicy container_policy;
   if (frame && frame->Owner())
-    container_policy_ = frame_policy.container_policy;
+    container_policy = frame_policy.container_policy;
 
   // TODO(icelland): This is problematic querying sandbox flags before
   // feature policy is initialized.
@@ -241,22 +243,31 @@
     // https://crbug.com/954349).
     DisallowFeatureIfNotPresent(
         mojom::blink::FeaturePolicyFeature::kFocusWithoutUserActivation,
-        container_policy_);
+        container_policy);
   }
 
-  if (frame && !frame->IsMainFrame())
-    parent_frame_ = frame->Tree().Parent();
-}
-
-std::unique_ptr<FeaturePolicy>
-SecurityContextInit::CreateReportOnlyFeaturePolicy() const {
-  // For non-Document initialization, returns nullptr directly.
-  if (!initialized_feature_policy_state_)
-    return nullptr;
-
-  // If header not present, returns nullptr directly.
-  if (report_only_feature_policy_header_.empty())
-    return nullptr;
+  // Feature policy should either come from a parent in the case of an
+  // embedded child frame, or from an opener if any when a new window is
+  // created by an opener. A main frame without an opener would not have a
+  // parent policy nor an opener feature state.
+  // For a main frame, get inherited feature policy from the opener if any.
+  std::unique_ptr<FeaturePolicy> feature_policy;
+  if (!frame->IsMainFrame() || frame->OpenerFeatureState().empty() ||
+      !RuntimeEnabledFeatures::FeaturePolicyForSandboxEnabled()) {
+    auto* parent_feature_policy =
+        frame->Tree().Parent()
+            ? frame->Tree().Parent()->GetSecurityContext()->GetFeaturePolicy()
+            : nullptr;
+    feature_policy = FeaturePolicy::CreateFromParentPolicy(
+        parent_feature_policy, container_policy,
+        security_origin_->ToUrlOrigin());
+  } else {
+    feature_policy = FeaturePolicy::CreateWithOpenerPolicy(
+        frame->OpenerFeatureState(), security_origin_->ToUrlOrigin());
+  }
+  feature_policy->SetHeaderPolicy(feature_policy_header_);
+  execution_context_->GetSecurityContext().SetFeaturePolicy(
+      std::move(feature_policy));
 
   // Report-only feature policy only takes effect when it is stricter than
   // enforced feature policy, i.e. when enforced feature policy allows a feature
@@ -266,56 +277,15 @@
   // is no need to inherit parent policy and container policy for report-only
   // feature policy. For inherited policies, the behavior is dominated by
   // enforced feature policy.
-  DCHECK(security_origin_);
-  std::unique_ptr<FeaturePolicy> report_only_policy =
-      FeaturePolicy::CreateFromParentPolicy(nullptr /* parent_policy */,
-                                            {} /* container_policy */,
-                                            security_origin_->ToUrlOrigin());
-  report_only_policy->SetHeaderPolicy(report_only_feature_policy_header_);
-  return report_only_policy;
-}
-
-std::unique_ptr<FeaturePolicy> SecurityContextInit::CreateFeaturePolicy()
-    const {
-  // For non-Document initialization, returns nullptr directly.
-  if (!initialized_feature_policy_state_)
-    return nullptr;
-
-  // Feature policy should either come from a parent in the case of an
-  // embedded child frame, or from an opener if any when a new window is
-  // created by an opener. A main frame without an opener would not have a
-  // parent policy nor an opener feature state.
-  DCHECK(!parent_frame_ || !frame_for_opener_feature_state_);
-  std::unique_ptr<FeaturePolicy> feature_policy;
-  if (!frame_for_opener_feature_state_ ||
-      !RuntimeEnabledFeatures::FeaturePolicyForSandboxEnabled()) {
-    auto* parent_feature_policy =
-        parent_frame_ ? parent_frame_->GetSecurityContext()->GetFeaturePolicy()
-                      : nullptr;
-    feature_policy = FeaturePolicy::CreateFromParentPolicy(
-        parent_feature_policy, container_policy_,
-        security_origin_->ToUrlOrigin());
-  } else {
-    DCHECK(!parent_frame_);
-    feature_policy = FeaturePolicy::CreateWithOpenerPolicy(
-        frame_for_opener_feature_state_->OpenerFeatureState(),
-        security_origin_->ToUrlOrigin());
+  if (!report_only_feature_policy_header.empty()) {
+    std::unique_ptr<FeaturePolicy> report_only_policy =
+        FeaturePolicy::CreateFromParentPolicy(nullptr /* parent_policy */,
+                                              {} /* container_policy */,
+                                              security_origin_->ToUrlOrigin());
+    report_only_policy->SetHeaderPolicy(report_only_feature_policy_header);
+    execution_context_->GetSecurityContext().SetReportOnlyFeaturePolicy(
+        std::move(report_only_policy));
   }
-  feature_policy->SetHeaderPolicy(feature_policy_header_);
-  return feature_policy;
-}
-
-std::unique_ptr<DocumentPolicy> SecurityContextInit::CreateDocumentPolicy()
-    const {
-  return DocumentPolicy::CreateWithHeaderPolicy(document_policy_);
-}
-
-std::unique_ptr<DocumentPolicy>
-SecurityContextInit::CreateReportOnlyDocumentPolicy() const {
-  return report_only_document_policy_.feature_state.empty()
-             ? nullptr
-             : DocumentPolicy::CreateWithHeaderPolicy(
-                   report_only_document_policy_);
 }
 
 void SecurityContextInit::CalculateSecureContextMode(LocalFrame* frame) {
diff --git a/third_party/blink/renderer/core/execution_context/security_context_init.h b/third_party/blink/renderer/core/execution_context/security_context_init.h
index bb248f33..6e261669 100644
--- a/third_party/blink/renderer/core/execution_context/security_context_init.h
+++ b/third_party/blink/renderer/core/execution_context/security_context_init.h
@@ -19,8 +19,6 @@
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace blink {
-class Document;
-class Frame;
 class LocalFrame;
 class OriginTrialContext;
 class ResourceResponse;
@@ -38,31 +36,18 @@
 
   void CalculateSecureContextMode(LocalFrame* frame);
   void InitializeOriginTrials(const String& origin_trials_header);
-  void CalculateFeaturePolicy(
-      LocalFrame* frame,
-      const ResourceResponse& response,
-      const base::Optional<WebOriginPolicy>& origin_policy,
-      const FramePolicy& frame_policy);
-  void CalculateDocumentPolicy(
-      const DocumentPolicy::ParsedDocumentPolicy& document_policy,
+  void ApplyFeaturePolicy(LocalFrame* frame,
+                          const ResourceResponse& response,
+                          const base::Optional<WebOriginPolicy>& origin_policy,
+                          const FramePolicy& frame_policy);
+  void ApplyDocumentPolicy(
+      DocumentPolicy::ParsedDocumentPolicy& document_policy,
       const String& report_only_document_policy_header);
 
   const scoped_refptr<SecurityOrigin>& GetSecurityOrigin() const {
     return security_origin_;
   }
 
-  // Returns nullptr if SecurityContext is used for non-Document contexts(i.e.,
-  // workers and tests).
-  std::unique_ptr<FeaturePolicy> CreateFeaturePolicy() const;
-  // Returns nullptr if SecurityContext is used for non-Document contexts(i.e.,
-  // workers and tests).
-  // Returns nullptr if there is no 'Feature-Policy-Report-Only' header present
-  // in http response.
-  std::unique_ptr<FeaturePolicy> CreateReportOnlyFeaturePolicy() const;
-
-  std::unique_ptr<DocumentPolicy> CreateDocumentPolicy() const;
-  std::unique_ptr<DocumentPolicy> CreateReportOnlyDocumentPolicy() const;
-
   const ParsedFeaturePolicy& FeaturePolicyHeader() const {
     return feature_policy_header_;
   }
@@ -82,14 +67,7 @@
  private:
   ExecutionContext* execution_context_ = nullptr;
   scoped_refptr<SecurityOrigin> security_origin_;
-  DocumentPolicy::ParsedDocumentPolicy document_policy_;
-  DocumentPolicy::ParsedDocumentPolicy report_only_document_policy_;
-  bool initialized_feature_policy_state_ = false;
   ParsedFeaturePolicy feature_policy_header_;
-  ParsedFeaturePolicy report_only_feature_policy_header_;
-  LocalFrame* frame_for_opener_feature_state_ = nullptr;
-  Frame* parent_frame_ = nullptr;
-  ParsedFeaturePolicy container_policy_;
   OriginTrialContext* origin_trials_ = nullptr;
   base::Optional<SecureContextMode> secure_context_mode_;
 };
diff --git a/third_party/blink/renderer/core/feature_policy/layout_animations_policy.cc b/third_party/blink/renderer/core/feature_policy/layout_animations_policy.cc
index 72252d6d..92ba10e 100644
--- a/third_party/blink/renderer/core/feature_policy/layout_animations_policy.cc
+++ b/third_party/blink/renderer/core/feature_policy/layout_animations_policy.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/core/feature_policy/layout_animations_policy.h"
 
 #include "third_party/blink/public/common/feature_policy/document_policy_features.h"
+#include "third_party/blink/public/mojom/feature_policy/document_policy_feature.mojom-blink.h"
 #include "third_party/blink/renderer/core/css/properties/css_property.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
diff --git a/third_party/blink/renderer/core/frame/BUILD.gn b/third_party/blink/renderer/core/frame/BUILD.gn
index a1733ae..9be3456 100644
--- a/third_party/blink/renderer/core/frame/BUILD.gn
+++ b/third_party/blink/renderer/core/frame/BUILD.gn
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//printing/buildflags/buildflags.gni")
 import("//third_party/blink/renderer/core/core.gni")
 
 blink_core_sources("frame") {
@@ -229,4 +230,8 @@
   if (is_mac) {
     deps += [ "//ui/base/mojom" ]
   }
+
+  if (enable_basic_printing) {
+    deps += [ "//printing" ]
+  }
 }
diff --git a/third_party/blink/renderer/core/frame/DEPS b/third_party/blink/renderer/core/frame/DEPS
index 2fd3b950..17cd6a7 100644
--- a/third_party/blink/renderer/core/frame/DEPS
+++ b/third_party/blink/renderer/core/frame/DEPS
@@ -20,6 +20,8 @@
   ],
   "remote_frame_view.cc": [
     "+components/paint_preview/common/paint_preview_tracker.h",
+    "+printing/buildflags/buildflags.h",
+    "+printing/metafile_skia.h",
   ],
   "visual_viewport.cc": [
     "+cc/layers/solid_color_scrollbar_layer.h",
diff --git a/third_party/blink/renderer/core/frame/remote_frame_client.h b/third_party/blink/renderer/core/frame/remote_frame_client.h
index 4addd8d..f3e0054 100644
--- a/third_party/blink/renderer/core/frame/remote_frame_client.h
+++ b/third_party/blink/renderer/core/frame/remote_frame_client.h
@@ -15,10 +15,6 @@
 #include "third_party/blink/renderer/core/frame/frame_types.h"
 #include "third_party/blink/renderer/platform/graphics/touch_action.h"
 
-namespace cc {
-class PaintCanvas;
-}
-
 namespace blink {
 class AssociatedInterfaceProvider;
 class IntRect;
@@ -49,8 +45,6 @@
   virtual void UpdateRemoteViewportIntersection(
       const ViewportIntersectionState& intersection_state) = 0;
 
-  virtual uint32_t Print(const IntRect&, cc::PaintCanvas*) const = 0;
-
   virtual AssociatedInterfaceProvider* GetRemoteAssociatedInterfaces() = 0;
 };
 
diff --git a/third_party/blink/renderer/core/frame/remote_frame_client_impl.cc b/third_party/blink/renderer/core/frame/remote_frame_client_impl.cc
index f989b4b..838ed77 100644
--- a/third_party/blink/renderer/core/frame/remote_frame_client_impl.cc
+++ b/third_party/blink/renderer/core/frame/remote_frame_client_impl.cc
@@ -140,11 +140,6 @@
   web_frame_->Client()->UpdateRemoteViewportIntersection(intersection_state);
 }
 
-uint32_t RemoteFrameClientImpl::Print(const IntRect& rect,
-                                      cc::PaintCanvas* canvas) const {
-  return web_frame_->Client()->Print(rect, canvas);
-}
-
 AssociatedInterfaceProvider*
 RemoteFrameClientImpl::GetRemoteAssociatedInterfaces() {
   return web_frame_->Client()->GetRemoteAssociatedInterfaces();
diff --git a/third_party/blink/renderer/core/frame/remote_frame_client_impl.h b/third_party/blink/renderer/core/frame/remote_frame_client_impl.h
index c3c2837..323651e 100644
--- a/third_party/blink/renderer/core/frame/remote_frame_client_impl.h
+++ b/third_party/blink/renderer/core/frame/remote_frame_client_impl.h
@@ -8,10 +8,6 @@
 #include "third_party/blink/public/mojom/input/focus_type.mojom-blink-forward.h"
 #include "third_party/blink/renderer/core/frame/remote_frame_client.h"
 
-namespace cc {
-class PaintCanvas;
-}
-
 namespace blink {
 class WebRemoteFrameImpl;
 
@@ -45,7 +41,6 @@
                          const IntRect& screen_space_rect) override;
   void UpdateRemoteViewportIntersection(
       const ViewportIntersectionState& intersection_state) override;
-  uint32_t Print(const IntRect&, cc::PaintCanvas*) const override;
   AssociatedInterfaceProvider* GetRemoteAssociatedInterfaces() override;
 
   WebRemoteFrameImpl* GetWebFrame() const { return web_frame_; }
diff --git a/third_party/blink/renderer/core/frame/remote_frame_view.cc b/third_party/blink/renderer/core/frame/remote_frame_view.cc
index 6074657..1f1cbd1 100644
--- a/third_party/blink/renderer/core/frame/remote_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/remote_frame_view.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/core/frame/remote_frame_view.h"
 
 #include "components/paint_preview/common/paint_preview_tracker.h"
+#include "printing/buildflags/buildflags.h"
 #include "third_party/blink/public/mojom/frame/frame_owner_element_type.mojom-blink.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
@@ -21,6 +22,12 @@
 #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
 #include "third_party/blink/renderer/platform/graphics/paint/foreign_layer_display_item.h"
 
+#if BUILDFLAG(ENABLE_PRINTING)
+// nogncheck because dependency on //printing is conditional upon
+// enable_basic_printing flags.
+#include "printing/metafile_skia.h"  // nogncheck
+#endif
+
 namespace blink {
 
 RemoteFrameView::RemoteFrameView(RemoteFrame* remote_frame)
@@ -297,7 +304,25 @@
 
 uint32_t RemoteFrameView::Print(const IntRect& rect,
                                 cc::PaintCanvas* canvas) const {
-  return remote_frame_->Client()->Print(rect, canvas);
+#if BUILDFLAG(ENABLE_PRINTING)
+  auto* metafile = canvas->GetPrintingMetafile();
+  DCHECK(metafile);
+
+  // Create a place holder content for the remote frame so it can be replaced
+  // with actual content later.
+  // TODO(crbug.com/1093929): Consider to use an embedding token which
+  // represents the state of the remote frame. See also comments on
+  // https://crrev.com/c/2245430/.
+  uint32_t content_id = metafile->CreateContentForRemoteFrame(
+      rect, remote_frame_->GetFrameToken());
+
+  // Inform browser to print the remote subframe.
+  remote_frame_->GetRemoteFrameHostRemote().PrintCrossProcessSubframe(
+      rect, metafile->GetDocumentCookie());
+  return content_id;
+#else
+  return 0;
+#endif
 }
 
 uint32_t RemoteFrameView::CapturePaintPreview(const IntRect& rect,
diff --git a/third_party/blink/renderer/core/html/html_frame_owner_element.cc b/third_party/blink/renderer/core/html/html_frame_owner_element.cc
index a7919315..c908f97f9 100644
--- a/third_party/blink/renderer/core/html/html_frame_owner_element.cc
+++ b/third_party/blink/renderer/core/html/html_frame_owner_element.cc
@@ -615,8 +615,9 @@
     // requests, just as unsetting the loading attribute does if automatic lazy
     // loading is disabled.
     if (loading == LoadingAttributeValue::kEager ||
-        !ShouldLazilyLoadFrame(GetDocument(),
-                               loading == LoadingAttributeValue::kLazy)) {
+        (GetDocument().GetSettings() &&
+         !ShouldLazilyLoadFrame(GetDocument(),
+                                loading == LoadingAttributeValue::kLazy))) {
       should_lazy_load_children_ = false;
       if (lazy_load_frame_observer_ &&
           lazy_load_frame_observer_->IsLazyLoadPending()) {
diff --git a/third_party/blink/renderer/core/html/media/media_element_parser_helpers.cc b/third_party/blink/renderer/core/html/media/media_element_parser_helpers.cc
index b358da2..8f567b8 100644
--- a/third_party/blink/renderer/core/html/media/media_element_parser_helpers.cc
+++ b/third_party/blink/renderer/core/html/media/media_element_parser_helpers.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/core/html/media/media_element_parser_helpers.h"
 
+#include "third_party/blink/public/mojom/feature_policy/document_policy_feature.mojom-blink.h"
 #include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom-blink.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/layout/layout_object.h"
diff --git a/third_party/blink/renderer/core/inspector/inspector_css_agent.cc b/third_party/blink/renderer/core/inspector/inspector_css_agent.cc
index 46ef824..5bba8dc 100644
--- a/third_party/blink/renderer/core/inspector/inspector_css_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_css_agent.cc
@@ -30,6 +30,9 @@
 #include "third_party/blink/renderer/core/css/css_color_value.h"
 #include "third_party/blink/renderer/core/css/css_computed_style_declaration.h"
 #include "third_party/blink/renderer/core/css/css_default_style_sheets.h"
+#include "third_party/blink/renderer/core/css/css_font_face.h"
+#include "third_party/blink/renderer/core/css/css_font_face_source.h"
+#include "third_party/blink/renderer/core/css/css_font_selector.h"
 #include "third_party/blink/renderer/core/css/css_gradient_value.h"
 #include "third_party/blink/renderer/core/css/css_import_rule.h"
 #include "third_party/blink/renderer/core/css/css_keyframe_rule.h"
@@ -737,11 +740,33 @@
   instrumenting_agents_->AddInspectorCSSAgent(this);
   dom_agent_->SetDOMListener(this);
   HeapVector<Member<Document>> documents = dom_agent_->Documents();
-  for (Document* document : documents)
+  for (Document* document : documents) {
     UpdateActiveStyleSheets(document);
+    TriggerFontsUpdatedForDocument(document);
+  }
   enable_completed_ = true;
 }
 
+void InspectorCSSAgent::TriggerFontsUpdatedForDocument(Document* document) {
+  const HeapLinkedHashSet<Member<FontFace>>& faces =
+      document->GetStyleEngine()
+          .GetFontSelector()
+          ->GetFontFaceCache()
+          ->CssConnectedFontFaces();
+  for (FontFace* face : faces) {
+    CSSFontFace* css_face = face->CssFontFace();
+    if (!css_face)
+      continue;
+    const CSSFontFaceSource* source = css_face->FrontSource();
+    if (!source || !source->IsLoaded())
+      continue;
+    const FontCustomPlatformData* data = source->GetCustomPlaftormData();
+    if (!data)
+      continue;
+    FontsUpdated(face, source->GetURL(), data);
+  }
+}
+
 Response InspectorCSSAgent::disable() {
   Reset();
   dom_agent_->SetDOMListener(nullptr);
diff --git a/third_party/blink/renderer/core/inspector/inspector_css_agent.h b/third_party/blink/renderer/core/inspector/inspector_css_agent.h
index 07e07d3..6b844c2 100644
--- a/third_party/blink/renderer/core/inspector/inspector_css_agent.h
+++ b/third_party/blink/renderer/core/inspector/inspector_css_agent.h
@@ -256,6 +256,8 @@
   void ResetNonPersistentData();
   InspectorStyleSheetForInlineStyle* AsInspectorStyleSheet(Element* element);
 
+  void TriggerFontsUpdatedForDocument(Document*);
+
   void UpdateActiveStyleSheets(Document*);
   void SetActiveStyleSheets(Document*,
                             const HeapVector<Member<CSSStyleSheet>>&);
diff --git a/third_party/blink/renderer/core/inspector/inspector_page_agent.cc b/third_party/blink/renderer/core/inspector/inspector_page_agent.cc
index 55f708d..19bd9561 100644
--- a/third_party/blink/renderer/core/inspector/inspector_page_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_page_agent.cc
@@ -1086,6 +1086,13 @@
   }
   if (loader && !loader->UnreachableURL().IsEmpty())
     frame_object->setUnreachableUrl(loader->UnreachableURL().GetString());
+  if (frame->IsAdRoot()) {
+    frame_object->setAdFrameType(protocol::Page::AdFrameTypeEnum::Root);
+  } else if (frame->IsAdSubframe()) {
+    frame_object->setAdFrameType(protocol::Page::AdFrameTypeEnum::Child);
+  } else {
+    frame_object->setAdFrameType(protocol::Page::AdFrameTypeEnum::None);
+  }
   return frame_object;
 }
 
diff --git a/third_party/blink/renderer/core/layout/layout_block_flow.cc b/third_party/blink/renderer/core/layout/layout_block_flow.cc
index bb698ae..7ab32b4 100644
--- a/third_party/blink/renderer/core/layout/layout_block_flow.cc
+++ b/third_party/blink/renderer/core/layout/layout_block_flow.cc
@@ -4477,7 +4477,7 @@
 bool LayoutBlockFlow::RecalcInlineChildrenLayoutOverflow() {
   DCHECK(ChildrenInline());
   bool children_layout_overflow_changed = false;
-  LinkedHashSet<RootInlineBox*> line_boxes;
+  HashSet<RootInlineBox*> line_boxes;
   for (InlineWalker walker(LineLayoutBlockFlow(this)); !walker.AtEnd();
        walker.Advance()) {
     LayoutObject* layout_object = walker.Current().GetLayoutObject();
@@ -4497,9 +4497,7 @@
   // FIXME: Glyph overflow will get lost in this case, but not really a big
   // deal.
   GlyphOverflowAndFallbackFontsMap text_box_data_map;
-  for (LinkedHashSet<RootInlineBox*>::const_iterator it = line_boxes.begin();
-       it != line_boxes.end(); ++it) {
-    RootInlineBox* box = *it;
+  for (auto* box : line_boxes) {
     box->ClearKnownToHaveNoOverflow();
     box->ComputeOverflow(box->LineTop(), box->LineBottom(), text_box_data_map);
   }
diff --git a/third_party/blink/renderer/core/layout/layout_block_flow_line.cc b/third_party/blink/renderer/core/layout/layout_block_flow_line.cc
index 28a8f43..a475917 100644
--- a/third_party/blink/renderer/core/layout/layout_block_flow_line.cc
+++ b/third_party/blink/renderer/core/layout/layout_block_flow_line.cc
@@ -2445,7 +2445,7 @@
         outline_rects, PhysicalOffset(),
         o.OutlineRectsShouldIncludeBlockVisualOverflow());
     if (!outline_rects.IsEmpty()) {
-      PhysicalRect outline_bounds = UnionRectEvenIfEmpty(outline_rects);
+      PhysicalRect outline_bounds = UnionRect(outline_rects);
       outline_bounds.Inflate(LayoutUnit(o.StyleRef().OutlineOutsetExtent()));
       outline_bounds_of_all_continuations.Unite(outline_bounds);
     }
diff --git a/third_party/blink/renderer/core/layout/layout_box.cc b/third_party/blink/renderer/core/layout/layout_box.cc
index cd98599..160a215 100644
--- a/third_party/blink/renderer/core/layout/layout_box.cc
+++ b/third_party/blink/renderer/core/layout/layout_box.cc
@@ -497,8 +497,11 @@
       // The overflow clip paint property depends on border sizes through
       // overflowClipRect(), and border radii, so we update properties on
       // border size or radii change.
+      //
+      // For some controls, it depends on paddings.
       if (!old_style->BorderSizeEquals(new_style) ||
-          !old_style->RadiiEqual(new_style)) {
+          !old_style->RadiiEqual(new_style) ||
+          (HasControlClip() && !old_style->PaddingEqual(new_style))) {
         SetNeedsPaintPropertyUpdate();
         if (Layer())
           Layer()->SetNeedsCompositingInputsUpdate();
@@ -5917,7 +5920,7 @@
   if (style.HasOutline()) {
     Vector<PhysicalRect> outline_rects = OutlineRects(
         PhysicalOffset(), OutlineRectsShouldIncludeBlockVisualOverflow());
-    PhysicalRect rect = UnionRectEvenIfEmpty(outline_rects);
+    PhysicalRect rect = UnionRect(outline_rects);
     bool outline_affected = rect.size != PhysicalSizeToBeNoop(Size());
     SetOutlineMayBeAffectedByDescendants(outline_affected);
     rect.Inflate(LayoutUnit(style.OutlineOutsetExtent()));
diff --git a/third_party/blink/renderer/core/layout/layout_inline.cc b/third_party/blink/renderer/core/layout/layout_inline.cc
index 4080c34..4cb960b3 100644
--- a/third_party/blink/renderer/core/layout/layout_inline.cc
+++ b/third_party/blink/renderer/core/layout/layout_inline.cc
@@ -1431,7 +1431,7 @@
                       OutlineRectsShouldIncludeBlockVisualOverflow());
     }
     if (!rects.IsEmpty()) {
-      PhysicalRect outline_rect = UnionRectEvenIfEmpty(rects);
+      PhysicalRect outline_rect = UnionRect(rects);
       outline_rect.Inflate(outline_outset);
       overflow_rect.Unite(outline_rect);
     }
diff --git a/third_party/blink/renderer/core/layout/layout_theme_win.cc b/third_party/blink/renderer/core/layout/layout_theme_win.cc
index e447ce6d5..34b6592 100644
--- a/third_party/blink/renderer/core/layout/layout_theme_win.cc
+++ b/third_party/blink/renderer/core/layout/layout_theme_win.cc
@@ -8,6 +8,7 @@
 
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
+#include "third_party/blink/renderer/platform/web_test_support.h"
 
 namespace blink {
 
@@ -58,7 +59,8 @@
       return LayoutThemeDefault::SystemColor(css_value_id, color_scheme);
   }
 
-  if (Platform::Current() && Platform::Current()->ThemeEngine()) {
+  if (!WebTestSupport::IsRunningWebTest() && Platform::Current() &&
+      Platform::Current()->ThemeEngine()) {
     const base::Optional<SkColor> system_color =
         Platform::Current()->ThemeEngine()->GetSystemColor(theme_color);
     if (system_color)
diff --git a/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc b/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc
index e524864..f7103e2 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc
@@ -464,6 +464,24 @@
   return ConstrainByMinMax(extent, min, max);
 }
 
+MinMaxSizes ComputeMinMaxBlockSize(
+    const NGConstraintSpace& constraint_space,
+    const ComputedStyle& style,
+    const NGBoxStrut& border_padding,
+    LayoutUnit content_size,
+    const LayoutUnit* opt_percentage_resolution_block_size_for_min_max) {
+  MinMaxSizes result;
+  result.min_size = ResolveMinBlockLength(
+      constraint_space, style, border_padding, style.LogicalMinHeight(),
+      LengthResolvePhase::kLayout,
+      opt_percentage_resolution_block_size_for_min_max);
+  result.max_size = ResolveMaxBlockLength(
+      constraint_space, style, border_padding, style.LogicalMaxHeight(),
+      LengthResolvePhase::kLayout,
+      opt_percentage_resolution_block_size_for_min_max);
+  return result;
+}
+
 namespace {
 
 // Computes the block-size for a fragment, ignoring the fixed block-size if set.
@@ -475,9 +493,8 @@
     base::Optional<LayoutUnit> inline_size,
     const LayoutUnit* opt_percentage_resolution_block_size_for_min_max =
         nullptr) {
-  LayoutUnit min = ResolveMinBlockLength(
-      constraint_space, style, border_padding, style.LogicalMinHeight(),
-      LengthResolvePhase::kLayout,
+  MinMaxSizes min_max = ComputeMinMaxBlockSize(
+      constraint_space, style, border_padding, content_size,
       opt_percentage_resolution_block_size_for_min_max);
   const Length& logical_height = style.LogicalHeight();
   // Scrollable percentage-sized children of table cells, in the table
@@ -496,7 +513,7 @@
           NGTableCellChildLayoutMode::kMeasureRestricted &&
       (style.OverflowY() == EOverflow::kAuto ||
        style.OverflowY() == EOverflow::kScroll))
-    return min;
+    return min_max.min_size;
 
   // TODO(cbiesinger): Audit callers of ResolveMainBlockLength to see whether
   // they need to respect aspect ratio.
@@ -514,18 +531,13 @@
     if (style.LogicalMinHeight().IsAuto() &&
         style.OverflowBlockDirection() == EOverflow::kVisible &&
         content_size != kIndefiniteSize)
-      min = content_size;
+      min_max.min_size = content_size;
   } else if (extent == kIndefiniteSize) {
     DCHECK_EQ(content_size, kIndefiniteSize);
     return extent;
   }
 
-  LayoutUnit max = ResolveMaxBlockLength(
-      constraint_space, style, border_padding, style.LogicalMaxHeight(),
-      LengthResolvePhase::kLayout,
-      opt_percentage_resolution_block_size_for_min_max);
-
-  return ConstrainByMinMax(extent, min, max);
+  return min_max.ClampSizeToMinAndMax(extent);
 }
 
 }  // namespace
diff --git a/third_party/blink/renderer/core/layout/ng/ng_length_utils.h b/third_party/blink/renderer/core/layout/ng/ng_length_utils.h
index 5db4337..53a4f31 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_length_utils.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_length_utils.h
@@ -299,6 +299,15 @@
     NGLayoutInputNode child,
     const MinMaxSizesInput&);
 
+// Computes the min-block-size and max-block-size values for a node.
+MinMaxSizes ComputeMinMaxBlockSize(
+    const NGConstraintSpace&,
+    const ComputedStyle&,
+    const NGBoxStrut& border_padding,
+    LayoutUnit content_size,
+    const LayoutUnit* opt_percentage_resolution_block_size_for_min_max =
+        nullptr);
+
 // Tries to compute the inline size of a node from its block size and
 // aspect ratio. If there is no aspect ratio or the block size is indefinite,
 // returns kIndefiniteSize.
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
index b57f3abb..83fbbb98 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
@@ -575,7 +575,7 @@
   do {
     scoped_refptr<const NGLayoutResult> layout_result =
         Layout(node, candidate_constraint_space, candidate_static_position,
-               container_content_size, container_info,
+               container_content_size, container_info, writing_mode_,
                default_containing_block_.direction, only_layout);
 
     if (!freeze_scrollbars.has_value()) {
@@ -635,6 +635,8 @@
       GetContainingBlockInfo(descendant, containing_block_fragment);
   const TextDirection default_direction =
       containing_block_fragment->Style().Direction();
+  const WritingMode default_writing_mode =
+      containing_block_fragment->Style().GetWritingMode();
   const ComputedStyle& descendant_style = node.Style();
   const WritingMode descendant_writing_mode = descendant_style.GetWritingMode();
   const TextDirection descendant_direction = descendant_style.Direction();
@@ -642,7 +644,7 @@
   LogicalSize container_content_size =
       container_info.ContentSize(descendant_style.GetPosition());
   PhysicalSize container_physical_content_size =
-      ToPhysicalSize(container_content_size, writing_mode_);
+      ToPhysicalSize(container_content_size, default_writing_mode);
 
   // Adjust the |static_position| (which is currently relative to the default
   // container's border-box). ng_absolute_utils expects the static position to
@@ -652,22 +654,29 @@
 
   NGLogicalStaticPosition descendant_static_position =
       static_position
-          .ConvertToPhysical(writing_mode_, default_direction,
+          .ConvertToPhysical(default_writing_mode, default_direction,
                              container_physical_content_size)
           .ConvertToLogical(descendant_writing_mode, descendant_direction,
                             container_physical_content_size);
 
+  // TODO(almaher): The index should be based on the fragmentainer that we will
+  // start layout in (skipping any column spanner fragments) rather than 0 every
+  // time.
+  const NGConstraintSpace& fragmentainer_constraint_space =
+      GetFragmentainerConstraintSpace(/* index */ 0);
+
   // Need a constraint space to resolve offsets.
-  NGConstraintSpaceBuilder builder(writing_mode_, descendant_writing_mode,
-                                   /* is_new_fc */ true);
-  builder.SetTextDirection(descendant_direction);
-  builder.SetAvailableSize(container_content_size);
-  builder.SetPercentageResolutionSize(container_content_size);
-  NGConstraintSpace descendant_constraint_space = builder.ToConstraintSpace();
+  // TODO(almaher): The block offset should be based on the descendant rather
+  // than on the containing block.
+  NGConstraintSpace descendant_constraint_space =
+      CreateConstraintSpaceForFragmentainerDescendant(
+          node, container_content_size,
+          container_info.container_offset.block_offset,
+          fragmentainer_constraint_space, default_writing_mode);
 
   return Layout(node, descendant_constraint_space, descendant_static_position,
-                container_content_size, container_info, default_direction,
-                nullptr);
+                container_content_size, container_info, default_writing_mode,
+                default_direction, nullptr);
 }
 
 scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::Layout(
@@ -676,6 +685,7 @@
     const NGLogicalStaticPosition& candidate_static_position,
     LogicalSize container_content_size,
     const ContainingBlockInfo& container_info,
+    const WritingMode default_writing_mode,
     const TextDirection default_direction,
     const LayoutBox* only_layout) {
   const ComputedStyle& candidate_style = node.Style();
@@ -684,7 +694,7 @@
   const TextDirection container_direction = container_info.direction;
 
   PhysicalSize container_physical_content_size =
-      ToPhysicalSize(container_content_size, writing_mode_);
+      ToPhysicalSize(container_content_size, default_writing_mode);
   LogicalSize container_content_size_in_candidate_writing_mode =
       container_physical_content_size.ConvertToLogical(candidate_writing_mode);
   NGBoxStrut border_padding =
@@ -723,7 +733,7 @@
       ComputeOutOfFlowBlockDimensions(
           candidate_constraint_space, candidate_style, border_padding,
           candidate_static_position, base::nullopt, base::nullopt,
-          writing_mode_, container_direction, &node_dimensions);
+          default_writing_mode, container_direction, &node_dimensions);
       has_computed_block_dimensions = true;
       input.percentage_resolution_block_size = node_dimensions.size.block_size;
     }
@@ -756,10 +766,10 @@
                     kIndefiniteSize};
   }
 
-  ComputeOutOfFlowInlineDimensions(candidate_constraint_space, candidate_style,
-                                   border_padding, candidate_static_position,
-                                   min_max_sizes, replaced_size, writing_mode_,
-                                   container_direction, &node_dimensions);
+  ComputeOutOfFlowInlineDimensions(
+      candidate_constraint_space, candidate_style, border_padding,
+      candidate_static_position, min_max_sizes, replaced_size,
+      default_writing_mode, container_direction, &node_dimensions);
 
   // |should_be_considered_as_replaced| sets the inline-size.
   // It does not set the block-size. This is a compatibility quirk.
@@ -797,15 +807,15 @@
   if (!has_computed_block_dimensions) {
     ComputeOutOfFlowBlockDimensions(
         candidate_constraint_space, candidate_style, border_padding,
-        candidate_static_position, block_estimate, replaced_size, writing_mode_,
-        container_direction, &node_dimensions);
+        candidate_static_position, block_estimate, replaced_size,
+        default_writing_mode, container_direction, &node_dimensions);
   }
 
   // Calculate the offsets.
   NGBoxStrut inset =
       node_dimensions.inset
           .ConvertToPhysical(candidate_writing_mode, candidate_direction)
-          .ConvertToLogical(writing_mode_, default_direction);
+          .ConvertToLogical(default_writing_mode, default_direction);
 
   // |inset| is relative to the container's padding-box. Convert this to being
   // relative to the default container's border-box.
@@ -900,7 +910,7 @@
       *node.GetLayoutBox(), layout_result->PhysicalFragment().Size().height);
   if (y.has_value()) {
     DCHECK(!container_space_.HasBlockFragmentation());
-    if (IsHorizontalWritingMode(writing_mode_))
+    if (IsHorizontalWritingMode(default_writing_mode))
       offset.block_offset = *y;
     else
       offset.inline_offset = *y;
@@ -975,14 +985,7 @@
   const NGBlockNode& node = container_builder_->Node();
   const auto& fragment =
       To<NGPhysicalBoxFragment>(*fragmentainer.fragment.get());
-  const WritingMode container_writing_mode =
-      container_builder_->Style().GetWritingMode();
-  // TODO(bebeaudr): Need to handle different fragmentation types. It won't
-  // always be multi-column.
-  NGConstraintSpace space = CreateConstraintSpaceForColumns(
-      *container_builder_->ConstraintSpace(), container_writing_mode,
-      fragment.Size().ConvertToLogical(container_writing_mode),
-      /* is_first_fragmentainer */ index == 0, /* balance_columns */ false);
+  const NGConstraintSpace& space = GetFragmentainerConstraintSpace(index);
   NGFragmentGeometry fragment_geometry =
       CalculateInitialFragmentGeometry(space, node);
   NGLayoutAlgorithmParams params(node, fragment_geometry, space,
@@ -1001,4 +1004,55 @@
       index, algorithm.Layout()->PhysicalFragment(), fragmentainer.offset);
 }
 
+const NGConstraintSpace& NGOutOfFlowLayoutPart::GetFragmentainerConstraintSpace(
+    const wtf_size_t index) {
+  DCHECK_LT(index, container_builder_->Children().size());
+
+  // Increase the index by 1 to avoid a key of 0.
+  wtf_size_t stored_index = index + 1;
+
+  auto it = fragmentainer_constraint_space_map_.find(stored_index);
+  if (it != fragmentainer_constraint_space_map_.end())
+    return it->value;
+
+  // TODO(bebeaudr): Need to handle different fragmentation types. It won't
+  // always be multi-column.
+  const auto& fragmentainer = container_builder_->Children()[index];
+  DCHECK(fragmentainer.fragment->IsFragmentainerBox());
+  const auto& fragment =
+      To<NGPhysicalBoxFragment>(*fragmentainer.fragment.get());
+  const WritingMode container_writing_mode =
+      container_builder_->Style().GetWritingMode();
+  NGConstraintSpace fragmentainer_constraint_space =
+      CreateConstraintSpaceForColumns(
+          *container_builder_->ConstraintSpace(), container_writing_mode,
+          fragment.Size().ConvertToLogical(container_writing_mode),
+          /* is_first_fragmentainer */ index == 0, /* balance_columns */ false);
+
+  return fragmentainer_constraint_space_map_
+      .insert(stored_index, fragmentainer_constraint_space)
+      .stored_value->value;
+}
+
+NGConstraintSpace
+NGOutOfFlowLayoutPart::CreateConstraintSpaceForFragmentainerDescendant(
+    const NGBlockNode& descendant,
+    const LogicalSize& content_size,
+    const LayoutUnit block_offset,
+    const NGConstraintSpace& fragmentainer_constraint_space,
+    const WritingMode default_writing_mode) const {
+  const ComputedStyle& descendant_style = descendant.Style();
+  NGConstraintSpaceBuilder builder(default_writing_mode,
+                                   descendant_style.GetWritingMode(),
+                                   /* is_new_fc */ true);
+  builder.SetTextDirection(descendant_style.Direction());
+  builder.SetAvailableSize(content_size);
+  builder.SetPercentageResolutionSize(content_size);
+
+  SetupSpaceBuilderForFragmentation(fragmentainer_constraint_space, descendant,
+                                    block_offset, &builder,
+                                    /* is_new_fc */ true);
+  return builder.ToConstraintSpace();
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h
index f624a5ba..5651139 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h
@@ -10,6 +10,7 @@
 #include "base/optional.h"
 #include "third_party/blink/renderer/core/layout/geometry/physical_offset.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_absolute_utils.h"
+#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h"
 #include "third_party/blink/renderer/core/style/computed_style_base_constants.h"
 #include "third_party/blink/renderer/platform/wtf/hash_map.h"
 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
@@ -21,7 +22,6 @@
 class LayoutObject;
 class NGBlockNode;
 class NGBoxFragmentBuilder;
-class NGConstraintSpace;
 class NGLayoutResult;
 class NGPhysicalContainerFragment;
 struct NGLogicalOutOfFlowPositionedNode;
@@ -120,6 +120,7 @@
                                              const NGLogicalStaticPosition&,
                                              LogicalSize container_content_size,
                                              const ContainingBlockInfo&,
+                                             const WritingMode,
                                              const TextDirection,
                                              const LayoutBox* only_layout);
 
@@ -127,6 +128,14 @@
 
   void AddOOFResultToFragmentainer(scoped_refptr<const NGLayoutResult> result,
                                    const wtf_size_t index);
+  NGConstraintSpace CreateConstraintSpaceForFragmentainerDescendant(
+      const NGBlockNode& descendant,
+      const LogicalSize& content_size,
+      const LayoutUnit block_offset,
+      const NGConstraintSpace& fragmentainer_constraint_space,
+      const WritingMode default_writing_mode) const;
+  const NGConstraintSpace& GetFragmentainerConstraintSpace(
+      const wtf_size_t index);
   scoped_refptr<const NGLayoutResult> GenerateFragment(
       NGBlockNode node,
       const LogicalSize& container_content_size_in_child_writing_mode,
@@ -137,6 +146,7 @@
   NGBoxFragmentBuilder* container_builder_;
   ContainingBlockInfo default_containing_block_;
   HashMap<const LayoutObject*, ContainingBlockInfo> containing_blocks_map_;
+  HashMap<wtf_size_t, NGConstraintSpace> fragmentainer_constraint_space_map_;
   const WritingMode writing_mode_;
   bool is_absolute_container_;
   bool is_fixed_container_;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc
index 452c4e3..d01c51d 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc
@@ -178,14 +178,6 @@
   return builder.ToBoxFragment();
 }
 
-bool NGPhysicalBoxFragment::HasSelfPaintingLayer() const {
-  if (!IsCSSBox())
-    return false;
-  SECURITY_DCHECK(GetLayoutObject() && GetLayoutObject()->IsBoxModelObject());
-  return (static_cast<const LayoutBoxModelObject*>(GetLayoutObject()))
-      ->HasSelfPaintingLayer();
-}
-
 PhysicalRect NGPhysicalBoxFragment::OverflowClipRect(
     const PhysicalOffset& location,
     OverlayScrollbarClipBehavior overlay_scrollbar_clip_behavior) const {
@@ -427,7 +419,7 @@
           PhysicalOffset(),
           GetLayoutObject()->OutlineRectsShouldIncludeBlockVisualOverflow(),
           &outline_rects);
-      PhysicalRect rect = UnionRectEvenIfEmpty(outline_rects);
+      PhysicalRect rect = UnionRect(outline_rects);
       rect.Inflate(LayoutUnit(style.OutlineOutsetExtent()));
       ink_overflow.Unite(rect);
     }
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h
index 2af7dfe..312cec5 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h
@@ -98,8 +98,6 @@
     return ComputePaddingAddress()->SnapToDevicePixels();
   }
 
-  bool HasSelfPaintingLayer() const;
-
   // Return true if this is either a container that establishes an inline
   // formatting context, or if it's non-atomic inline content participating in
   // one. Empty blocks don't establish an inline formatting context.
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc b/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc
index bc4b4e35..e31ff7a 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc
@@ -325,25 +325,6 @@
   }
 }
 
-PaintLayer* NGPhysicalFragment::Layer() const {
-  if (!HasLayer())
-    return nullptr;
-
-  // If the underlying LayoutObject has a layer it's guaranteed to be a
-  // LayoutBoxModelObject.
-  return static_cast<LayoutBoxModelObject*>(layout_object_)->Layer();
-}
-
-bool NGPhysicalFragment::HasSelfPaintingLayer() const {
-  if (!HasLayer())
-    return false;
-
-  // If the underlying LayoutObject has a layer it's guaranteed to be a
-  // LayoutBoxModelObject.
-  return static_cast<LayoutBoxModelObject*>(layout_object_)
-      ->HasSelfPaintingLayer();
-}
-
 bool NGPhysicalFragment::IsBlockFlow() const {
   return !IsLineBox() && layout_object_->IsLayoutBlockFlow();
 }
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h b/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h
index fc8de7b..b569ed2 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h
@@ -238,10 +238,17 @@
   bool HasLayer() const { return IsCSSBox() && layout_object_->HasLayer(); }
 
   // The PaintLayer associated with the fragment.
-  PaintLayer* Layer() const;
+  PaintLayer* Layer() const {
+    if (!HasLayer())
+      return nullptr;
+    return To<LayoutBoxModelObject>(layout_object_)->Layer();
+  }
 
   // Whether this object has a self-painting |Layer()|.
-  bool HasSelfPaintingLayer() const;
+  bool HasSelfPaintingLayer() const {
+    return HasLayer() &&
+           To<LayoutBoxModelObject>(layout_object_)->HasSelfPaintingLayer();
+  }
 
   // True if overflow != 'visible', except for certain boxes that do not allow
   // overflow clip; i.e., AllowOverflowClip() returns false.
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc
index 03d3945..fb35d8c8 100644
--- a/third_party/blink/renderer/core/loader/document_loader.cc
+++ b/third_party/blink/renderer/core/loader/document_loader.cc
@@ -1268,11 +1268,8 @@
   if (!frame_->IsMainFrame() && response_.HasMajorCertificateErrors()) {
     MixedContentChecker::HandleCertificateError(
         response_, mojom::RequestContextType::HYPERLINK,
-        GetFrame()->GetSettings()
-            ? GetFrame()
-                  ->GetSettings()
-                  ->GetStrictMixedContentCheckingForPlugin()
-            : false,
+        MixedContentChecker::DecideCheckModeForPlugin(
+            GetFrame()->GetSettings()),
         GetContentSecurityNotifier());
   }
   GetFrameLoader().Progress().IncrementProgress(main_resource_identifier_,
@@ -1706,9 +1703,12 @@
   security_init.CalculateSecureContextMode(frame_.Get());
   security_init.InitializeOriginTrials(
       response_.HttpHeaderField(http_names::kOriginTrial));
+
+  frame_->DomWindow()->Initialize(security_init);
+
   // TODO(iclelland): Add Feature-Policy-Report-Only to Origin Policy.
-  security_init.CalculateFeaturePolicy(frame_.Get(), response_, origin_policy_,
-                                       frame_policy_);
+  security_init.ApplyFeaturePolicy(frame_.Get(), response_, origin_policy_,
+                                   frame_policy_);
   // |document_policy_| is parsed in document loader because it is
   // compared with |frame_policy.required_document_policy| to decide
   // whether to block the document load or not.
@@ -1716,12 +1716,10 @@
   // initialization is delayed to
   // SecurityContextInit::InitializeDocumentPolicy(), similar to
   // |report_only_feature_policy|.
-  security_init.CalculateDocumentPolicy(
+  security_init.ApplyDocumentPolicy(
       document_policy_,
       response_.HttpHeaderField(http_names::kDocumentPolicyReportOnly));
 
-  frame_->DomWindow()->Initialize(security_init);
-
   frame_->DomWindow()->SetOriginIsolationRestricted(
       origin_isolation_restricted_);
 
diff --git a/third_party/blink/renderer/core/loader/empty_clients.h b/third_party/blink/renderer/core/loader/empty_clients.h
index f08d47d..36a26e3 100644
--- a/third_party/blink/renderer/core/loader/empty_clients.h
+++ b/third_party/blink/renderer/core/loader/empty_clients.h
@@ -415,9 +415,6 @@
                          const IntRect& transformed_frame_rect) override {}
   void UpdateRemoteViewportIntersection(
       const ViewportIntersectionState& intersection_state) override {}
-  uint32_t Print(const IntRect& rect, cc::PaintCanvas* canvas) const override {
-    return 0;
-  }
   AssociatedInterfaceProvider* GetRemoteAssociatedInterfaces() override {
     return AssociatedInterfaceProvider::GetEmptyAssociatedInterfaceProvider();
   }
diff --git a/third_party/blink/renderer/core/loader/mixed_content_checker.cc b/third_party/blink/renderer/core/loader/mixed_content_checker.cc
index 78cb1e3..6c34ed8 100644
--- a/third_party/blink/renderer/core/loader/mixed_content_checker.cc
+++ b/third_party/blink/renderer/core/loader/mixed_content_checker.cc
@@ -37,7 +37,6 @@
 #include "third_party/blink/public/mojom/loader/request_context_frame_type.mojom-blink.h"
 #include "third_party/blink/public/mojom/security_context/insecure_request_policy.mojom-blink.h"
 #include "third_party/blink/public/platform/web_content_settings_client.h"
-#include "third_party/blink/public/platform/web_mixed_content.h"
 #include "third_party/blink/public/platform/web_security_origin.h"
 #include "third_party/blink/public/platform/web_worker_fetch_context.h"
 #include "third_party/blink/renderer/core/dom/document.h"
@@ -374,8 +373,7 @@
   // blockable category:
   WebMixedContentContextType context_type =
       WebMixedContent::ContextTypeFromRequestContext(
-          request_context,
-          frame->GetSettings()->GetStrictMixedContentCheckingForPlugin());
+          request_context, DecideCheckModeForPlugin(frame->GetSettings()));
   if (context_type == WebMixedContentContextType::kBlockable) {
     UseCounter::Count(source->GetDocument(),
                       WebFeature::kMixedContentBlockable);
@@ -473,7 +471,7 @@
 
   WebMixedContentContextType context_type =
       WebMixedContent::ContextTypeFromRequestContext(
-          request_context, settings->GetStrictMixedContentCheckingForPlugin());
+          request_context, DecideCheckModeForPlugin(settings));
 
   switch (context_type) {
     case WebMixedContentContextType::kOptionallyBlockable:
@@ -759,11 +757,12 @@
     WebContentSettingsClient* settings_client,
     const KURL& url) {
   // We are currently not autoupgrading plugin loaded content, which is why
-  // strict_mixed_content_for_plugin is hardcoded to true.
+  // check_mode_for_plugin is hardcoded to kStrict.
   if (!base::FeatureList::IsEnabled(
           blink::features::kMixedContentAutoupgrade) ||
       context_https_state == HttpsState::kNone ||
-      WebMixedContent::ContextTypeFromRequestContext(type, true) !=
+      WebMixedContent::ContextTypeFromRequestContext(
+          type, WebMixedContent::CheckModeForPlugin::kStrict) !=
           WebMixedContentContextType::kOptionallyBlockable) {
     return false;
   }
@@ -812,11 +811,11 @@
 void MixedContentChecker::HandleCertificateError(
     const ResourceResponse& response,
     mojom::RequestContextType request_context,
-    bool strict_mixed_content_checking_for_plugin,
+    WebMixedContent::CheckModeForPlugin check_mode_for_plugin,
     mojom::blink::ContentSecurityNotifier& notifier) {
   WebMixedContentContextType context_type =
-      WebMixedContent::ContextTypeFromRequestContext(
-          request_context, strict_mixed_content_checking_for_plugin);
+      WebMixedContent::ContextTypeFromRequestContext(request_context,
+                                                     check_mode_for_plugin);
   if (context_type == WebMixedContentContextType::kBlockable) {
     notifier.NotifyContentWithCertificateErrorsRan();
   } else {
@@ -882,12 +881,9 @@
   Frame* mixed_frame = InWhichFrameIsContentMixed(frame, request.Url());
   if (!mixed_frame)
     return WebMixedContentContextType::kNotMixedContent;
-
-  bool strict_mixed_content_checking_for_plugin =
-      mixed_frame->GetSettings() &&
-      mixed_frame->GetSettings()->GetStrictMixedContentCheckingForPlugin();
   return WebMixedContent::ContextTypeFromRequestContext(
-      request.GetRequestContext(), strict_mixed_content_checking_for_plugin);
+      request.GetRequestContext(),
+      DecideCheckModeForPlugin(mixed_frame->GetSettings()));
 }
 
 // static
@@ -978,7 +974,8 @@
             WebFeature::kUpgradeInsecureRequestsUpgradedRequestUnknown);
       } else {
         WebMixedContentContextType content_type =
-            WebMixedContent::ContextTypeFromRequestContext(context, false);
+            WebMixedContent::ContextTypeFromRequestContext(
+                context, WebMixedContent::CheckModeForPlugin::kLax);
         switch (content_type) {
           case WebMixedContentContextType::kOptionallyBlockable:
             UseCounter::Count(
@@ -1004,4 +1001,12 @@
   }
 }
 
+// static
+WebMixedContent::CheckModeForPlugin
+MixedContentChecker::DecideCheckModeForPlugin(Settings* settings) {
+  if (settings && settings->GetStrictMixedContentCheckingForPlugin())
+    return WebMixedContent::CheckModeForPlugin::kStrict;
+  return WebMixedContent::CheckModeForPlugin::kLax;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/loader/mixed_content_checker.h b/third_party/blink/renderer/core/loader/mixed_content_checker.h
index 7033bf5..82e8afc 100644
--- a/third_party/blink/renderer/core/loader/mixed_content_checker.h
+++ b/third_party/blink/renderer/core/loader/mixed_content_checker.h
@@ -34,6 +34,7 @@
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "third_party/blink/public/mojom/loader/content_security_notifier.mojom-blink-forward.h"
+#include "third_party/blink/public/platform/web_mixed_content.h"
 #include "third_party/blink/public/platform/web_mixed_content_context_type.h"
 #include "third_party/blink/public/platform/web_url_request.h"
 #include "third_party/blink/renderer/core/core_export.h"
@@ -54,6 +55,7 @@
 class KURL;
 class ResourceResponse;
 class SecurityOrigin;
+class Settings;
 class SourceLocation;
 class WebContentSettingsClient;
 class WorkerFetchContext;
@@ -112,12 +114,10 @@
       LocalFrame*,
       const ResourceRequest&);
 
-  // TODO(nhiroki): Introduce an enum class for
-  // |strict_mixed_content_checking_for_plugin|.
   static void HandleCertificateError(
       const ResourceResponse&,
       mojom::RequestContextType,
-      bool strict_mixed_content_checking_for_plugin,
+      WebMixedContent::CheckModeForPlugin,
       mojom::blink::ContentSecurityNotifier& notifier);
 
   // Receive information about mixed content found externally.
@@ -146,6 +146,9 @@
       mojom::RequestContextFrameType,
       WebContentSettingsClient* settings_client);
 
+  static WebMixedContent::CheckModeForPlugin DecideCheckModeForPlugin(
+      Settings*);
+
  private:
   FRIEND_TEST_ALL_PREFIXES(MixedContentCheckerTest, HandleCertificateError);
 
diff --git a/third_party/blink/renderer/core/loader/mixed_content_checker_test.cc b/third_party/blink/renderer/core/loader/mixed_content_checker_test.cc
index 346bfa5..42a217f 100644
--- a/third_party/blink/renderer/core/loader/mixed_content_checker_test.cc
+++ b/third_party/blink/renderer/core/loader/mixed_content_checker_test.cc
@@ -128,21 +128,20 @@
   EXPECT_CALL(mock_notifier, NotifyContentWithCertificateErrorsRan()).Times(1);
   MixedContentChecker::HandleCertificateError(
       response1, mojom::RequestContextType::SCRIPT,
-      /*strict_mixed_content_checking_for_plugin=*/false, *notifier_remote);
+      WebMixedContent::CheckModeForPlugin::kLax, *notifier_remote);
 
   ResourceResponse response2(displayed_url);
   mojom::RequestContextType request_context = mojom::RequestContextType::IMAGE;
   ASSERT_EQ(
       WebMixedContentContextType::kOptionallyBlockable,
       WebMixedContent::ContextTypeFromRequestContext(
-          request_context, dummy_page_holder->GetFrame()
-                               .GetSettings()
-                               ->GetStrictMixedContentCheckingForPlugin()));
+          request_context, MixedContentChecker::DecideCheckModeForPlugin(
+                               dummy_page_holder->GetFrame().GetSettings())));
   EXPECT_CALL(mock_notifier, NotifyContentWithCertificateErrorsDisplayed())
       .Times(1);
   MixedContentChecker::HandleCertificateError(
-      response2, request_context,
-      /*strict_mixed_content_checking_for_plugin=*/false, *notifier_remote);
+      response2, request_context, WebMixedContent::CheckModeForPlugin::kLax,
+      *notifier_remote);
 
   notifier_remote.FlushForTesting();
 }
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 efa2467..272852b 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
@@ -7,6 +7,8 @@
 #include <memory>
 
 #include "base/metrics/histogram_macros.h"
+#include "third_party/blink/public/common/feature_policy/policy_value.h"
+#include "third_party/blink/public/mojom/feature_policy/document_policy_feature.mojom-blink.h"
 #include "third_party/blink/public/mojom/feature_policy/feature_policy_feature.mojom-blink.h"
 #include "third_party/blink/public/mojom/feature_policy/policy_value.mojom-blink.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
diff --git a/third_party/blink/renderer/core/loader/resource_load_observer_for_frame.cc b/third_party/blink/renderer/core/loader/resource_load_observer_for_frame.cc
index d9ec349..026281b 100644
--- a/third_party/blink/renderer/core/loader/resource_load_observer_for_frame.cc
+++ b/third_party/blink/renderer/core/loader/resource_load_observer_for_frame.cc
@@ -179,9 +179,7 @@
   if (response.HasMajorCertificateErrors()) {
     MixedContentChecker::HandleCertificateError(
         response, request.GetRequestContext(),
-        frame->GetSettings()
-            ? frame->GetSettings()->GetStrictMixedContentCheckingForPlugin()
-            : false,
+        MixedContentChecker::DecideCheckModeForPlugin(frame->GetSettings()),
         document_loader_->GetContentSecurityNotifier());
   }
 
diff --git a/third_party/blink/renderer/core/loader/resource_load_observer_for_worker.cc b/third_party/blink/renderer/core/loader/resource_load_observer_for_worker.cc
index d754d01..e0be94a 100644
--- a/third_party/blink/renderer/core/loader/resource_load_observer_for_worker.cc
+++ b/third_party/blink/renderer/core/loader/resource_load_observer_for_worker.cc
@@ -56,7 +56,7 @@
   if (response.HasMajorCertificateErrors()) {
     MixedContentChecker::HandleCertificateError(
         response, request.GetRequestContext(),
-        /*strict_mixed_content_checking_for_plugin=*/false,
+        WebMixedContent::CheckModeForPlugin::kLax,
         worker_fetch_context_->GetContentSecurityNotifier());
   }
   probe::DidReceiveResourceResponse(probe_, identifier, nullptr, response,
diff --git a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
index 7ededbe..82462eca 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
+++ b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
@@ -2462,6 +2462,8 @@
        cursor.MoveToPreviousSibling()) {
     const NGFragmentItem* item = cursor.Current().Item();
     DCHECK(item);
+    if (UNLIKELY(item->IsLayoutObjectDestroyedOrMoved()))
+      continue;
     if (item->Type() == NGFragmentItem::kBox) {
       if (const NGPhysicalBoxFragment* child_box = item->BoxFragment()) {
         if (child_box->HasSelfPaintingLayer())
diff --git a/third_party/blink/renderer/core/paint/object_painter_base.cc b/third_party/blink/renderer/core/paint/object_painter_base.cc
index c7344f7c..bb6a75d 100644
--- a/third_party/blink/renderer/core/paint/object_painter_base.cc
+++ b/third_party/blink/renderer/core/paint/object_painter_base.cc
@@ -579,8 +579,7 @@
     return;
   }
 
-  IntRect united_outline_rect =
-      UnionRectEvenIfEmpty(pixel_snapped_outline_rects);
+  IntRect united_outline_rect = UnionRect(pixel_snapped_outline_rects);
   if (united_outline_rect == pixel_snapped_outline_rects[0]) {
     PaintSingleRectangleOutline(paint_info, united_outline_rect, style, color);
     return;
diff --git a/third_party/blink/renderer/core/streams/transferable_streams.cc b/third_party/blink/renderer/core/streams/transferable_streams.cc
index bcd44ba..94ce65b 100644
--- a/third_party/blink/renderer/core/streams/transferable_streams.cc
+++ b/third_party/blink/renderer/core/streams/transferable_streams.cc
@@ -487,10 +487,7 @@
 class CrossRealmTransformReadable final : public CrossRealmTransformStream {
  public:
   CrossRealmTransformReadable(ScriptState* script_state, MessagePort* port)
-      : script_state_(script_state),
-        message_port_(port),
-        backpressure_promise_(
-            MakeGarbageCollected<StreamPromiseResolver>(script_state)) {}
+      : script_state_(script_state), message_port_(port) {}
 
   ReadableStream* CreateReadableStream(ExceptionState&);
 
@@ -502,7 +499,6 @@
   void Trace(Visitor* visitor) const override {
     visitor->Trace(script_state_);
     visitor->Trace(message_port_);
-    visitor->Trace(backpressure_promise_);
     visitor->Trace(controller_);
     CrossRealmTransformStream::Trace(visitor);
   }
@@ -513,7 +509,6 @@
 
   const Member<ScriptState> script_state_;
   const Member<MessagePort> message_port_;
-  Member<StreamPromiseResolver> backpressure_promise_;
   Member<ReadableStreamDefaultController> controller_;
   bool finished_ = false;
 };
@@ -542,7 +537,9 @@
       return PromiseReject(script_state, error);
     }
 
-    return readable_->backpressure_promise_->V8Promise(isolate);
+    // The Streams Standard guarantees that PullAlgorithm won't be called again
+    // until Enqueue() is called.
+    return PromiseResolveWithUndefined(script_state);
   }
 
   void Trace(Visitor* visitor) const override {
@@ -624,10 +621,6 @@
         // 1.0.
         ReadableStreamDefaultController::Enqueue(script_state_, controller_,
                                                  value, ASSERT_NO_EXCEPTION);
-
-        backpressure_promise_->ResolveWithUndefined(script_state_);
-        backpressure_promise_ =
-            MakeGarbageCollected<StreamPromiseResolver>(script_state_);
       }
       return;
     }
diff --git a/third_party/blink/renderer/core/svg/svg_image_element.cc b/third_party/blink/renderer/core/svg/svg_image_element.cc
index 3dd31c2..f2717be 100644
--- a/third_party/blink/renderer/core/svg/svg_image_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_image_element.cc
@@ -21,6 +21,7 @@
 
 #include "third_party/blink/renderer/core/svg/svg_image_element.h"
 
+#include "third_party/blink/public/mojom/feature_policy/document_policy_feature.mojom-blink.h"
 #include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom-blink.h"
 #include "third_party/blink/renderer/core/css/css_property_names.h"
 #include "third_party/blink/renderer/core/css/style_change_reason.h"
diff --git a/third_party/blink/renderer/core/testing/fake_remote_frame_host.cc b/third_party/blink/renderer/core/testing/fake_remote_frame_host.cc
index 3d32bdc2..58a32ea9 100644
--- a/third_party/blink/renderer/core/testing/fake_remote_frame_host.cc
+++ b/third_party/blink/renderer/core/testing/fake_remote_frame_host.cc
@@ -46,6 +46,9 @@
     const String& target_origin,
     BlinkTransferableMessage message) {}
 
+void FakeRemoteFrameHost::PrintCrossProcessSubframe(const gfx::Rect& rect,
+                                                    int document_cookie) {}
+
 void FakeRemoteFrameHost::BindFrameHostReceiver(
     mojo::ScopedInterfaceEndpointHandle handle) {
   receiver_.Bind(mojo::PendingAssociatedReceiver<mojom::blink::RemoteFrameHost>(
diff --git a/third_party/blink/renderer/core/testing/fake_remote_frame_host.h b/third_party/blink/renderer/core/testing/fake_remote_frame_host.h
index a83b0f1..72624b7 100644
--- a/third_party/blink/renderer/core/testing/fake_remote_frame_host.h
+++ b/third_party/blink/renderer/core/testing/fake_remote_frame_host.h
@@ -43,6 +43,8 @@
       const String& source_origin,
       const String& target_origin,
       BlinkTransferableMessage message) override;
+  void PrintCrossProcessSubframe(const gfx::Rect& rect,
+                                 int document_cookie) override;
 
  private:
   void BindFrameHostReceiver(mojo::ScopedInterfaceEndpointHandle handle);
diff --git a/third_party/blink/renderer/modules/BUILD.gn b/third_party/blink/renderer/modules/BUILD.gn
index db9d785..50031049 100644
--- a/third_party/blink/renderer/modules/BUILD.gn
+++ b/third_party/blink/renderer/modules/BUILD.gn
@@ -67,6 +67,7 @@
   ]
 
   sub_modules = [
+    "//third_party/blink/renderer/bindings/modules/v8",
     "//third_party/blink/renderer/modules/accessibility",
     "//third_party/blink/renderer/modules/animationworklet",
     "//third_party/blink/renderer/modules/app_banner",
@@ -199,9 +200,9 @@
 }
 
 jumbo_source_set("modules_testing") {
+  testonly = true
+
   sources = [
-    "$bindings_modules_v8_output_dir/v8_internals_partial.cc",
-    "$bindings_modules_v8_output_dir/v8_internals_partial.h",
     "accessibility/testing/internals_accessibility.cc",
     "accessibility/testing/internals_accessibility.h",
     "canvas/canvas2d/testing/internals_canvas_rendering_context_2d.cc",
@@ -245,6 +246,7 @@
   ]
 
   deps = [
+    "//third_party/blink/renderer/bindings/modules/v8:testing",
     "//third_party/blink/renderer/core",
     "//third_party/blink/renderer/modules",
   ]
@@ -254,6 +256,7 @@
   public_deps = [
     ":module_names",
     "//third_party/blink/renderer/bindings/modules:bindings_modules_generated",
+    "//third_party/blink/renderer/bindings/modules/v8:generated",
     "//third_party/blink/renderer/core:core_event_interfaces",
   ]
 }
diff --git a/third_party/blink/renderer/modules/manifest/image_resource_type_converters.cc b/third_party/blink/renderer/modules/manifest/image_resource_type_converters.cc
index 700e0c2..d98302c 100644
--- a/third_party/blink/renderer/modules/manifest/image_resource_type_converters.cc
+++ b/third_party/blink/renderer/modules/manifest/image_resource_type_converters.cc
@@ -106,8 +106,7 @@
     image_resource_ptr->sizes = ParseSizes(image_resource->sizes());
   if (image_resource->hasPurpose())
     image_resource_ptr->purpose = ParsePurpose(image_resource->purpose());
-  if (image_resource->hasType())
-    image_resource_ptr->type = ParseType(image_resource->type());
+  image_resource_ptr->type = ParseType(image_resource->type());
   return image_resource_ptr;
 }
 
diff --git a/third_party/blink/renderer/modules/media_controls/elements/media_control_timeline_metrics.h b/third_party/blink/renderer/modules/media_controls/elements/media_control_timeline_metrics.h
index ec5fe37..f74e980b 100644
--- a/third_party/blink/renderer/modules/media_controls/elements/media_control_timeline_metrics.h
+++ b/third_party/blink/renderer/modules/media_controls/elements/media_control_timeline_metrics.h
@@ -6,7 +6,6 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIA_CONTROLS_ELEMENTS_MEDIA_CONTROL_TIMELINE_METRICS_H_
 
 #include "base/time/time.h"
-#include "third_party/blink/public/common/screen_orientation/web_screen_orientation_type.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_impl_test.cc b/third_party/blink/renderer/modules/media_controls/media_controls_impl_test.cc
index bb34469..a8236e1 100644
--- a/third_party/blink/renderer/modules/media_controls/media_controls_impl_test.cc
+++ b/third_party/blink/renderer/modules/media_controls/media_controls_impl_test.cc
@@ -11,6 +11,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/input/web_mouse_event.h"
 #include "third_party/blink/public/mojom/input/focus_type.mojom-blink.h"
+#include "third_party/blink/public/mojom/widget/screen_orientation.mojom-blink.h"
 #include "third_party/blink/public/platform/modules/remoteplayback/web_remote_playback_client.h"
 #include "third_party/blink/public/platform/web_screen_info.h"
 #include "third_party/blink/public/platform/web_size.h"
@@ -68,7 +69,8 @@
   // ChromeClient overrides.
   WebScreenInfo GetScreenInfo(LocalFrame&) const override {
     WebScreenInfo screen_info;
-    screen_info.orientation_type = kWebScreenOrientationLandscapePrimary;
+    screen_info.orientation_type =
+        mojom::blink::ScreenOrientation::kLandscapePrimary;
     return screen_info;
   }
 };
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_orientation_lock_delegate.cc b/third_party/blink/renderer/modules/media_controls/media_controls_orientation_lock_delegate.cc
index feff64f..29993c0 100644
--- a/third_party/blink/renderer/modules/media_controls/media_controls_orientation_lock_delegate.cc
+++ b/third_party/blink/renderer/modules/media_controls/media_controls_orientation_lock_delegate.cc
@@ -264,13 +264,13 @@
 
   ChromeClient& chrome_client = frame->GetChromeClient();
   switch (chrome_client.GetScreenInfo(*frame).orientation_type) {
-    case kWebScreenOrientationPortraitPrimary:
-    case kWebScreenOrientationPortraitSecondary:
+    case mojom::blink::ScreenOrientation::kPortraitPrimary:
+    case mojom::blink::ScreenOrientation::kPortraitSecondary:
       return kWebScreenOrientationLockPortrait;
-    case kWebScreenOrientationLandscapePrimary:
-    case kWebScreenOrientationLandscapeSecondary:
+    case mojom::blink::ScreenOrientation::kLandscapePrimary:
+    case mojom::blink::ScreenOrientation::kLandscapeSecondary:
       return kWebScreenOrientationLockLandscape;
-    case kWebScreenOrientationUndefined:
+    case mojom::blink::ScreenOrientation::kUndefined:
       return kWebScreenOrientationLockLandscape;
   }
 
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_orientation_lock_delegate_test.cc b/third_party/blink/renderer/modules/media_controls/media_controls_orientation_lock_delegate_test.cc
index b8f19d31..b0661128 100644
--- a/third_party/blink/renderer/modules/media_controls/media_controls_orientation_lock_delegate_test.cc
+++ b/third_party/blink/renderer/modules/media_controls/media_controls_orientation_lock_delegate_test.cc
@@ -12,6 +12,7 @@
 #include "services/device/public/mojom/screen_orientation.mojom-blink.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/mojom/widget/screen_orientation.mojom-blink.h"
 #include "third_party/blink/public/platform/web_size.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/frame/frame_view.h"
@@ -362,7 +363,7 @@
   }
 
   // Calls must be wrapped in ASSERT_NO_FATAL_FAILURE.
-  void RotateScreenTo(WebScreenOrientationType screen_orientation_type,
+  void RotateScreenTo(mojom::blink::ScreenOrientation screen_orientation_type,
                       uint16_t screen_orientation_angle) {
     WebScreenInfo screen_info;
     screen_info.orientation_type = screen_orientation_type;
@@ -585,31 +586,35 @@
 
   // 100x100 has more subtilities, it depends on the current screen orientation.
   WebScreenInfo screen_info;
-  screen_info.orientation_type = kWebScreenOrientationUndefined;
+  screen_info.orientation_type = mojom::blink::ScreenOrientation::kUndefined;
   EXPECT_CALL(ChromeClient(), GetScreenInfo(_))
       .Times(1)
       .WillOnce(Return(screen_info));
   EXPECT_EQ(kWebScreenOrientationLockLandscape, ComputeOrientationLock());
 
-  screen_info.orientation_type = kWebScreenOrientationPortraitPrimary;
+  screen_info.orientation_type =
+      mojom::blink::ScreenOrientation::kPortraitPrimary;
   EXPECT_CALL(ChromeClient(), GetScreenInfo(_))
       .Times(1)
       .WillOnce(Return(screen_info));
   EXPECT_EQ(kWebScreenOrientationLockPortrait, ComputeOrientationLock());
 
-  screen_info.orientation_type = kWebScreenOrientationPortraitPrimary;
+  screen_info.orientation_type =
+      mojom::blink::ScreenOrientation::kPortraitPrimary;
   EXPECT_CALL(ChromeClient(), GetScreenInfo(_))
       .Times(1)
       .WillOnce(Return(screen_info));
   EXPECT_EQ(kWebScreenOrientationLockPortrait, ComputeOrientationLock());
 
-  screen_info.orientation_type = kWebScreenOrientationLandscapePrimary;
+  screen_info.orientation_type =
+      mojom::blink::ScreenOrientation::kLandscapePrimary;
   EXPECT_CALL(ChromeClient(), GetScreenInfo(_))
       .Times(1)
       .WillOnce(Return(screen_info));
   EXPECT_EQ(kWebScreenOrientationLockLandscape, ComputeOrientationLock());
 
-  screen_info.orientation_type = kWebScreenOrientationLandscapeSecondary;
+  screen_info.orientation_type =
+      mojom::blink::ScreenOrientation::kLandscapeSecondary;
   EXPECT_CALL(ChromeClient(), GetScreenInfo(_))
       .Times(1)
       .WillOnce(Return(screen_info));
@@ -640,27 +645,32 @@
     // or naturally landscape). Similarly for a naturally landscape device.
     for (int screen_angle = 0; screen_angle < 360; screen_angle += 90) {
       SCOPED_TRACE(testing::Message() << "screen_angle=" << screen_angle);
-      WebScreenOrientationType screen_type = kWebScreenOrientationUndefined;
+      mojom::blink::ScreenOrientation screen_type =
+          mojom::blink::ScreenOrientation::kUndefined;
       switch (screen_angle) {
         case 0:
-          screen_type = natural_orientation_is_portrait_
-                            ? kWebScreenOrientationPortraitPrimary
-                            : kWebScreenOrientationLandscapePrimary;
+          screen_type =
+              natural_orientation_is_portrait_
+                  ? mojom::blink::ScreenOrientation::kPortraitPrimary
+                  : mojom::blink::ScreenOrientation::kLandscapePrimary;
           break;
         case 90:
-          screen_type = natural_orientation_is_portrait_
-                            ? kWebScreenOrientationLandscapePrimary
-                            : kWebScreenOrientationPortraitSecondary;
+          screen_type =
+              natural_orientation_is_portrait_
+                  ? mojom::blink::ScreenOrientation::kLandscapePrimary
+                  : mojom::blink::ScreenOrientation::kPortraitSecondary;
           break;
         case 180:
-          screen_type = natural_orientation_is_portrait_
-                            ? kWebScreenOrientationPortraitSecondary
-                            : kWebScreenOrientationLandscapeSecondary;
+          screen_type =
+              natural_orientation_is_portrait_
+                  ? mojom::blink::ScreenOrientation::kPortraitSecondary
+                  : mojom::blink::ScreenOrientation::kLandscapeSecondary;
           break;
         case 270:
-          screen_type = natural_orientation_is_portrait_
-                            ? kWebScreenOrientationLandscapeSecondary
-                            : kWebScreenOrientationPortraitPrimary;
+          screen_type =
+              natural_orientation_is_portrait_
+                  ? mojom::blink::ScreenOrientation::kLandscapeSecondary
+                  : mojom::blink::ScreenOrientation::kPortraitPrimary;
           break;
       }
       ASSERT_NO_FATAL_FAILURE(RotateScreenTo(screen_type, screen_angle));
@@ -800,7 +810,7 @@
   // Naturally portrait device, initially portrait, with landscape video.
   natural_orientation_is_portrait_ = true;
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationPortraitPrimary, 0));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kPortraitPrimary, 0));
   InitVideo(640, 480);
   SetIsAutoRotateEnabledByUser(true);
   PlayVideo();
@@ -814,7 +824,7 @@
   // Simulate user rotating their device to landscape triggering a screen
   // orientation change.
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationLandscapePrimary, 90));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kLandscapePrimary, 90));
 
   // MediaControlsRotateToFullscreenDelegate should enter fullscreen, so
   // MediaControlsOrientationLockDelegate should lock orientation to landscape
@@ -839,7 +849,7 @@
   // Naturally portrait device, initially portrait, with landscape video.
   natural_orientation_is_portrait_ = true;
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationPortraitPrimary, 0));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kPortraitPrimary, 0));
   InitVideo(640, 480);
   SetIsAutoRotateEnabledByUser(true);
 
@@ -858,7 +868,7 @@
 
   // This will trigger a screen orientation change to landscape.
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationLandscapePrimary, 90));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kLandscapePrimary, 90));
 
   // Even though the device is still held in portrait.
   RotateDeviceTo(0 /* portrait primary */);
@@ -875,7 +885,7 @@
   // to landscape screen orientation, with landscape video.
   natural_orientation_is_portrait_ = true;
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationLandscapePrimary, 90));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kLandscapePrimary, 90));
   InitVideo(640, 480);
   SetIsAutoRotateEnabledByUser(true);
 
@@ -903,7 +913,7 @@
   // to landscape screen orientation, with landscape video.
   natural_orientation_is_portrait_ = true;
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationLandscapePrimary, 90));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kLandscapePrimary, 90));
   InitVideo(640, 480);
   SetIsAutoRotateEnabledByUser(true);
 
@@ -931,7 +941,7 @@
   // to portrait (since the device orientation was already portrait, even though
   // the screen was locked to landscape).
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationPortraitPrimary, 0));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kPortraitPrimary, 0));
 
   // Video should remain inline, unlocked.
   CheckStatePendingFullscreen();
@@ -944,7 +954,7 @@
   // Naturally portrait device, initially landscape, with landscape video.
   natural_orientation_is_portrait_ = true;
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationLandscapePrimary, 90));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kLandscapePrimary, 90));
   InitVideo(640, 480);
   SetIsAutoRotateEnabledByUser(true);
   PlayVideo();
@@ -958,7 +968,7 @@
   // Simulate user rotating their device to portrait triggering a screen
   // orientation change.
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationPortraitPrimary, 0));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kPortraitPrimary, 0));
   test::RunDelayedTasks(GetUnlockDelay());
 
   // Video should remain inline, unlocked.
@@ -972,7 +982,7 @@
   // Naturally portrait device, initially landscape, with landscape video.
   natural_orientation_is_portrait_ = true;
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationLandscapePrimary, 90));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kLandscapePrimary, 90));
   InitVideo(640, 480);
   SetIsAutoRotateEnabledByUser(true);
 
@@ -1006,7 +1016,7 @@
   // Naturally portrait device, initially landscape, with landscape video.
   natural_orientation_is_portrait_ = true;
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationLandscapePrimary, 90));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kLandscapePrimary, 90));
   InitVideo(640, 480);
   SetIsAutoRotateEnabledByUser(true);
 
@@ -1022,7 +1032,7 @@
   // Simulate user rotating their device to portrait triggering a screen
   // orientation change.
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationPortraitPrimary, 0));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kPortraitPrimary, 0));
   test::RunDelayedTasks(GetUnlockDelay());
 
   // MediaControlsRotateToFullscreenDelegate should exit fullscreen.
@@ -1038,7 +1048,7 @@
   // Naturally portrait device, initially landscape, with landscape video.
   natural_orientation_is_portrait_ = true;
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationLandscapePrimary, 90));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kLandscapePrimary, 90));
   InitVideo(640, 480);
   SetIsAutoRotateEnabledByUser(true);
 
@@ -1066,7 +1076,7 @@
   // Naturally portrait device, initially portrait, with landscape video.
   natural_orientation_is_portrait_ = true;
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationPortraitPrimary, 0));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kPortraitPrimary, 0));
   InitVideo(640, 480);
   // But this time the user has disabled auto rotate, e.g. locked to portrait.
   SetIsAutoRotateEnabledByUser(false);
@@ -1087,7 +1097,7 @@
   // This will trigger a screen orientation change to landscape, since the app's
   // lock overrides the user's orientation lock (at least on Android).
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationLandscapePrimary, 90));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kLandscapePrimary, 90));
 
   // Even though the device is still held in portrait.
   RotateDeviceTo(0 /* portrait primary */);
@@ -1105,7 +1115,7 @@
   // to landscape screen orientation, with landscape video.
   natural_orientation_is_portrait_ = true;
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationLandscapePrimary, 90));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kLandscapePrimary, 90));
   InitVideo(640, 480);
   // But this time the user has disabled auto rotate, e.g. locked to portrait
   // (even though the app's landscape screen orientation lock overrides it).
@@ -1138,7 +1148,7 @@
   // to landscape screen orientation, with landscape video.
   natural_orientation_is_portrait_ = true;
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationLandscapePrimary, 90));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kLandscapePrimary, 90));
   InitVideo(640, 480);
   // But this time the user has disabled auto rotate, e.g. locked to portrait
   // (even though the app's landscape screen orientation lock overrides it).
@@ -1169,7 +1179,7 @@
   // (which happens to also match the device orientation) and
   // MediaControlsOrientationLockDelegate is no longer overriding that lock.
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationPortraitPrimary, 0));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kPortraitPrimary, 0));
 
   // Video should remain inline, unlocked.
   CheckStatePendingFullscreen();
@@ -1185,7 +1195,7 @@
   // rotate, with landscape video.
   natural_orientation_is_portrait_ = true;
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationLandscapePrimary, 90));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kLandscapePrimary, 90));
   InitVideo(640, 480);
   // The user has disabled auto rotate, e.g. locked to portrait (even though the
   // app's landscape screen orientation lock overrides it).
@@ -1224,7 +1234,7 @@
   // rotate, with landscape video.
   natural_orientation_is_portrait_ = true;
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationLandscapePrimary, 90));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kLandscapePrimary, 90));
   InitVideo(640, 480);
   // The user has disabled auto rotate, e.g. locked to portrait (even though the
   // app's landscape screen orientation lock overrides it).
@@ -1255,7 +1265,7 @@
   // had locked the screen orientation to portrait, and
   // MediaControlsOrientationLockDelegate is no longer overriding that.
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationPortraitPrimary, 0));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kPortraitPrimary, 0));
 
   // Video should remain inline, unlocked.
   CheckStatePendingFullscreen();
@@ -1268,7 +1278,7 @@
   // Naturally portrait device, initially landscape, with *portrait* video.
   natural_orientation_is_portrait_ = true;
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationLandscapePrimary, 90));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kLandscapePrimary, 90));
   InitVideo(480, 640);
   SetIsAutoRotateEnabledByUser(true);
   PlayVideo();
@@ -1282,7 +1292,7 @@
   // Simulate user rotating their device to portrait triggering a screen
   // orientation change.
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationPortraitPrimary, 0));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kPortraitPrimary, 0));
 
   // MediaControlsRotateToFullscreenDelegate should enter fullscreen, so
   // MediaControlsOrientationLockDelegate should lock orientation to portrait
@@ -1305,7 +1315,7 @@
   // Simulate user rotating their device to landscape triggering a screen
   // orientation change.
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationLandscapePrimary, 90));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kLandscapePrimary, 90));
 
   // MediaControlsRotateToFullscreenDelegate should exit fullscreen.
   EXPECT_FALSE(Video().IsFullscreen());
@@ -1320,7 +1330,7 @@
   // Naturally *landscape* device, initially portrait, with landscape video.
   natural_orientation_is_portrait_ = false;
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationPortraitPrimary, 270));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kPortraitPrimary, 270));
   InitVideo(640, 480);
   SetIsAutoRotateEnabledByUser(true);
   PlayVideo();
@@ -1334,7 +1344,7 @@
   // Simulate user rotating their device to landscape triggering a screen
   // orientation change.
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationLandscapePrimary, 0));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kLandscapePrimary, 0));
 
   // MediaControlsRotateToFullscreenDelegate should enter fullscreen, so
   // MediaControlsOrientationLockDelegate should lock orientation to landscape
@@ -1357,7 +1367,7 @@
   // Simulate user rotating their device to portrait triggering a screen
   // orientation change.
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationPortraitPrimary, 270));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kPortraitPrimary, 270));
 
   // MediaControlsRotateToFullscreenDelegate should exit fullscreen.
   EXPECT_FALSE(Video().IsFullscreen());
@@ -1372,7 +1382,7 @@
   // Naturally portrait device, initially portrait, with landscape video.
   natural_orientation_is_portrait_ = true;
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationPortraitPrimary, 0));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kPortraitPrimary, 0));
   InitVideo(640, 480);
   SetIsAutoRotateEnabledByUser(true);
 
@@ -1391,7 +1401,7 @@
 
   // This will trigger a screen orientation change to landscape.
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationLandscapePrimary, 90));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kLandscapePrimary, 90));
 
   // Even though the device is still held in portrait.
   RotateDeviceTo(0 /* portrait primary */);
@@ -1428,8 +1438,8 @@
 
   // Simulate the OS processing the device orientation change after a delay of
   // `kMinUnlockDelay` and hence changing the screen orientation.
-  ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationLandscapeSecondary, 270));
+  ASSERT_NO_FATAL_FAILURE(RotateScreenTo(
+      mojom::blink::ScreenOrientation::kLandscapeSecondary, 270));
 
   // MediaControlsOrientationLockDelegate should remain locked to landscape.
   CheckStateMaybeLockedFullscreen();
@@ -1449,7 +1459,7 @@
   // Naturally portrait device, initially portrait, with landscape video.
   natural_orientation_is_portrait_ = true;
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationPortraitPrimary, 0));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kPortraitPrimary, 0));
   InitVideo(640, 480);
   SetIsAutoRotateEnabledByUser(true);
 
@@ -1468,7 +1478,7 @@
 
   // This will trigger a screen orientation change to landscape.
   ASSERT_NO_FATAL_FAILURE(
-      RotateScreenTo(kWebScreenOrientationLandscapePrimary, 90));
+      RotateScreenTo(mojom::blink::ScreenOrientation::kLandscapePrimary, 90));
 
   // Rotate the device to match.
   RotateDeviceTo(90 /* landscape primary */);
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_rotate_to_fullscreen_delegate.cc b/third_party/blink/renderer/modules/media_controls/media_controls_rotate_to_fullscreen_delegate.cc
index 69e43a6c..3732ecc 100644
--- a/third_party/blink/renderer/modules/media_controls/media_controls_rotate_to_fullscreen_delegate.cc
+++ b/third_party/blink/renderer/modules/media_controls/media_controls_rotate_to_fullscreen_delegate.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/modules/media_controls/media_controls_rotate_to_fullscreen_delegate.h"
 
+#include "third_party/blink/public/mojom/widget/screen_orientation.mojom-blink.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/web_screen_info.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
@@ -272,13 +273,13 @@
     return SimpleOrientation::kUnknown;
 
   switch (frame->GetChromeClient().GetScreenInfo(*frame).orientation_type) {
-    case kWebScreenOrientationPortraitPrimary:
-    case kWebScreenOrientationPortraitSecondary:
+    case mojom::blink::ScreenOrientation::kPortraitPrimary:
+    case mojom::blink::ScreenOrientation::kPortraitSecondary:
       return SimpleOrientation::kPortrait;
-    case kWebScreenOrientationLandscapePrimary:
-    case kWebScreenOrientationLandscapeSecondary:
+    case mojom::blink::ScreenOrientation::kLandscapePrimary:
+    case mojom::blink::ScreenOrientation::kLandscapeSecondary:
       return SimpleOrientation::kLandscape;
-    case kWebScreenOrientationUndefined:
+    case mojom::blink::ScreenOrientation::kUndefined:
       return SimpleOrientation::kUnknown;
   }
 
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_rotate_to_fullscreen_delegate_test.cc b/third_party/blink/renderer/modules/media_controls/media_controls_rotate_to_fullscreen_delegate_test.cc
index 0efcbc52..bfbb9bb 100644
--- a/third_party/blink/renderer/modules/media_controls/media_controls_rotate_to_fullscreen_delegate_test.cc
+++ b/third_party/blink/renderer/modules/media_controls/media_controls_rotate_to_fullscreen_delegate_test.cc
@@ -8,7 +8,7 @@
 #include "services/device/public/mojom/screen_orientation.mojom-blink.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/public/common/screen_orientation/web_screen_orientation_type.h"
+#include "third_party/blink/public/mojom/widget/screen_orientation.mojom-blink.h"
 #include "third_party/blink/public/platform/web_size.h"
 #include "third_party/blink/renderer/core/css/css_style_declaration.h"
 #include "third_party/blink/renderer/core/dom/document.h"
@@ -154,9 +154,10 @@
     target.DispatchEvent(*Event::Create(type));
   }
 
-  void InitScreenAndVideo(WebScreenOrientationType initial_screen_orientation,
-                          gfx::Size video_size,
-                          bool with_device_orientation = true);
+  void InitScreenAndVideo(
+      mojom::blink::ScreenOrientation initial_screen_orientation,
+      gfx::Size video_size,
+      bool with_device_orientation = true);
 
   void PlayVideo();
 
@@ -166,7 +167,7 @@
     test::RunPendingTasks();
   }
 
-  void RotateTo(WebScreenOrientationType new_screen_orientation);
+  void RotateTo(mojom::blink::ScreenOrientation new_screen_orientation);
 
   MockChromeClient& GetChromeClient() const { return *chrome_client_; }
   LocalDOMWindow& GetWindow() const { return *GetDocument().domWindow(); }
@@ -185,7 +186,7 @@
 };
 
 void MediaControlsRotateToFullscreenDelegateTest::InitScreenAndVideo(
-    WebScreenOrientationType initial_screen_orientation,
+    mojom::blink::ScreenOrientation initial_screen_orientation,
     gfx::Size video_size,
     bool with_device_orientation /* = true */) {
   // Set initial screen orientation (called by `Attach` during `AppendChild`).
@@ -225,7 +226,7 @@
 }
 
 void MediaControlsRotateToFullscreenDelegateTest::RotateTo(
-    WebScreenOrientationType new_screen_orientation) {
+    mojom::blink::ScreenOrientation new_screen_orientation) {
   WebScreenInfo screen_info;
   screen_info.orientation_type = new_screen_orientation;
   testing::Mock::VerifyAndClearExpectations(&GetChromeClient());
@@ -336,7 +337,8 @@
 TEST_F(MediaControlsRotateToFullscreenDelegateTest,
        EnterSuccessPortraitToLandscape) {
   // Portrait screen, landscape video.
-  InitScreenAndVideo(kWebScreenOrientationPortraitPrimary, gfx::Size(640, 480));
+  InitScreenAndVideo(mojom::blink::ScreenOrientation::kPortraitPrimary,
+                     gfx::Size(640, 480));
   EXPECT_EQ(SimpleOrientation::kPortrait, ObservedScreenOrientation());
   EXPECT_EQ(SimpleOrientation::kLandscape, ComputeVideoOrientation());
 
@@ -348,7 +350,7 @@
   EXPECT_FALSE(GetVideo().IsFullscreen());
 
   // Rotate screen to landscape.
-  RotateTo(kWebScreenOrientationLandscapePrimary);
+  RotateTo(mojom::blink::ScreenOrientation::kLandscapePrimary);
 
   // Should enter fullscreen.
   EXPECT_TRUE(GetVideo().IsFullscreen());
@@ -357,7 +359,7 @@
 TEST_F(MediaControlsRotateToFullscreenDelegateTest,
        EnterSuccessLandscapeToPortrait) {
   // Landscape screen, portrait video.
-  InitScreenAndVideo(kWebScreenOrientationLandscapePrimary,
+  InitScreenAndVideo(mojom::blink::ScreenOrientation::kLandscapePrimary,
                      gfx::Size(480, 640));
   EXPECT_EQ(SimpleOrientation::kLandscape, ObservedScreenOrientation());
   EXPECT_EQ(SimpleOrientation::kPortrait, ComputeVideoOrientation());
@@ -370,7 +372,7 @@
   EXPECT_FALSE(GetVideo().IsFullscreen());
 
   // Rotate screen to portrait.
-  RotateTo(kWebScreenOrientationPortraitPrimary);
+  RotateTo(mojom::blink::ScreenOrientation::kPortraitPrimary);
 
   // Should enter fullscreen.
   EXPECT_TRUE(GetVideo().IsFullscreen());
@@ -379,7 +381,8 @@
 TEST_F(MediaControlsRotateToFullscreenDelegateTest,
        EnterSuccessSquarePortraitToLandscape) {
   // Portrait screen, square video.
-  InitScreenAndVideo(kWebScreenOrientationPortraitPrimary, gfx::Size(400, 400));
+  InitScreenAndVideo(mojom::blink::ScreenOrientation::kPortraitPrimary,
+                     gfx::Size(400, 400));
   EXPECT_EQ(SimpleOrientation::kPortrait, ObservedScreenOrientation());
   EXPECT_EQ(SimpleOrientation::kLandscape, ComputeVideoOrientation());
 
@@ -391,7 +394,7 @@
   EXPECT_FALSE(GetVideo().IsFullscreen());
 
   // Rotate screen to landscape.
-  RotateTo(kWebScreenOrientationLandscapePrimary);
+  RotateTo(mojom::blink::ScreenOrientation::kLandscapePrimary);
 
   // Should enter fullscreen, since square videos are currently treated the same
   // as landscape videos.
@@ -400,7 +403,7 @@
 
 TEST_F(MediaControlsRotateToFullscreenDelegateTest, EnterFailWrongOrientation) {
   // Landscape screen, landscape video.
-  InitScreenAndVideo(kWebScreenOrientationLandscapePrimary,
+  InitScreenAndVideo(mojom::blink::ScreenOrientation::kLandscapePrimary,
                      gfx::Size(640, 480));
   EXPECT_EQ(SimpleOrientation::kLandscape, ObservedScreenOrientation());
   EXPECT_EQ(SimpleOrientation::kLandscape, ComputeVideoOrientation());
@@ -412,7 +415,7 @@
   EXPECT_TRUE(ObservedVisibility());
 
   // Rotate screen to portrait.
-  RotateTo(kWebScreenOrientationPortraitPrimary);
+  RotateTo(mojom::blink::ScreenOrientation::kPortraitPrimary);
 
   // Should not enter fullscreen since the orientation that the device was
   // rotated to does not match the orientation of the video.
@@ -422,7 +425,7 @@
 TEST_F(MediaControlsRotateToFullscreenDelegateTest,
        EnterFailSquareWrongOrientation) {
   // Landscape screen, square video.
-  InitScreenAndVideo(kWebScreenOrientationLandscapePrimary,
+  InitScreenAndVideo(mojom::blink::ScreenOrientation::kLandscapePrimary,
                      gfx::Size(400, 400));
   EXPECT_EQ(SimpleOrientation::kLandscape, ObservedScreenOrientation());
   EXPECT_EQ(SimpleOrientation::kLandscape, ComputeVideoOrientation());
@@ -434,7 +437,7 @@
   EXPECT_TRUE(ObservedVisibility());
 
   // Rotate screen to portrait.
-  RotateTo(kWebScreenOrientationPortraitPrimary);
+  RotateTo(mojom::blink::ScreenOrientation::kPortraitPrimary);
 
   // Should not enter fullscreen since square videos are treated as landscape,
   // so rotating to portrait does not match the orientation of the video.
@@ -445,7 +448,8 @@
   DisableControls();
 
   // Portrait screen, landscape video.
-  InitScreenAndVideo(kWebScreenOrientationPortraitPrimary, gfx::Size(640, 480));
+  InitScreenAndVideo(mojom::blink::ScreenOrientation::kPortraitPrimary,
+                     gfx::Size(640, 480));
   EXPECT_EQ(SimpleOrientation::kPortrait, ObservedScreenOrientation());
   EXPECT_EQ(SimpleOrientation::kLandscape, ComputeVideoOrientation());
 
@@ -456,7 +460,7 @@
   EXPECT_TRUE(ObservedVisibility());
 
   // Rotate screen to landscape.
-  RotateTo(kWebScreenOrientationLandscapePrimary);
+  RotateTo(mojom::blink::ScreenOrientation::kLandscapePrimary);
 
   // Should not enter fullscreen since video has no controls.
   EXPECT_FALSE(GetVideo().IsFullscreen());
@@ -465,8 +469,8 @@
 TEST_F(MediaControlsRotateToFullscreenDelegateTest,
        EnterFailNoDeviceOrientation) {
   // Portrait screen, landscape video.
-  InitScreenAndVideo(kWebScreenOrientationPortraitPrimary, gfx::Size(640, 480),
-                     false /* with_device_orientation */);
+  InitScreenAndVideo(mojom::blink::ScreenOrientation::kPortraitPrimary,
+                     gfx::Size(640, 480), false /* with_device_orientation */);
   EXPECT_EQ(SimpleOrientation::kPortrait, ObservedScreenOrientation());
   EXPECT_EQ(SimpleOrientation::kLandscape, ComputeVideoOrientation());
 
@@ -483,7 +487,7 @@
   EXPECT_TRUE(ObservedVisibility());
 
   // Rotate screen to landscape.
-  RotateTo(kWebScreenOrientationLandscapePrimary);
+  RotateTo(mojom::blink::ScreenOrientation::kLandscapePrimary);
 
   // Should not enter fullscreen since Device Orientation is not available.
   EXPECT_FALSE(GetVideo().IsFullscreen());
@@ -492,8 +496,8 @@
 TEST_F(MediaControlsRotateToFullscreenDelegateTest,
        EnterFailZeroDeviceOrientation) {
   // Portrait screen, landscape video.
-  InitScreenAndVideo(kWebScreenOrientationPortraitPrimary, gfx::Size(640, 480),
-                     false /* with_device_orientation */);
+  InitScreenAndVideo(mojom::blink::ScreenOrientation::kPortraitPrimary,
+                     gfx::Size(640, 480), false /* with_device_orientation */);
   EXPECT_EQ(SimpleOrientation::kPortrait, ObservedScreenOrientation());
   EXPECT_EQ(SimpleOrientation::kLandscape, ComputeVideoOrientation());
 
@@ -513,7 +517,7 @@
   EXPECT_TRUE(ObservedVisibility());
 
   // Rotate screen to landscape.
-  RotateTo(kWebScreenOrientationLandscapePrimary);
+  RotateTo(mojom::blink::ScreenOrientation::kLandscapePrimary);
 
   // Should not enter fullscreen since Device Orientation is not available.
   EXPECT_FALSE(GetVideo().IsFullscreen());
@@ -521,7 +525,8 @@
 
 TEST_F(MediaControlsRotateToFullscreenDelegateTest, EnterFailPaused) {
   // Portrait screen, landscape video.
-  InitScreenAndVideo(kWebScreenOrientationPortraitPrimary, gfx::Size(640, 480));
+  InitScreenAndVideo(mojom::blink::ScreenOrientation::kPortraitPrimary,
+                     gfx::Size(640, 480));
   EXPECT_EQ(SimpleOrientation::kPortrait, ObservedScreenOrientation());
   EXPECT_EQ(SimpleOrientation::kLandscape, ComputeVideoOrientation());
 
@@ -532,7 +537,7 @@
   EXPECT_FALSE(ObservedVisibility());
 
   // Rotate screen to landscape.
-  RotateTo(kWebScreenOrientationLandscapePrimary);
+  RotateTo(mojom::blink::ScreenOrientation::kLandscapePrimary);
 
   // Should not enter fullscreen since video is paused.
   EXPECT_FALSE(GetVideo().IsFullscreen());
@@ -540,7 +545,8 @@
 
 TEST_F(MediaControlsRotateToFullscreenDelegateTest, EnterFailHidden) {
   // Portrait screen, landscape video.
-  InitScreenAndVideo(kWebScreenOrientationPortraitPrimary, gfx::Size(640, 480));
+  InitScreenAndVideo(mojom::blink::ScreenOrientation::kPortraitPrimary,
+                     gfx::Size(640, 480));
   EXPECT_EQ(SimpleOrientation::kPortrait, ObservedScreenOrientation());
   EXPECT_EQ(SimpleOrientation::kLandscape, ComputeVideoOrientation());
 
@@ -560,7 +566,7 @@
   EXPECT_FALSE(ObservedVisibility());
 
   // Rotate screen to landscape.
-  RotateTo(kWebScreenOrientationLandscapePrimary);
+  RotateTo(mojom::blink::ScreenOrientation::kLandscapePrimary);
 
   // Should not enter fullscreen since video is not visible.
   EXPECT_FALSE(GetVideo().IsFullscreen());
@@ -569,7 +575,7 @@
 TEST_F(MediaControlsRotateToFullscreenDelegateTest,
        EnterFail180DegreeRotation) {
   // Landscape screen, landscape video.
-  InitScreenAndVideo(kWebScreenOrientationLandscapeSecondary,
+  InitScreenAndVideo(mojom::blink::ScreenOrientation::kLandscapeSecondary,
                      gfx::Size(640, 480));
   EXPECT_EQ(SimpleOrientation::kLandscape, ObservedScreenOrientation());
   EXPECT_EQ(SimpleOrientation::kLandscape, ComputeVideoOrientation());
@@ -582,7 +588,7 @@
 
   // Rotate screen 180 degrees to the opposite landscape (without passing via a
   // portrait orientation).
-  RotateTo(kWebScreenOrientationLandscapePrimary);
+  RotateTo(mojom::blink::ScreenOrientation::kLandscapePrimary);
 
   // Should not enter fullscreen since this is a 180 degree orientation.
   EXPECT_FALSE(GetVideo().IsFullscreen());
@@ -590,7 +596,8 @@
 
 TEST_F(MediaControlsRotateToFullscreenDelegateTest, EnterFailSmall) {
   // Portrait screen, small landscape video.
-  InitScreenAndVideo(kWebScreenOrientationPortraitPrimary, gfx::Size(300, 199));
+  InitScreenAndVideo(mojom::blink::ScreenOrientation::kPortraitPrimary,
+                     gfx::Size(300, 199));
   EXPECT_EQ(SimpleOrientation::kPortrait, ObservedScreenOrientation());
   EXPECT_EQ(SimpleOrientation::kUnknown, ComputeVideoOrientation());
 
@@ -601,7 +608,7 @@
   EXPECT_TRUE(ObservedVisibility());
 
   // Rotate screen to landscape.
-  RotateTo(kWebScreenOrientationLandscapePrimary);
+  RotateTo(mojom::blink::ScreenOrientation::kLandscapePrimary);
 
   // Should not enter fullscreen since video is too small.
   EXPECT_FALSE(GetVideo().IsFullscreen());
@@ -610,7 +617,8 @@
 TEST_F(MediaControlsRotateToFullscreenDelegateTest,
        EnterFailDocumentFullscreen) {
   // Portrait screen, landscape video.
-  InitScreenAndVideo(kWebScreenOrientationPortraitPrimary, gfx::Size(640, 480));
+  InitScreenAndVideo(mojom::blink::ScreenOrientation::kPortraitPrimary,
+                     gfx::Size(640, 480));
   EXPECT_EQ(SimpleOrientation::kPortrait, ObservedScreenOrientation());
   EXPECT_EQ(SimpleOrientation::kLandscape, ComputeVideoOrientation());
 
@@ -629,7 +637,7 @@
   EXPECT_TRUE(ObservedVisibility());
 
   // Rotate screen to landscape.
-  RotateTo(kWebScreenOrientationLandscapePrimary);
+  RotateTo(mojom::blink::ScreenOrientation::kLandscapePrimary);
 
   // Should not enter fullscreen on video, since document is already fullscreen.
   EXPECT_TRUE(Fullscreen::IsFullscreenElement(*GetDocument().body()));
@@ -639,7 +647,7 @@
 TEST_F(MediaControlsRotateToFullscreenDelegateTest,
        ExitSuccessLandscapeFullscreenToPortraitInline) {
   // Landscape screen, landscape video.
-  InitScreenAndVideo(kWebScreenOrientationLandscapePrimary,
+  InitScreenAndVideo(mojom::blink::ScreenOrientation::kLandscapePrimary,
                      gfx::Size(640, 480));
   EXPECT_EQ(SimpleOrientation::kLandscape, ObservedScreenOrientation());
   EXPECT_EQ(SimpleOrientation::kLandscape, ComputeVideoOrientation());
@@ -659,7 +667,7 @@
 
   // Rotate screen to portrait. This relies on the screen orientation not being
   // locked by MediaControlsOrientationLockDelegate (which has its own tests).
-  RotateTo(kWebScreenOrientationPortraitPrimary);
+  RotateTo(mojom::blink::ScreenOrientation::kPortraitPrimary);
 
   // Should exit fullscreen.
   EXPECT_FALSE(GetVideo().IsFullscreen());
@@ -668,7 +676,8 @@
 TEST_F(MediaControlsRotateToFullscreenDelegateTest,
        ExitSuccessPortraitFullscreenToLandscapeInline) {
   // Portrait screen, portrait video.
-  InitScreenAndVideo(kWebScreenOrientationPortraitPrimary, gfx::Size(480, 640));
+  InitScreenAndVideo(mojom::blink::ScreenOrientation::kPortraitPrimary,
+                     gfx::Size(480, 640));
   EXPECT_EQ(SimpleOrientation::kPortrait, ObservedScreenOrientation());
   EXPECT_EQ(SimpleOrientation::kPortrait, ComputeVideoOrientation());
 
@@ -687,7 +696,7 @@
 
   // Rotate screen to landscape. This relies on the screen orientation not being
   // locked by MediaControlsOrientationLockDelegate (which has its own tests).
-  RotateTo(kWebScreenOrientationLandscapePrimary);
+  RotateTo(mojom::blink::ScreenOrientation::kLandscapePrimary);
 
   // Should exit fullscreen.
   EXPECT_FALSE(GetVideo().IsFullscreen());
@@ -696,7 +705,7 @@
 TEST_F(MediaControlsRotateToFullscreenDelegateTest,
        ExitFailDocumentFullscreen) {
   // Landscape screen, landscape video.
-  InitScreenAndVideo(kWebScreenOrientationLandscapePrimary,
+  InitScreenAndVideo(mojom::blink::ScreenOrientation::kLandscapePrimary,
                      gfx::Size(640, 480));
   EXPECT_EQ(SimpleOrientation::kLandscape, ObservedScreenOrientation());
   EXPECT_EQ(SimpleOrientation::kLandscape, ComputeVideoOrientation());
@@ -714,7 +723,7 @@
   EXPECT_FALSE(ObservedVisibility());
 
   // Rotate screen to portrait.
-  RotateTo(kWebScreenOrientationPortraitPrimary);
+  RotateTo(mojom::blink::ScreenOrientation::kPortraitPrimary);
 
   // Should not exit fullscreen, since video was not the fullscreen element.
   EXPECT_TRUE(Fullscreen::IsFullscreenElement(*GetDocument().body()));
@@ -724,7 +733,8 @@
 TEST_F(MediaControlsRotateToFullscreenDelegateTest,
        EnterFailControlsListNoFullscreen) {
   // Portrait screen, landscape video.
-  InitScreenAndVideo(kWebScreenOrientationPortraitPrimary, gfx::Size(640, 480));
+  InitScreenAndVideo(mojom::blink::ScreenOrientation::kPortraitPrimary,
+                     gfx::Size(640, 480));
   EXPECT_EQ(SimpleOrientation::kPortrait, ObservedScreenOrientation());
   EXPECT_EQ(SimpleOrientation::kLandscape, ComputeVideoOrientation());
 
@@ -738,7 +748,7 @@
   EXPECT_TRUE(ObservedVisibility());
 
   // Rotate screen to landscape.
-  RotateTo(kWebScreenOrientationLandscapePrimary);
+  RotateTo(mojom::blink::ScreenOrientation::kLandscapePrimary);
 
   // Should not enter fullscreen when controlsList=nofullscreen.
   EXPECT_FALSE(GetVideo().IsFullscreen());
@@ -747,7 +757,8 @@
 TEST_F(MediaControlsRotateToFullscreenDelegateTest,
        EnterSuccessControlsListNoDownload) {
   // Portrait screen, landscape video.
-  InitScreenAndVideo(kWebScreenOrientationPortraitPrimary, gfx::Size(640, 480));
+  InitScreenAndVideo(mojom::blink::ScreenOrientation::kPortraitPrimary,
+                     gfx::Size(640, 480));
   EXPECT_EQ(SimpleOrientation::kPortrait, ObservedScreenOrientation());
   EXPECT_EQ(SimpleOrientation::kLandscape, ComputeVideoOrientation());
 
@@ -761,7 +772,7 @@
   EXPECT_TRUE(ObservedVisibility());
 
   // Rotate screen to landscape.
-  RotateTo(kWebScreenOrientationLandscapePrimary);
+  RotateTo(mojom::blink::ScreenOrientation::kLandscapePrimary);
 
   // Should enter fullscreen when controlsList is not set to nofullscreen.
   EXPECT_TRUE(GetVideo().IsFullscreen());
diff --git a/third_party/blink/renderer/modules/mediastream/apply_constraints_processor.cc b/third_party/blink/renderer/modules/mediastream/apply_constraints_processor.cc
index 45674f5..93d8c22c 100644
--- a/third_party/blink/renderer/modules/mediastream/apply_constraints_processor.cc
+++ b/third_party/blink/renderer/modules/mediastream/apply_constraints_processor.cc
@@ -259,7 +259,7 @@
   // values. However, initialize |settings| with the default values as a
   // fallback in case GetSettings returns nothing and leaves |settings|
   // unmodified.
-  blink::WebMediaStreamTrack::Settings settings;
+  MediaStreamTrackPlatform::Settings settings;
   settings.width = blink::MediaStreamVideoSource::kDefaultWidth;
   settings.height = blink::MediaStreamVideoSource::kDefaultHeight;
   settings.frame_rate = blink::MediaStreamVideoSource::kDefaultFrameRate;
diff --git a/third_party/blink/renderer/modules/mediastream/input_device_info.cc b/third_party/blink/renderer/modules/mediastream/input_device_info.cc
index 7a744e5..40c6cf6d 100644
--- a/third_party/blink/renderer/modules/mediastream/input_device_info.cc
+++ b/third_party/blink/renderer/modules/mediastream/input_device_info.cc
@@ -13,33 +13,13 @@
 #include "third_party/blink/renderer/bindings/modules/v8/v8_double_range.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_long_range.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_media_track_capabilities.h"
+#include "third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_video_device.h"
 #include "third_party/blink/renderer/platform/mediastream/media_stream_audio_processor_options.h"
 #include "third_party/blink/renderer/platform/mediastream/media_stream_audio_source.h"
 #include "third_party/webrtc/modules/audio_processing/include/audio_processing.h"
 
 namespace blink {
 
-namespace {
-
-// TODO(c.padhi): Merge this method with ToWebFacingMode() in
-// media_stream_constraints_util_video_device.h, see https://crbug.com/821668.
-WebMediaStreamTrack::FacingMode ToWebFacingMode(
-    media::VideoFacingMode facing_mode) {
-  switch (facing_mode) {
-    case media::MEDIA_VIDEO_FACING_NONE:
-      return WebMediaStreamTrack::FacingMode::kNone;
-    case media::MEDIA_VIDEO_FACING_USER:
-      return WebMediaStreamTrack::FacingMode::kUser;
-    case media::MEDIA_VIDEO_FACING_ENVIRONMENT:
-      return WebMediaStreamTrack::FacingMode::kEnvironment;
-    default:
-      NOTREACHED();
-      return WebMediaStreamTrack::FacingMode::kNone;
-  }
-}
-
-}  // namespace
-
 InputDeviceInfo::InputDeviceInfo(const String& device_id,
                                  const String& label,
                                  const String& group_id,
@@ -53,7 +33,7 @@
   // ComputeCapabilitiesForVideoSource() in media_stream_constraints_util.h, see
   // https://crbug.com/821668.
   platform_capabilities_.facing_mode =
-      ToWebFacingMode(video_input_capabilities->facing_mode);
+      ToPlatformFacingMode(video_input_capabilities->facing_mode);
   if (!video_input_capabilities->formats.IsEmpty()) {
     int max_width = 1;
     int max_height = 1;
@@ -166,19 +146,19 @@
     }
     Vector<String> facing_mode;
     switch (platform_capabilities_.facing_mode) {
-      case WebMediaStreamTrack::FacingMode::kUser:
+      case MediaStreamTrackPlatform::FacingMode::kUser:
         facing_mode.push_back("user");
         break;
-      case WebMediaStreamTrack::FacingMode::kEnvironment:
+      case MediaStreamTrackPlatform::FacingMode::kEnvironment:
         facing_mode.push_back("environment");
         break;
-      case WebMediaStreamTrack::FacingMode::kLeft:
+      case MediaStreamTrackPlatform::FacingMode::kLeft:
         facing_mode.push_back("left");
         break;
-      case WebMediaStreamTrack::FacingMode::kRight:
+      case MediaStreamTrackPlatform::FacingMode::kRight:
         facing_mode.push_back("right");
         break;
-      case WebMediaStreamTrack::FacingMode::kNone:
+      case MediaStreamTrackPlatform::FacingMode::kNone:
         break;
     }
     capabilities->setFacingMode(facing_mode);
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util.cc b/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util.cc
index a8d53ea0..7cc4754 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util.cc
@@ -291,7 +291,7 @@
   MediaStreamSource::Capabilities capabilities;
   capabilities.device_id = std::move(device_id);
   if (is_device_capture) {
-    capabilities.facing_mode = ToWebFacingMode(facing_mode);
+    capabilities.facing_mode = ToPlatformFacingMode(facing_mode);
     if (group_id)
       capabilities.group_id = WebString::FromUTF8(*group_id);
   }
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_sets.cc b/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_sets.cc
index 522645d..88e92c1 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_sets.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_sets.cc
@@ -6,6 +6,7 @@
 
 #include <cmath>
 
+#include "third_party/blink/public/platform/web_string.h"
 #include "third_party/blink/renderer/modules/mediastream/media_stream_constraints_util.h"
 #include "third_party/blink/renderer/platform/mediastream/media_constraints.h"
 
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_video_device.cc b/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_video_device.cc
index 37708d9..e327e97 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_video_device.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_video_device.cc
@@ -691,17 +691,17 @@
              : WebString::FromASCII(kVideoKindColor);
 }
 
-WebMediaStreamTrack::FacingMode ToWebFacingMode(
+MediaStreamTrackPlatform::FacingMode ToPlatformFacingMode(
     media::VideoFacingMode video_facing) {
   switch (video_facing) {
     case media::MEDIA_VIDEO_FACING_NONE:
-      return WebMediaStreamTrack::FacingMode::kNone;
+      return MediaStreamTrackPlatform::FacingMode::kNone;
     case media::MEDIA_VIDEO_FACING_USER:
-      return WebMediaStreamTrack::FacingMode::kUser;
+      return MediaStreamTrackPlatform::FacingMode::kUser;
     case media::MEDIA_VIDEO_FACING_ENVIRONMENT:
-      return WebMediaStreamTrack::FacingMode::kEnvironment;
+      return MediaStreamTrackPlatform::FacingMode::kEnvironment;
     default:
-      return WebMediaStreamTrack::FacingMode::kNone;
+      return MediaStreamTrackPlatform::FacingMode::kNone;
   }
 }
 
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_video_device.h b/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_video_device.h
index 9ab7622..ecb2aa1 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_video_device.h
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_video_device.h
@@ -24,7 +24,7 @@
 MODULES_EXPORT WebString
 GetVideoKindForFormat(const media::VideoCaptureFormat& format);
 
-MODULES_EXPORT WebMediaStreamTrack::FacingMode ToWebFacingMode(
+MODULES_EXPORT MediaStreamTrackPlatform::FacingMode ToPlatformFacingMode(
     media::VideoFacingMode video_facing);
 
 // This is a temporary struct to bridge blink and content mojo types.
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_video_device_test.cc b/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_video_device_test.cc
index ae5108b0..1acb3c4 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_video_device_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_video_device_test.cc
@@ -10,6 +10,7 @@
 #include "base/optional.h"
 #include "media/base/limits.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/platform/web_string.h"
 #include "third_party/blink/public/web/modules/mediastream/media_stream_video_source.h"
 #include "third_party/blink/renderer/modules/mediastream/mock_constraint_factory.h"
 #include "third_party/blink/renderer/platform/mediastream/media_constraints.h"
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_track.cc b/third_party/blink/renderer/modules/mediastream/media_stream_track.cc
index 242fa11..c9add9fd 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_track.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_track.cc
@@ -157,8 +157,7 @@
 std::unique_ptr<WebAudioSourceProvider>
 CreateWebAudioSourceFromMediaStreamTrack(MediaStreamComponent* component,
                                          int context_sample_rate) {
-  WebPlatformMediaStreamTrack* media_stream_track =
-      component->GetPlatformTrack();
+  MediaStreamTrackPlatform* media_stream_track = component->GetPlatformTrack();
   if (!media_stream_track) {
     DLOG(ERROR) << "Native track missing for webaudio source.";
     return nullptr;
@@ -193,7 +192,7 @@
 
 void DidSetMediaStreamTrackEnabled(MediaStreamComponent* component) {
   auto* native_track =
-      WebPlatformMediaStreamTrack::GetTrack(WebMediaStreamTrack(component));
+      MediaStreamTrackPlatform::GetTrack(WebMediaStreamTrack(component));
   if (native_track)
     native_track->SetEnabled(component->Enabled());
 }
@@ -504,16 +503,16 @@
     }
     Vector<String> facing_mode;
     switch (platform_capabilities.facing_mode) {
-      case WebMediaStreamTrack::FacingMode::kUser:
+      case MediaStreamTrackPlatform::FacingMode::kUser:
         facing_mode.push_back("user");
         break;
-      case WebMediaStreamTrack::FacingMode::kEnvironment:
+      case MediaStreamTrackPlatform::FacingMode::kEnvironment:
         facing_mode.push_back("environment");
         break;
-      case WebMediaStreamTrack::FacingMode::kLeft:
+      case MediaStreamTrackPlatform::FacingMode::kLeft:
         facing_mode.push_back("left");
         break;
-      case WebMediaStreamTrack::FacingMode::kRight:
+      case MediaStreamTrackPlatform::FacingMode::kRight:
         facing_mode.push_back("right");
         break;
       default:
@@ -548,7 +547,7 @@
 
 MediaTrackSettings* MediaStreamTrack::getSettings() const {
   MediaTrackSettings* settings = MediaTrackSettings::Create();
-  WebMediaStreamTrack::Settings platform_settings;
+  MediaStreamTrackPlatform::Settings platform_settings;
   component_->GetSettings(platform_settings);
   if (platform_settings.HasFrameRate())
     settings->setFrameRate(platform_settings.frame_rate);
@@ -568,16 +567,16 @@
     settings->setGroupId(platform_settings.group_id);
   if (platform_settings.HasFacingMode()) {
     switch (platform_settings.facing_mode) {
-      case WebMediaStreamTrack::FacingMode::kUser:
+      case MediaStreamTrackPlatform::FacingMode::kUser:
         settings->setFacingMode("user");
         break;
-      case WebMediaStreamTrack::FacingMode::kEnvironment:
+      case MediaStreamTrackPlatform::FacingMode::kEnvironment:
         settings->setFacingMode("environment");
         break;
-      case WebMediaStreamTrack::FacingMode::kLeft:
+      case MediaStreamTrackPlatform::FacingMode::kLeft:
         settings->setFacingMode("left");
         break;
-      case WebMediaStreamTrack::FacingMode::kRight:
+      case MediaStreamTrackPlatform::FacingMode::kRight:
         settings->setFacingMode("right");
         break;
       default:
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source_test.cc b/third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source_test.cc
index 31723ea..a09e888df 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source_test.cc
@@ -354,8 +354,8 @@
       .WillOnce(InvokeWithoutArgs(
           this, &MediaStreamVideoCapturerSourceTest::SetStopCaptureFlag));
   EXPECT_CALL(*this, MockNotification());
-  WebPlatformMediaStreamTrack* track =
-      WebPlatformMediaStreamTrack::GetTrack(web_track);
+  MediaStreamTrackPlatform* track =
+      MediaStreamTrackPlatform::GetTrack(web_track);
   track->StopAndNotify(
       WTF::Bind(&MediaStreamVideoCapturerSourceTest::MockNotification,
                 base::Unretained(this)));
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_video_source_test.cc b/third_party/blink/renderer/modules/mediastream/media_stream_video_source_test.cc
index a420708..e63e9ea 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_video_source_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_video_source_test.cc
@@ -451,7 +451,7 @@
 
   MediaStreamVideoTrack* native_track =
       MediaStreamVideoTrack::GetVideoTrack(track);
-  WebMediaStreamTrack::Settings settings;
+  MediaStreamTrackPlatform::Settings settings;
   native_track->GetSettings(settings);
   EXPECT_EQ(settings.width, 640);
   EXPECT_EQ(settings.height, 480);
@@ -482,7 +482,7 @@
 
   MediaStreamVideoTrack* native_track =
       MediaStreamVideoTrack::GetVideoTrack(track);
-  WebMediaStreamTrack::Settings settings;
+  MediaStreamTrackPlatform::Settings settings;
   native_track->GetSettings(settings);
   EXPECT_EQ(settings.width, 640);
   EXPECT_EQ(settings.height, 480);
@@ -497,7 +497,7 @@
 
   source()->ReconfigureTrack(
       native_track, VideoTrackAdapterSettings(gfx::Size(630, 470), 30.0));
-  WebMediaStreamTrack::Settings stopped_settings;
+  MediaStreamTrackPlatform::Settings stopped_settings;
   native_track->GetSettings(stopped_settings);
   EXPECT_EQ(stopped_settings.width, -1);
   EXPECT_EQ(stopped_settings.height, -1);
@@ -649,8 +649,8 @@
             WebMediaStreamSource::kReadyStateLive);
 
   EXPECT_CALL(*this, MockNotification());
-  WebPlatformMediaStreamTrack* track =
-      WebPlatformMediaStreamTrack::GetTrack(web_track);
+  MediaStreamTrackPlatform* track =
+      MediaStreamTrackPlatform::GetTrack(web_track);
   track->StopAndNotify(WTF::Bind(&MediaStreamVideoSourceTest::MockNotification,
                                  base::Unretained(this)));
   EXPECT_EQ(web_track.Source().GetReadyState(),
@@ -667,8 +667,8 @@
             WebMediaStreamSource::kReadyStateLive);
 
   EXPECT_CALL(*this, MockNotification());
-  WebPlatformMediaStreamTrack* track =
-      WebPlatformMediaStreamTrack::GetTrack(web_track);
+  MediaStreamTrackPlatform* track =
+      MediaStreamTrackPlatform::GetTrack(web_track);
   track->StopAndNotify(WTF::Bind(&MediaStreamVideoSourceTest::MockNotification,
                                  base::Unretained(this)));
   EXPECT_EQ(web_track.Source().GetReadyState(),
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_video_track.cc b/third_party/blink/renderer/modules/mediastream/media_stream_video_track.cc
index d7e7576..61b262a 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_video_track.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_video_track.cc
@@ -382,7 +382,7 @@
     MediaStreamVideoSource* source,
     MediaStreamVideoSource::ConstraintsOnceCallback callback,
     bool enabled)
-    : WebPlatformMediaStreamTrack(true),
+    : MediaStreamTrackPlatform(true),
       adapter_settings_(std::make_unique<VideoTrackAdapterSettings>(
           VideoTrackAdapterSettings())),
       is_screencast_(false),
@@ -419,7 +419,7 @@
     bool pan_tilt_zoom_allowed,
     MediaStreamVideoSource::ConstraintsOnceCallback callback,
     bool enabled)
-    : WebPlatformMediaStreamTrack(true),
+    : MediaStreamTrackPlatform(true),
       adapter_settings_(
           std::make_unique<VideoTrackAdapterSettings>(adapter_settings)),
       noise_reduction_(noise_reduction),
@@ -452,21 +452,21 @@
 
 MediaStreamVideoTrack::~MediaStreamVideoTrack() {
   DCHECK_CALLED_ON_VALID_THREAD(main_render_thread_checker_);
-  DCHECK(sinks_.empty());
-  DCHECK(encoded_sinks_.empty());
+  DCHECK(sinks_.IsEmpty());
+  DCHECK(encoded_sinks_.IsEmpty());
   Stop();
   DVLOG(3) << "~MediaStreamVideoTrack()";
 }
 
-static void AddSinkInternal(std::vector<WebMediaStreamSink*>* sinks,
+static void AddSinkInternal(Vector<WebMediaStreamSink*>* sinks,
                             WebMediaStreamSink* sink) {
   DCHECK(!base::Contains(*sinks, sink));
   sinks->push_back(sink);
 }
 
-static void RemoveSinkInternal(std::vector<WebMediaStreamSink*>* sinks,
+static void RemoveSinkInternal(Vector<WebMediaStreamSink*>* sinks,
                                WebMediaStreamSink* sink) {
-  auto it = std::find(sinks->begin(), sinks->end(), sink);
+  auto** it = std::find(sinks->begin(), sinks->end(), sink);
   DCHECK(it != sinks->end());
   sinks->erase(it);
 }
@@ -522,7 +522,7 @@
   DCHECK_CALLED_ON_VALID_THREAD(main_render_thread_checker_);
   if (!source_)
     return;
-  bool has_consumers = !sinks_.empty() || !encoded_sinks_.empty();
+  bool has_consumers = !sinks_.IsEmpty() || !encoded_sinks_.IsEmpty();
   source_->UpdateHasConsumers(this, has_consumers);
 }
 
@@ -533,7 +533,7 @@
   // stream undecodable.
   bool maybe_await_key_frame = false;
   if (enabled && source_ && source_->SupportsEncodedOutput() &&
-      !encoded_sinks_.empty()) {
+      !encoded_sinks_.IsEmpty()) {
     source_->RequestRefreshFrame();
     maybe_await_key_frame = true;
   }
@@ -570,7 +570,7 @@
 }
 
 void MediaStreamVideoTrack::GetSettings(
-    WebMediaStreamTrack::Settings& settings) {
+    MediaStreamTrackPlatform::Settings& settings) {
   DCHECK_CALLED_ON_VALID_THREAD(main_render_thread_checker_);
   if (!source_)
     return;
@@ -599,7 +599,7 @@
       settings.frame_rate = *computed_frame_rate_;
   }
 
-  settings.facing_mode = ToWebFacingMode(source_->device().video_facing);
+  settings.facing_mode = ToPlatformFacingMode(source_->device().video_facing);
   settings.resize_mode = WebString::FromASCII(std::string(
       adapter_settings().target_size() ? WebMediaStreamTrack::kResizeModeRescale
                                        : WebMediaStreamTrack::kResizeModeNone));
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_video_track.h b/third_party/blink/renderer/modules/mediastream/media_stream_video_track.h
index ba2f86ce..619a78c 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_video_track.h
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_video_track.h
@@ -6,7 +6,6 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASTREAM_MEDIA_STREAM_VIDEO_TRACK_H_
 
 #include <memory>
-#include <vector>
 
 #include "base/compiler_specific.h"
 #include "base/gtest_prod_util.h"
@@ -16,20 +15,20 @@
 #include "third_party/blink/public/platform/modules/mediastream/secure_display_link_tracker.h"
 #include "third_party/blink/public/platform/modules/mediastream/web_media_stream_sink.h"
 #include "third_party/blink/public/platform/modules/mediastream/web_media_stream_track.h"
-#include "third_party/blink/public/platform/modules/mediastream/web_platform_media_stream_track.h"
 #include "third_party/blink/public/web/modules/mediastream/encoded_video_frame.h"
 #include "third_party/blink/public/web/modules/mediastream/media_stream_video_source.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
+#include "third_party/blink/renderer/platform/mediastream/media_stream_track_platform.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace blink {
 
 class VideoTrackAdapterSettings;
 
 // MediaStreamVideoTrack is a video-specific representation of a
-// WebPlatformMediaStreamTrack. It is owned by a MediaStreamComponent
+// MediaStreamTrackPlatform. It is owned by a MediaStreamComponent
 // and can be retrieved using MediaStreamComponent::GetPlatformTrack().
-class MODULES_EXPORT MediaStreamVideoTrack
-    : public WebPlatformMediaStreamTrack {
+class MODULES_EXPORT MediaStreamVideoTrack : public MediaStreamTrackPlatform {
  public:
   // Help method to create a WebMediaStreamTrack and a
   // MediaStreamVideoTrack instance. The MediaStreamVideoTrack object is owned
@@ -82,7 +81,7 @@
   void SetContentHint(
       WebMediaStreamTrack::ContentHintType content_hint) override;
   void StopAndNotify(base::OnceClosure callback) override;
-  void GetSettings(WebMediaStreamTrack::Settings& settings) override;
+  void GetSettings(MediaStreamTrackPlatform::Settings& settings) override;
 
   // Add |sink| to receive state changes on the main render thread and video
   // frames in the |callback| method on the IO-thread.
@@ -170,8 +169,8 @@
   // or data flow changes are being called on the main thread.
   THREAD_CHECKER(main_render_thread_checker_);
 
-  std::vector<WebMediaStreamSink*> sinks_;
-  std::vector<WebMediaStreamSink*> encoded_sinks_;
+  Vector<WebMediaStreamSink*> sinks_;
+  Vector<WebMediaStreamSink*> encoded_sinks_;
 
   // |FrameDeliverer| is an internal helper object used for delivering video
   // frames on the IO-thread using callbacks to all registered tracks.
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_video_track_test.cc b/third_party/blink/renderer/modules/mediastream/media_stream_video_track_test.cc
index e30f739..3564e09 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_video_track_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_video_track_test.cc
@@ -264,7 +264,7 @@
   sink.ConnectEncodedToTrack(track);
   video_track->SetEnabled(true);
   video_track->SetEnabled(false);
-  WebMediaStreamTrack::Settings settings;
+  MediaStreamTrackPlatform::Settings settings;
   video_track->GetSettings(settings);
   sink.DisconnectFromTrack();
   sink.DisconnectEncodedFromTrack();
@@ -337,13 +337,13 @@
   WebMediaStreamTrack track = CreateTrack();
   MediaStreamVideoTrack* const native_track =
       MediaStreamVideoTrack::GetVideoTrack(track);
-  WebMediaStreamTrack::Settings settings;
+  MediaStreamTrackPlatform::Settings settings;
   native_track->GetSettings(settings);
   // These values come straight from the mock video track implementation.
   EXPECT_EQ(640, settings.width);
   EXPECT_EQ(480, settings.height);
   EXPECT_EQ(30.0, settings.frame_rate);
-  EXPECT_EQ(WebMediaStreamTrack::FacingMode::kNone, settings.facing_mode);
+  EXPECT_EQ(MediaStreamTrackPlatform::FacingMode::kNone, settings.facing_mode);
 }
 
 TEST_F(MediaStreamVideoTrackTest, GetSettingsWithAdjustment) {
@@ -357,12 +357,12 @@
   WebMediaStreamTrack track = CreateTrackWithSettings(adapter_settings);
   MediaStreamVideoTrack* const native_track =
       MediaStreamVideoTrack::GetVideoTrack(track);
-  WebMediaStreamTrack::Settings settings;
+  MediaStreamTrackPlatform::Settings settings;
   native_track->GetSettings(settings);
   EXPECT_EQ(kAdjustedWidth, settings.width);
   EXPECT_EQ(kAdjustedHeight, settings.height);
   EXPECT_EQ(kAdjustedFrameRate, settings.frame_rate);
-  EXPECT_EQ(WebMediaStreamTrack::FacingMode::kNone, settings.facing_mode);
+  EXPECT_EQ(MediaStreamTrackPlatform::FacingMode::kNone, settings.facing_mode);
 }
 
 TEST_F(MediaStreamVideoTrackTest, GetSettingsStopped) {
@@ -371,12 +371,12 @@
   MediaStreamVideoTrack* const native_track =
       MediaStreamVideoTrack::GetVideoTrack(track);
   native_track->Stop();
-  WebMediaStreamTrack::Settings settings;
+  MediaStreamTrackPlatform::Settings settings;
   native_track->GetSettings(settings);
   EXPECT_EQ(-1, settings.width);
   EXPECT_EQ(-1, settings.height);
   EXPECT_EQ(-1, settings.frame_rate);
-  EXPECT_EQ(WebMediaStreamTrack::FacingMode::kNone, settings.facing_mode);
+  EXPECT_EQ(MediaStreamTrackPlatform::FacingMode::kNone, settings.facing_mode);
   EXPECT_TRUE(settings.device_id.IsNull());
 }
 
@@ -387,7 +387,7 @@
   sink.ConnectToTrack(track);
   MediaStreamVideoTrack* const native_track =
       MediaStreamVideoTrack::GetVideoTrack(track);
-  WebMediaStreamTrack::Settings settings;
+  MediaStreamTrackPlatform::Settings settings;
 
   auto frame1 = media::VideoFrame::CreateBlackFrame(gfx::Size(600, 400));
   DeliverVideoFrameAndWaitForRenderer(std::move(frame1), &sink);
diff --git a/third_party/blink/renderer/modules/mediastream/user_media_client.cc b/third_party/blink/renderer/modules/mediastream/user_media_client.cc
index 5f38632..6f61c97 100644
--- a/third_party/blink/renderer/modules/mediastream/user_media_client.cc
+++ b/third_party/blink/renderer/modules/mediastream/user_media_client.cc
@@ -213,7 +213,7 @@
                   WrapWeakPersistent(this)));
   } else {
     DCHECK(current_request->IsStopTrack());
-    WebPlatformMediaStreamTrack* track = WebPlatformMediaStreamTrack::GetTrack(
+    MediaStreamTrackPlatform* track = MediaStreamTrackPlatform::GetTrack(
         WebMediaStreamTrack(current_request->track_to_stop()));
     if (track) {
       track->StopAndNotify(WTF::Bind(&UserMediaClient::CurrentRequestCompleted,
diff --git a/third_party/blink/renderer/modules/mediastream/user_media_client_test.cc b/third_party/blink/renderer/modules/mediastream/user_media_client_test.cc
index 534fb83..3c84e73c 100644
--- a/third_party/blink/renderer/modules/mediastream/user_media_client_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/user_media_client_test.cc
@@ -23,7 +23,6 @@
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom-blink.h"
 #include "third_party/blink/public/platform/modules/mediastream/web_media_stream_source.h"
 #include "third_party/blink/public/platform/modules/mediastream/web_media_stream_track.h"
-#include "third_party/blink/public/platform/modules/mediastream/web_platform_media_stream_track.h"
 #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
 #include "third_party/blink/public/platform/web_screen_info.h"
 #include "third_party/blink/public/platform/web_string.h"
@@ -44,6 +43,7 @@
 #include "third_party/blink/renderer/platform/mediastream/media_stream_audio_track.h"
 #include "third_party/blink/renderer/platform/mediastream/media_stream_component.h"
 #include "third_party/blink/renderer/platform/mediastream/media_stream_descriptor.h"
+#include "third_party/blink/renderer/platform/mediastream/media_stream_track_platform.h"
 #include "third_party/blink/renderer/platform/testing/io_task_runner_testing_platform_support.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
@@ -133,7 +133,7 @@
       MediaStreamVideoTrack::GetVideoTrack(WebMediaStreamTrack(component));
   EXPECT_EQ(track->source(), source);
 
-  WebMediaStreamTrack::Settings settings;
+  MediaStreamTrackPlatform::Settings settings;
   track->GetSettings(settings);
   EXPECT_EQ(settings.width, expected_track_width);
   EXPECT_EQ(settings.height, expected_track_height);
@@ -720,17 +720,15 @@
   MediaStreamDescriptor* mixed_desc = RequestLocalMediaStream();
 
   auto audio_components = mixed_desc->AudioComponents();
-  WebPlatformMediaStreamTrack* audio_track =
-      WebPlatformMediaStreamTrack::GetTrack(
-          WebMediaStreamTrack(audio_components[0]));
+  MediaStreamTrackPlatform* audio_track = MediaStreamTrackPlatform::GetTrack(
+      WebMediaStreamTrack(audio_components[0]));
   audio_track->Stop();
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1, mock_dispatcher_host_.stop_audio_device_counter());
 
   auto video_components = mixed_desc->VideoComponents();
-  WebPlatformMediaStreamTrack* video_track =
-      WebPlatformMediaStreamTrack::GetTrack(
-          WebMediaStreamTrack(video_components[0]));
+  MediaStreamTrackPlatform* video_track = MediaStreamTrackPlatform::GetTrack(
+      WebMediaStreamTrack(video_components[0]));
   video_track->Stop();
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1, mock_dispatcher_host_.stop_video_device_counter());
@@ -746,33 +744,29 @@
   MediaStreamDescriptor* desc2 = RequestLocalMediaStream();
 
   auto audio_components1 = desc1->AudioComponents();
-  WebPlatformMediaStreamTrack* audio_track1 =
-      WebPlatformMediaStreamTrack::GetTrack(
-          WebMediaStreamTrack(audio_components1[0]));
+  MediaStreamTrackPlatform* audio_track1 = MediaStreamTrackPlatform::GetTrack(
+      WebMediaStreamTrack(audio_components1[0]));
   audio_track1->Stop();
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(0, mock_dispatcher_host_.stop_audio_device_counter());
 
   auto audio_components2 = desc2->AudioComponents();
-  WebPlatformMediaStreamTrack* audio_track2 =
-      WebPlatformMediaStreamTrack::GetTrack(
-          WebMediaStreamTrack(audio_components2[0]));
+  MediaStreamTrackPlatform* audio_track2 = MediaStreamTrackPlatform::GetTrack(
+      WebMediaStreamTrack(audio_components2[0]));
   audio_track2->Stop();
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1, mock_dispatcher_host_.stop_audio_device_counter());
 
   auto video_components1 = desc1->VideoComponents();
-  WebPlatformMediaStreamTrack* video_track1 =
-      WebPlatformMediaStreamTrack::GetTrack(
-          WebMediaStreamTrack(video_components1[0]));
+  MediaStreamTrackPlatform* video_track1 = MediaStreamTrackPlatform::GetTrack(
+      WebMediaStreamTrack(video_components1[0]));
   video_track1->Stop();
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(0, mock_dispatcher_host_.stop_video_device_counter());
 
   auto video_components2 = desc2->VideoComponents();
-  WebPlatformMediaStreamTrack* video_track2 =
-      WebPlatformMediaStreamTrack::GetTrack(
-          WebMediaStreamTrack(video_components2[0]));
+  MediaStreamTrackPlatform* video_track2 = MediaStreamTrackPlatform::GetTrack(
+      WebMediaStreamTrack(video_components2[0]));
   video_track2->Stop();
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1, mock_dispatcher_host_.stop_video_device_counter());
@@ -882,17 +876,15 @@
   EXPECT_EQ(1, mock_dispatcher_host_.stop_video_device_counter());
 
   auto audio_components = mixed_desc->AudioComponents();
-  WebPlatformMediaStreamTrack* audio_track =
-      WebPlatformMediaStreamTrack::GetTrack(
-          WebMediaStreamTrack(audio_components[0]));
+  MediaStreamTrackPlatform* audio_track = MediaStreamTrackPlatform::GetTrack(
+      WebMediaStreamTrack(audio_components[0]));
   audio_track->Stop();
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1, mock_dispatcher_host_.stop_audio_device_counter());
 
   auto video_components = mixed_desc->VideoComponents();
-  WebPlatformMediaStreamTrack* video_track =
-      WebPlatformMediaStreamTrack::GetTrack(
-          WebMediaStreamTrack(video_components[0]));
+  MediaStreamTrackPlatform* video_track = MediaStreamTrackPlatform::GetTrack(
+      WebMediaStreamTrack(video_components[0]));
   video_track->Stop();
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1, mock_dispatcher_host_.stop_video_device_counter());
@@ -1261,7 +1253,7 @@
   // Try to use applyConstraints() to change the first track to 800x600@30Hz.
   // after stopping the second track. In this case, the source is left with a
   // single track and it supports reconfiguration to the requested mode.
-  blink::WebPlatformMediaStreamTrack::GetTrack(WebMediaStreamTrack(component2))
+  blink::MediaStreamTrackPlatform::GetTrack(WebMediaStreamTrack(component2))
       ->Stop();
   ApplyConstraintsVideoMode(component, 800, 600, 30.0);
   CheckVideoSourceAndTrack(source, 800, 600, 30.0, component, 800, 600, 30.0);
@@ -1322,14 +1314,14 @@
   CheckVideoSourceAndTrack(source, 1024, 768, 20.0, component, 1024, 768, 20.0);
 
   // Try to switch the source and track to 640x480 after stopping the track.
-  WebPlatformMediaStreamTrack* track =
-      WebPlatformMediaStreamTrack::GetTrack(WebMediaStreamTrack(component));
+  MediaStreamTrackPlatform* track =
+      MediaStreamTrackPlatform::GetTrack(WebMediaStreamTrack(component));
   track->Stop();
   EXPECT_EQ(component->Source()->GetReadyState(),
             MediaStreamSource::kReadyStateEnded);
   EXPECT_FALSE(source->IsRunning());
   {
-    WebMediaStreamTrack::Settings settings;
+    MediaStreamTrackPlatform::Settings settings;
     track->GetSettings(settings);
     EXPECT_EQ(settings.width, -1);
     EXPECT_EQ(settings.height, -1);
@@ -1341,7 +1333,7 @@
             MediaStreamSource::kReadyStateEnded);
   EXPECT_FALSE(source->IsRunning());
   {
-    WebMediaStreamTrack::Settings settings;
+    MediaStreamTrackPlatform::Settings settings;
     track->GetSettings(settings);
     EXPECT_EQ(settings.width, -1);
     EXPECT_EQ(settings.height, -1);
diff --git a/third_party/blink/renderer/modules/mediastream/user_media_processor.cc b/third_party/blink/renderer/modules/mediastream/user_media_processor.cc
index 3299ed2..3247d38 100644
--- a/third_party/blink/renderer/modules/mediastream/user_media_processor.cc
+++ b/third_party/blink/renderer/modules/mediastream/user_media_processor.cc
@@ -1473,15 +1473,15 @@
     GetUserMediaRequestFailed(result, constraint_name);
 
     for (auto web_track : request_info->descriptor()->AudioComponents()) {
-      WebPlatformMediaStreamTrack* track =
-          WebPlatformMediaStreamTrack::GetTrack(WebMediaStreamTrack(web_track));
+      MediaStreamTrackPlatform* track =
+          MediaStreamTrackPlatform::GetTrack(WebMediaStreamTrack(web_track));
       if (track)
         track->Stop();
     }
 
     for (auto web_track : request_info->descriptor()->VideoComponents()) {
-      WebPlatformMediaStreamTrack* track =
-          WebPlatformMediaStreamTrack::GetTrack(WebMediaStreamTrack(web_track));
+      MediaStreamTrackPlatform* track =
+          MediaStreamTrackPlatform::GetTrack(WebMediaStreamTrack(web_track));
       if (track)
         track->Stop();
     }
diff --git a/third_party/blink/renderer/modules/modules_idl_files.gni b/third_party/blink/renderer/modules/modules_idl_files.gni
index 84425b1..7544f66 100644
--- a/third_party/blink/renderer/modules/modules_idl_files.gni
+++ b/third_party/blink/renderer/modules/modules_idl_files.gni
@@ -250,9 +250,13 @@
 modules_static_dependency_idl_files =
     modules_dependency_idl_files + modules_testing_dependency_idl_files
 
-modules_generated_dependency_idl_files =
-    modules_core_global_constructors_generated_idl_files +
-    modules_global_constructors_generated_idl_files
+if (use_blink_v8_binding_new_idl_interface) {
+  modules_generated_dependency_idl_files = []
+} else {
+  modules_generated_dependency_idl_files =
+      modules_core_global_constructors_generated_idl_files +
+      modules_global_constructors_generated_idl_files
+}
 
 # 'modules_dependency_idl_files' is already used in Source/modules, so avoid
 # collision
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.cc b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.cc
index f7b4d64..c35ede7 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.cc
@@ -24,7 +24,6 @@
 #include "base/threading/thread_checker.h"
 #include "base/trace_event/trace_event.h"
 #include "media/base/media_switches.h"
-#include "third_party/blink/public/platform/modules/mediastream/web_platform_media_stream_track.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/web_string.h"
 #include "third_party/blink/public/platform/web_url.h"
@@ -38,6 +37,7 @@
 #include "third_party/blink/renderer/platform/heap/heap.h"
 #include "third_party/blink/renderer/platform/mediastream/media_constraints.h"
 #include "third_party/blink/renderer/platform/mediastream/media_stream_component.h"
+#include "third_party/blink/renderer/platform/mediastream/media_stream_track_platform.h"
 #include "third_party/blink/renderer/platform/mediastream/webrtc_uma_histograms.h"
 #include "third_party/blink/renderer/platform/peerconnection/rtc_answer_options_platform.h"
 #include "third_party/blink/renderer/platform/peerconnection/rtc_event_log_output_sink.h"
diff --git a/third_party/blink/renderer/modules/push_messaging/push_subscription.cc b/third_party/blink/renderer/modules/push_messaging/push_subscription.cc
index ea49f2d7..d67b744 100644
--- a/third_party/blink/renderer/modules/push_messaging/push_subscription.cc
+++ b/third_party/blink/renderer/modules/push_messaging/push_subscription.cc
@@ -67,7 +67,8 @@
     const WTF::Vector<uint8_t>& application_server_key,
     const WTF::Vector<unsigned char>& p256dh,
     const WTF::Vector<unsigned char>& auth,
-    ServiceWorkerRegistration* service_worker_registration)
+    ServiceWorkerRegistration* service_worker_registration,
+    const base::Optional<DOMTimeStamp> expiration_time)
     : endpoint_(endpoint),
       options_(MakeGarbageCollected<PushSubscriptionOptions>(
           user_visible_only,
@@ -76,7 +77,8 @@
                                      SafeCast<unsigned>(p256dh.size()))),
       auth_(
           DOMArrayBuffer::Create(auth.data(), SafeCast<unsigned>(auth.size()))),
-      service_worker_registration_(service_worker_registration) {}
+      service_worker_registration_(service_worker_registration),
+      expiration_time_(expiration_time) {}
 
 PushSubscription::~PushSubscription() = default;
 
@@ -84,7 +86,7 @@
   // This attribute reflects the time at which the subscription will expire,
   // which is not relevant to this implementation yet as subscription refreshes
   // are not supported.
-  return base::nullopt;
+  return expiration_time_;
 }
 
 DOMArrayBuffer* PushSubscription::getKey(const AtomicString& name) const {
@@ -113,7 +115,12 @@
 
   V8ObjectBuilder result(script_state);
   result.AddString("endpoint", endpoint());
-  result.AddNull("expirationTime");
+
+  if (expiration_time_) {
+    result.AddNumber("expirationTime", *expiration_time_);
+  } else {
+    result.AddNull("expirationTime");
+  }
 
   V8ObjectBuilder keys(script_state);
   keys.Add("p256dh", ToBase64URLWithoutPadding(p256dh_));
diff --git a/third_party/blink/renderer/modules/push_messaging/push_subscription.h b/third_party/blink/renderer/modules/push_messaging/push_subscription.h
index 116bf52..d4b77b00 100644
--- a/third_party/blink/renderer/modules/push_messaging/push_subscription.h
+++ b/third_party/blink/renderer/modules/push_messaging/push_subscription.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include "base/gtest_prod_util.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/optional.h"
 #include "third_party/blink/public/mojom/push_messaging/push_messaging.mojom-blink-forward.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
@@ -33,12 +34,14 @@
       mojom::blink::PushSubscriptionPtr subscription,
       ServiceWorkerRegistration* service_worker_registration);
 
-  PushSubscription(const KURL& endpoint,
-                   bool user_visible_only,
-                   const WTF::Vector<uint8_t>& application_server_key,
-                   const WTF::Vector<unsigned char>& p256dh,
-                   const WTF::Vector<unsigned char>& auth,
-                   ServiceWorkerRegistration* service_worker_registration);
+  PushSubscription(
+      const KURL& endpoint,
+      bool user_visible_only,
+      const WTF::Vector<uint8_t>& application_server_key,
+      const WTF::Vector<unsigned char>& p256dh,
+      const WTF::Vector<unsigned char>& auth,
+      ServiceWorkerRegistration* service_worker_registration,
+      const base::Optional<DOMTimeStamp> expiration_time = base::nullopt);
 
   ~PushSubscription() override;
 
@@ -66,6 +69,8 @@
   Member<DOMArrayBuffer> auth_;
 
   Member<ServiceWorkerRegistration> service_worker_registration_;
+
+  base::Optional<DOMTimeStamp> expiration_time_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/screen_orientation/lock_orientation_callback.h b/third_party/blink/renderer/modules/screen_orientation/lock_orientation_callback.h
index 44d4f26b..282497e 100644
--- a/third_party/blink/renderer/modules/screen_orientation/lock_orientation_callback.h
+++ b/third_party/blink/renderer/modules/screen_orientation/lock_orientation_callback.h
@@ -7,7 +7,6 @@
 
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
-#include "third_party/blink/public/common/screen_orientation/web_screen_orientation_type.h"
 #include "third_party/blink/renderer/modules/screen_orientation/web_lock_orientation_callback.h"
 #include "third_party/blink/renderer/platform/heap/persistent.h"
 
diff --git a/third_party/blink/renderer/modules/screen_orientation/screen_orientation.cc b/third_party/blink/renderer/modules/screen_orientation/screen_orientation.cc
index 43aaa3a..4befaa41 100644
--- a/third_party/blink/renderer/modules/screen_orientation/screen_orientation.cc
+++ b/third_party/blink/renderer/modules/screen_orientation/screen_orientation.cc
@@ -8,7 +8,6 @@
 
 #include "base/stl_util.h"
 #include "services/network/public/mojom/web_sandbox_flags.mojom-blink.h"
-#include "third_party/blink/public/common/screen_orientation/web_screen_orientation_type.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
@@ -19,15 +18,15 @@
 #include "third_party/blink/renderer/platform/heap/heap.h"
 #include "third_party/blink/renderer/platform/wtf/assertions.h"
 
-// This code assumes that WebScreenOrientationType values are included in
+// This code assumes that mojom::blink::ScreenOrientation values are included in
 // WebScreenOrientationLockType.
-STATIC_ASSERT_ENUM(blink::kWebScreenOrientationPortraitPrimary,
+STATIC_ASSERT_ENUM(blink::mojom::blink::ScreenOrientation::kPortraitPrimary,
                    blink::kWebScreenOrientationLockPortraitPrimary);
-STATIC_ASSERT_ENUM(blink::kWebScreenOrientationPortraitSecondary,
+STATIC_ASSERT_ENUM(blink::mojom::blink::ScreenOrientation::kPortraitSecondary,
                    blink::kWebScreenOrientationLockPortraitSecondary);
-STATIC_ASSERT_ENUM(blink::kWebScreenOrientationLandscapePrimary,
+STATIC_ASSERT_ENUM(blink::mojom::blink::ScreenOrientation::kLandscapePrimary,
                    blink::kWebScreenOrientationLockLandscapePrimary);
-STATIC_ASSERT_ENUM(blink::kWebScreenOrientationLandscapeSecondary,
+STATIC_ASSERT_ENUM(blink::mojom::blink::ScreenOrientation::kLandscapeSecondary,
                    blink::kWebScreenOrientationLockLandscapeSecondary);
 
 namespace blink {
@@ -66,7 +65,7 @@
 }
 
 const AtomicString& ScreenOrientation::OrientationTypeToString(
-    WebScreenOrientationType orientation) {
+    mojom::blink::ScreenOrientation orientation) {
   unsigned length = 0;
   ScreenOrientationInfo* orientation_map = OrientationsMap(length);
   for (unsigned i = 0; i < length; ++i) {
@@ -103,7 +102,7 @@
 
 ScreenOrientation::ScreenOrientation(LocalDOMWindow* window)
     : ExecutionContextClient(window),
-      type_(kWebScreenOrientationUndefined),
+      type_(mojom::blink::ScreenOrientation::kUndefined),
       angle_(0) {}
 
 ScreenOrientation::~ScreenOrientation() = default;
@@ -124,7 +123,7 @@
   return angle_;
 }
 
-void ScreenOrientation::SetType(WebScreenOrientationType type) {
+void ScreenOrientation::SetType(mojom::blink::ScreenOrientation type) {
   type_ = type;
 }
 
diff --git a/third_party/blink/renderer/modules/screen_orientation/screen_orientation.h b/third_party/blink/renderer/modules/screen_orientation/screen_orientation.h
index deb53f1..ad558c3a 100644
--- a/third_party/blink/renderer/modules/screen_orientation/screen_orientation.h
+++ b/third_party/blink/renderer/modules/screen_orientation/screen_orientation.h
@@ -5,7 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_SCREEN_ORIENTATION_SCREEN_ORIENTATION_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_SCREEN_ORIENTATION_SCREEN_ORIENTATION_H_
 
-#include "third_party/blink/public/common/screen_orientation/web_screen_orientation_type.h"
+#include "third_party/blink/public/mojom/widget/screen_orientation.mojom-blink.h"
 #include "third_party/blink/renderer/core/dom/events/event_target.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
@@ -41,7 +41,7 @@
   String type() const;
   uint16_t angle() const;
 
-  void SetType(WebScreenOrientationType);
+  void SetType(mojom::blink::ScreenOrientation);
   void SetAngle(uint16_t);
 
   ScriptPromise lock(ScriptState*,
@@ -52,14 +52,15 @@
   DEFINE_ATTRIBUTE_EVENT_LISTENER(change, kChange)
 
   // Helper being used by this class and LockOrientationCallback.
-  static const AtomicString& OrientationTypeToString(WebScreenOrientationType);
+  static const AtomicString& OrientationTypeToString(
+      mojom::blink::ScreenOrientation);
 
   void Trace(Visitor*) const override;
 
  private:
   ScreenOrientationController* Controller();
 
-  WebScreenOrientationType type_;
+  mojom::blink::ScreenOrientation type_;
   uint16_t angle_;
 };
 
diff --git a/third_party/blink/renderer/modules/screen_orientation/screen_orientation_controller.cc b/third_party/blink/renderer/modules/screen_orientation/screen_orientation_controller.cc
index 09eabe7..8715c9e 100644
--- a/third_party/blink/renderer/modules/screen_orientation/screen_orientation_controller.cc
+++ b/third_party/blink/renderer/modules/screen_orientation/screen_orientation_controller.cc
@@ -57,14 +57,14 @@
 
 // Compute the screen orientation using the orientation angle and the screen
 // width / height.
-WebScreenOrientationType ScreenOrientationController::ComputeOrientation(
+mojom::blink::ScreenOrientation ScreenOrientationController::ComputeOrientation(
     const gfx::Rect& rect,
     uint16_t rotation) {
   // Bypass orientation detection in web tests to get consistent results.
   // FIXME: The screen dimension should be fixed when running the web tests
   // to avoid such issues.
   if (WebTestSupport::IsRunningWebTest())
-    return kWebScreenOrientationPortraitPrimary;
+    return mojom::blink::ScreenOrientation::kPortraitPrimary;
 
   bool is_tall_display = rotation % 180 ? rect.height() < rect.width()
                                         : rect.height() > rect.width();
@@ -76,20 +76,24 @@
   // together fully determine the relationship.
   switch (rotation) {
     case 0:
-      return is_tall_display ? kWebScreenOrientationPortraitPrimary
-                             : kWebScreenOrientationLandscapePrimary;
+      return is_tall_display
+                 ? mojom::blink::ScreenOrientation::kPortraitPrimary
+                 : mojom::blink::ScreenOrientation::kLandscapePrimary;
     case 90:
-      return is_tall_display ? kWebScreenOrientationLandscapePrimary
-                             : kWebScreenOrientationPortraitSecondary;
+      return is_tall_display
+                 ? mojom::blink::ScreenOrientation::kLandscapePrimary
+                 : mojom::blink::ScreenOrientation::kPortraitSecondary;
     case 180:
-      return is_tall_display ? kWebScreenOrientationPortraitSecondary
-                             : kWebScreenOrientationLandscapeSecondary;
+      return is_tall_display
+                 ? mojom::blink::ScreenOrientation::kPortraitSecondary
+                 : mojom::blink::ScreenOrientation::kLandscapeSecondary;
     case 270:
-      return is_tall_display ? kWebScreenOrientationLandscapeSecondary
-                             : kWebScreenOrientationPortraitPrimary;
+      return is_tall_display
+                 ? mojom::blink::ScreenOrientation::kLandscapeSecondary
+                 : mojom::blink::ScreenOrientation::kPortraitPrimary;
     default:
       NOTREACHED();
-      return kWebScreenOrientationPortraitPrimary;
+      return mojom::blink::ScreenOrientation::kPortraitPrimary;
   }
 }
 
@@ -98,15 +102,16 @@
   DCHECK(GetPage());
   ChromeClient& chrome_client = GetPage()->GetChromeClient();
   WebScreenInfo screen_info = chrome_client.GetScreenInfo(*GetFrame());
-  WebScreenOrientationType orientation_type = screen_info.orientation_type;
-  if (orientation_type == kWebScreenOrientationUndefined) {
+  mojom::blink::ScreenOrientation orientation_type =
+      screen_info.orientation_type;
+  if (orientation_type == mojom::blink::ScreenOrientation::kUndefined) {
     // The embedder could not provide us with an orientation, deduce it
     // ourselves.
     orientation_type =
         ComputeOrientation(chrome_client.GetScreenInfo(*GetFrame()).rect,
                            screen_info.orientation_angle);
   }
-  DCHECK(orientation_type != kWebScreenOrientationUndefined);
+  DCHECK(orientation_type != mojom::blink::ScreenOrientation::kUndefined);
 
   orientation_->SetType(orientation_type);
   orientation_->SetAngle(screen_info.orientation_angle);
diff --git a/third_party/blink/renderer/modules/screen_orientation/screen_orientation_controller.h b/third_party/blink/renderer/modules/screen_orientation/screen_orientation_controller.h
index e54a0f5..4e5bb9d 100644
--- a/third_party/blink/renderer/modules/screen_orientation/screen_orientation_controller.h
+++ b/third_party/blink/renderer/modules/screen_orientation/screen_orientation_controller.h
@@ -10,7 +10,7 @@
 #include "base/macros.h"
 #include "services/device/public/mojom/screen_orientation.mojom-blink.h"
 #include "third_party/blink/public/common/screen_orientation/web_screen_orientation_lock_type.h"
-#include "third_party/blink/public/common/screen_orientation/web_screen_orientation_type.h"
+#include "third_party/blink/public/mojom/widget/screen_orientation.mojom-blink.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/page/page_visibility_observer.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
@@ -56,8 +56,8 @@
   friend class MediaControlsOrientationLockAndRotateToFullscreenDelegateTest;
   friend class ScreenOrientationControllerTest;
 
-  static WebScreenOrientationType ComputeOrientation(const gfx::Rect&,
-                                                     uint16_t);
+  static mojom::blink::ScreenOrientation ComputeOrientation(const gfx::Rect&,
+                                                            uint16_t);
 
   // Inherited from ExecutionContextLifecycleObserver and
   // PageVisibilityObserver.
diff --git a/third_party/blink/renderer/modules/screen_orientation/web_lock_orientation_callback.h b/third_party/blink/renderer/modules/screen_orientation/web_lock_orientation_callback.h
index 0c93aa8..49ecd09 100644
--- a/third_party/blink/renderer/modules/screen_orientation/web_lock_orientation_callback.h
+++ b/third_party/blink/renderer/modules/screen_orientation/web_lock_orientation_callback.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_SCREEN_ORIENTATION_WEB_LOCK_ORIENTATION_CALLBACK_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_SCREEN_ORIENTATION_WEB_LOCK_ORIENTATION_CALLBACK_H_
 
-#include "third_party/blink/public/common/screen_orientation/web_screen_orientation_type.h"
 #include "third_party/blink/renderer/modules/screen_orientation/web_lock_orientation_error.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/modules/webaudio/audio_buffer_source_node.cc b/third_party/blink/renderer/modules/webaudio/audio_buffer_source_node.cc
index 0e3c414..33da99b 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_buffer_source_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_buffer_source_node.cc
@@ -612,6 +612,14 @@
 
 void AudioBufferSourceHandler::HandleStoppableSourceNode() {
   DCHECK(Context()->IsAudioThread());
+
+  MutexTryLocker try_locker(process_lock_);
+  if (!try_locker.Locked()) {
+    // Can't get the lock, so just return.  It's ok to handle these at a later
+    // time; this was just a hint anyway so stopping them a bit later is ok.
+    return;
+  }
+
   // If the source node has been scheduled to stop, we can stop the node once
   // the current time reaches that value.  Usually,
   // AudioScheduledSourceHandler::UpdateSchedulingInfo handles stopped nodes,
diff --git a/third_party/blink/renderer/modules/webaudio/constant_source_node.cc b/third_party/blink/renderer/modules/webaudio/constant_source_node.cc
index b4c1070e..ae424d3 100644
--- a/third_party/blink/renderer/modules/webaudio/constant_source_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/constant_source_node.cc
@@ -108,6 +108,13 @@
 void ConstantSourceHandler::HandleStoppableSourceNode() {
   double now = Context()->currentTime();
 
+  MutexTryLocker try_locker(process_lock_);
+  if (!try_locker.Locked()) {
+    // Can't get the lock, so just return.  It's ok to handle these at a later
+    // time; this was just a hint anyway so stopping them a bit later is ok.
+    return;
+  }
+
   // If we know the end time, and the source was started and the current time is
   // definitely past the end time, we can stop this node.  (This handles the
   // case where the this source is not connected to the destination and we want
diff --git a/third_party/blink/renderer/modules/webaudio/oscillator_node.cc b/third_party/blink/renderer/modules/webaudio/oscillator_node.cc
index 0dcfc0d..952a0e4 100644
--- a/third_party/blink/renderer/modules/webaudio/oscillator_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/oscillator_node.cc
@@ -641,6 +641,13 @@
 void OscillatorHandler::HandleStoppableSourceNode() {
   double now = Context()->currentTime();
 
+  MutexTryLocker try_locker(process_lock_);
+  if (!try_locker.Locked()) {
+    // Can't get the lock, so just return.  It's ok to handle these at a later
+    // time; this was just a hint anyway so stopping them a bit later is ok.
+    return;
+  }
+
   // If we know the end time, and the source was started and the current time is
   // definitely past the end time, we can stop this node.  (This handles the
   // case where the this source is not connected to the destination and we want
diff --git a/third_party/blink/renderer/modules/webcodecs/decoder_selector.cc b/third_party/blink/renderer/modules/webcodecs/decoder_selector.cc
index a8ffe46..33ec055 100644
--- a/third_party/blink/renderer/modules/webcodecs/decoder_selector.cc
+++ b/third_party/blink/renderer/modules/webcodecs/decoder_selector.cc
@@ -30,10 +30,6 @@
   ~NullDemuxerStream() override = default;
 
   void Read(ReadCB read_cb) override { NOTREACHED(); }
-  bool IsReadPending() const override {
-    NOTREACHED();
-    return false;
-  }
 
   void Configure(DecoderConfigType config);
 
diff --git a/third_party/blink/renderer/modules/webcodecs/video_frame.cc b/third_party/blink/renderer/modules/webcodecs/video_frame.cc
index d099cc5a..c4aad8b 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_frame.cc
+++ b/third_party/blink/renderer/modules/webcodecs/video_frame.cc
@@ -27,6 +27,41 @@
 
 namespace blink {
 
+namespace {
+
+bool IsValidSkColorSpace(sk_sp<SkColorSpace> sk_color_space) {
+  // Refer to CanvasColorSpaceToGfxColorSpace in CanvasColorParams.
+  sk_sp<SkColorSpace> valid_sk_color_spaces[] = {
+      gfx::ColorSpace::CreateSRGB().ToSkColorSpace(),
+      gfx::ColorSpace::CreateDisplayP3D65().ToSkColorSpace(),
+      gfx::ColorSpace(gfx::ColorSpace::PrimaryID::BT2020,
+                      gfx::ColorSpace::TransferID::GAMMA24)
+          .ToSkColorSpace()};
+  for (auto& valid_sk_color_space : valid_sk_color_spaces) {
+    if (SkColorSpace::Equals(sk_color_space.get(),
+                             valid_sk_color_space.get())) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool IsValidSkColorType(SkColorType sk_color_type) {
+  SkColorType valid_sk_color_types[] = {
+      kBGRA_8888_SkColorType, kRGBA_8888_SkColorType,
+      // TODO(jie.a.chen@intel.com): Add F16 support.
+      // kRGBA_F16_SkColorType
+  };
+  for (auto& valid_sk_color_type : valid_sk_color_types) {
+    if (sk_color_type == valid_sk_color_type) {
+      return true;
+    }
+  }
+  return false;
+}
+
+}  // namespace
+
 VideoFrame::VideoFrame(scoped_refptr<media::VideoFrame> frame)
     : frame_(std::move(frame)) {
   DCHECK(frame_);
@@ -101,6 +136,25 @@
     return nullptr;
   }
 
+  auto sk_image =
+      source->BitmapImage()->PaintImageForCurrentFrame().GetSkImage();
+  auto sk_color_space = sk_image->refColorSpace();
+  if (!sk_color_space) {
+    sk_color_space = SkColorSpace::MakeSRGB();
+  }
+  if (!IsValidSkColorSpace(sk_color_space)) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      "Invalid color space");
+    return nullptr;
+  }
+  auto sk_color_type = sk_image->colorType();
+  if (!IsValidSkColorType(sk_color_type)) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      "Invalid pixel format");
+    return nullptr;
+  }
+
+  // TODO(jie.a.chen@intel.com): Handle data of float type.
   // Full copy #1
   WTF::Vector<uint8_t> pixel_data = source->CopyBitmapData();
   if (pixel_data.size() <
@@ -118,25 +172,41 @@
     return nullptr;
   }
 
+#if SK_PMCOLOR_BYTE_ORDER(B, G, R, A)
+  auto libyuv_convert_to_i420 = libyuv::ARGBToI420;
+#else
+  auto libyuv_convert_to_i420 = libyuv::ABGRToI420;
+#endif
+  if (sk_color_type != kN32_SkColorType) {
+    // Swap ARGB and ABGR if not using the native pixel format.
+    libyuv_convert_to_i420 =
+        (libyuv_convert_to_i420 == libyuv::ARGBToI420 ? libyuv::ABGRToI420
+                                                      : libyuv::ARGBToI420);
+  }
+
+  // TODO(jie.a.chen@intel.com): Use GPU to do the conversion.
   // Full copy #2
   int error =
-      libyuv::ABGRToI420(pixel_data.data(), source->width() * 4,
-                         frame->visible_data(media::VideoFrame::kYPlane),
-                         frame->stride(media::VideoFrame::kYPlane),
-                         frame->visible_data(media::VideoFrame::kUPlane),
-                         frame->stride(media::VideoFrame::kUPlane),
-                         frame->visible_data(media::VideoFrame::kVPlane),
-                         frame->stride(media::VideoFrame::kVPlane),
-                         source->width(), source->height());
+      libyuv_convert_to_i420(pixel_data.data(), source->width() * 4,
+                             frame->visible_data(media::VideoFrame::kYPlane),
+                             frame->stride(media::VideoFrame::kYPlane),
+                             frame->visible_data(media::VideoFrame::kUPlane),
+                             frame->stride(media::VideoFrame::kUPlane),
+                             frame->visible_data(media::VideoFrame::kVPlane),
+                             frame->stride(media::VideoFrame::kVPlane),
+                             source->width(), source->height());
   if (error) {
     exception_state.ThrowDOMException(DOMExceptionCode::kOperationError,
                                       "ARGB to YUV420 conversion error");
     return nullptr;
   }
-  // TODO(jie.a.chen@intel.com): Figure out the right colorspace and conversion
-  // according to source ImageBitmap.
-  // libyuv::ABGRToI420 seems to assume Bt.601.
-  frame->set_color_space(gfx::ColorSpace::CreateREC601());
+  gfx::ColorSpace gfx_color_space(*sk_color_space);
+  // 'libyuv_convert_to_i420' assumes SMPTE170M.
+  // Refer to the func below to check the actual conversion:
+  // third_party/libyuv/source/row_common.cc -- RGBToY(...)
+  gfx_color_space = gfx_color_space.GetWithMatrixAndRange(
+      gfx::ColorSpace::MatrixID::SMPTE170M, gfx::ColorSpace::RangeID::LIMITED);
+  frame->set_color_space(gfx_color_space);
   auto* result = MakeGarbageCollected<VideoFrame>(std::move(frame));
   return result;
 }
@@ -166,6 +236,14 @@
        (frame_->format() == media::PIXEL_FORMAT_NV12 &&
         frame_->HasTextures()))) {
     scoped_refptr<StaticBitmapImage> image;
+    gfx::ColorSpace gfx_color_space = frame_->ColorSpace();
+    gfx_color_space = gfx_color_space.GetWithMatrixAndRange(
+        gfx::ColorSpace::MatrixID::RGB, gfx::ColorSpace::RangeID::FULL);
+    auto sk_color_space = gfx_color_space.ToSkColorSpace();
+    if (!sk_color_space) {
+      sk_color_space = SkColorSpace::MakeSRGB();
+    }
+
     if (!preferAcceleratedImageBitmap()) {
       size_t bytes_per_row = sizeof(SkColor) * visibleWidth();
       size_t image_pixels_size = bytes_per_row * visibleHeight();
@@ -179,11 +257,9 @@
       media::PaintCanvasVideoRenderer::ConvertVideoFrameToRGBPixels(
           frame_.get(), image_pixels->writable_data(), bytes_per_row);
 
-      // TODO(jie.a.chen@intel.com): Figure out the correct SkColorSpace.
-      sk_sp<SkColorSpace> skColorSpace = SkColorSpace::MakeSRGB();
       SkImageInfo info =
           SkImageInfo::Make(visibleWidth(), visibleHeight(), kN32_SkColorType,
-                            kUnpremul_SkAlphaType, std::move(skColorSpace));
+                            kUnpremul_SkAlphaType, std::move(sk_color_space));
       sk_sp<SkImage> skImage =
           SkImage::MakeRasterData(info, image_pixels, bytes_per_row);
       image = UnacceleratedStaticBitmapImage::Create(std::move(skImage));
@@ -224,7 +300,8 @@
           base::Unretained(shared_image_interface), dest_holder.mailbox));
 
       const SkImageInfo sk_image_info =
-          SkImageInfo::MakeN32Premul(codedWidth(), codedHeight());
+          SkImageInfo::Make(codedWidth(), codedHeight(), kN32_SkColorType,
+                            kUnpremul_SkAlphaType, std::move(sk_color_space));
 
       image = AcceleratedStaticBitmapImage::CreateFromCanvasMailbox(
           dest_holder.mailbox, sync_token, 0u, sk_image_info,
diff --git a/third_party/blink/renderer/modules/webcodecs/video_frame.h b/third_party/blink/renderer/modules/webcodecs/video_frame.h
index 79e3978..3346e51 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_frame.h
+++ b/third_party/blink/renderer/modules/webcodecs/video_frame.h
@@ -53,7 +53,7 @@
   scoped_refptr<const media::VideoFrame> frame() const;
 
  private:
-  static constexpr uint64_t kCpuEfficientFrameSize = 480u * 320u;
+  static constexpr uint64_t kCpuEfficientFrameSize = 320u * 240u;
   bool preferAcceleratedImageBitmap() const;
   scoped_refptr<media::VideoFrame> frame_;
 };
diff --git a/third_party/blink/renderer/modules/webgl/BUILD.gn b/third_party/blink/renderer/modules/webgl/BUILD.gn
index f7d6b04..e5841a2 100644
--- a/third_party/blink/renderer/modules/webgl/BUILD.gn
+++ b/third_party/blink/renderer/modules/webgl/BUILD.gn
@@ -143,6 +143,8 @@
     "webgl_transform_feedback.h",
     "webgl_uniform_location.cc",
     "webgl_uniform_location.h",
+    "webgl_unowned_texture.cc",
+    "webgl_unowned_texture.h",
     "webgl_vertex_array_object.cc",
     "webgl_vertex_array_object.h",
     "webgl_vertex_array_object_base.cc",
diff --git a/third_party/blink/renderer/modules/webgl/webgl_texture.h b/third_party/blink/renderer/modules/webgl/webgl_texture.h
index 80e9902..0883232c 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_texture.h
+++ b/third_party/blink/renderer/modules/webgl/webgl_texture.h
@@ -31,18 +31,12 @@
 
 namespace blink {
 
-class WebGLTexture final : public WebGLSharedPlatform3DObject {
+class WebGLTexture : public WebGLSharedPlatform3DObject {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
   explicit WebGLTexture(WebGLRenderingContextBase*);
 
-  // The provided GLuint must have been created in the same
-  // WebGLRenderingContextBase that is provided.
-  explicit WebGLTexture(WebGLRenderingContextBase* ctx,
-                        GLuint texture,
-                        GLenum target);
-
   ~WebGLTexture() override;
 
   void SetTarget(GLenum);
@@ -77,6 +71,12 @@
     return last_uploaded_video_frame_metadata_.skipped;
   }
 
+ protected:
+  // Constructor for WebGLUnownedTexture.
+  explicit WebGLTexture(WebGLRenderingContextBase* ctx,
+                        GLuint texture,
+                        GLenum target);
+
  private:
   void DeleteObjectImpl(gpu::gles2::GLES2Interface*) override;
 
diff --git a/third_party/blink/renderer/modules/webgl/webgl_unowned_texture.cc b/third_party/blink/renderer/modules/webgl/webgl_unowned_texture.cc
new file mode 100644
index 0000000..bd29e7e
--- /dev/null
+++ b/third_party/blink/renderer/modules/webgl/webgl_unowned_texture.cc
@@ -0,0 +1,23 @@
+// Copyright 2020 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/modules/webgl/webgl_unowned_texture.h"
+
+#include "gpu/command_buffer/client/gles2_interface.h"
+#include "third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h"
+
+namespace blink {
+
+WebGLUnownedTexture::WebGLUnownedTexture(WebGLRenderingContextBase* ctx,
+                                         GLuint texture,
+                                         GLenum target)
+    : WebGLTexture(ctx, texture, target) {}
+
+void WebGLUnownedTexture::DeleteObjectImpl(gpu::gles2::GLES2Interface* gl) {
+  object_ = 0;
+}
+
+WebGLUnownedTexture::~WebGLUnownedTexture() = default;
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/webgl/webgl_unowned_texture.h b/third_party/blink/renderer/modules/webgl/webgl_unowned_texture.h
new file mode 100644
index 0000000..ee694dd
--- /dev/null
+++ b/third_party/blink/renderer/modules/webgl/webgl_unowned_texture.h
@@ -0,0 +1,33 @@
+// Copyright 2020 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_MODULES_WEBGL_WEBGL_UNOWNED_TEXTURE_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_UNOWNED_TEXTURE_H_
+
+#include "third_party/blink/renderer/modules/webgl/webgl_texture.h"
+
+namespace blink {
+
+// This class exists to prevent a double-freeing of a texture resource.
+// It is also necessary for WebXR's Camera Access feature to be able to
+// provide a camera image textures until it's decided how to best expose
+// the texture to the WebXR API.
+// TODO(https://bugs.chromium.org/p/chromium/issues/detail?id=1104340).
+class WebGLUnownedTexture final : public WebGLTexture {
+ public:
+  // The provided GLuint must have been created in the same
+  // WebGLRenderingContextBase that is provided. Garbage collection of
+  // this texture will not result in any GL calls being issued.
+  explicit WebGLUnownedTexture(WebGLRenderingContextBase* ctx,
+                               GLuint texture,
+                               GLenum target);
+  ~WebGLUnownedTexture() override;
+
+ private:
+  void DeleteObjectImpl(gpu::gles2::GLES2Interface*) override;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_UNOWNED_TEXTURE_H_
diff --git a/third_party/blink/renderer/modules/xr/xr_view.h b/third_party/blink/renderer/modules/xr/xr_view.h
index 19c9caf..3804f78 100644
--- a/third_party/blink/renderer/modules/xr/xr_view.h
+++ b/third_party/blink/renderer/modules/xr/xr_view.h
@@ -35,6 +35,13 @@
   DOMFloat32Array* projectionMatrix() const;
   XRRigidTransform* transform() const;
 
+  // isFirstPersonObserver is only true for views that composed with a video
+  // feed that is not directly displayed on the viewer device. Primarily this is
+  // used for video streams from optically transparent AR headsets. Since Chrome
+  // does not directly support any such headset at this time we return false
+  // unconditionally.
+  bool isFirstPersonObserver() const { return false; }
+
   void Trace(Visitor*) const override;
 
  private:
diff --git a/third_party/blink/renderer/modules/xr/xr_view.idl b/third_party/blink/renderer/modules/xr/xr_view.idl
index 173d1c7..1dc47b8 100644
--- a/third_party/blink/renderer/modules/xr/xr_view.idl
+++ b/third_party/blink/renderer/modules/xr/xr_view.idl
@@ -17,4 +17,8 @@
   readonly attribute XREye eye;
   readonly attribute Float32Array projectionMatrix;
   [SameObject] readonly attribute XRRigidTransform transform;
+
+  // Added by the AR module.
+  // https://immersive-web.github.io/webxr-ar-module/
+  readonly attribute boolean isFirstPersonObserver;
 };
diff --git a/third_party/blink/renderer/modules/xr/xr_webgl_binding.cc b/third_party/blink/renderer/modules/xr/xr_webgl_binding.cc
index 4db916de..800ebd7 100644
--- a/third_party/blink/renderer/modules/xr/xr_webgl_binding.cc
+++ b/third_party/blink/renderer/modules/xr/xr_webgl_binding.cc
@@ -6,6 +6,7 @@
 
 #include "third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h"
 #include "third_party/blink/renderer/modules/webgl/webgl_texture.h"
+#include "third_party/blink/renderer/modules/webgl/webgl_unowned_texture.h"
 #include "third_party/blink/renderer/modules/xr/xr_cube_map.h"
 #include "third_party/blink/renderer/modules/xr/xr_frame.h"
 #include "third_party/blink/renderer/modules/xr/xr_light_probe.h"
@@ -101,7 +102,8 @@
 
   GLuint texture_id = base_layer->CameraImageTextureId();
 
-  WebGLTexture* texture = MakeGarbageCollected<WebGLTexture>(
+  // This resource is owned by the renderer, and is freed OnFrameEnd();
+  WebGLUnownedTexture* texture = MakeGarbageCollected<WebGLUnownedTexture>(
       webgl_context_, texture_id, GL_TEXTURE_2D);
   return texture;
 }
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index bcf74de..984e5cc 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -496,7 +496,6 @@
     "exported/mediastream/web_media_stream_source.cc",
     "exported/mediastream/web_media_stream_track.cc",
     "exported/mediastream/web_platform_media_stream_source.cc",
-    "exported/mediastream/web_platform_media_stream_track.cc",
     "exported/platform.cc",
     "exported/url_conversion.cc",
     "exported/video_capture/web_video_capture_impl_manager.cc",
@@ -1216,6 +1215,8 @@
     "mediastream/media_stream_descriptor.h",
     "mediastream/media_stream_source.cc",
     "mediastream/media_stream_source.h",
+    "mediastream/media_stream_track_platform.cc",
+    "mediastream/media_stream_track_platform.h",
     "mediastream/media_stream_web_audio_source.cc",
     "mediastream/media_stream_web_audio_source.h",
     "mediastream/webaudio_destination_consumer.h",
diff --git a/third_party/blink/renderer/platform/exported/mediastream/web_platform_media_stream_track.cc b/third_party/blink/renderer/platform/exported/mediastream/web_platform_media_stream_track.cc
deleted file mode 100644
index 6687524..0000000
--- a/third_party/blink/renderer/platform/exported/mediastream/web_platform_media_stream_track.cc
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/blink/public/platform/modules/mediastream/web_platform_media_stream_track.h"
-#include "third_party/blink/renderer/platform/mediastream/media_stream_component.h"
-
-namespace blink {
-
-// static
-WebPlatformMediaStreamTrack* WebPlatformMediaStreamTrack::GetTrack(
-    const WebMediaStreamTrack& track) {
-  if (track.IsNull())
-    return nullptr;
-
-  MediaStreamComponent& component = *track;
-  return component.GetPlatformTrack();
-}
-
-WebPlatformMediaStreamTrack::WebPlatformMediaStreamTrack(bool is_local_track)
-    : is_local_track_(is_local_track) {}
-
-WebPlatformMediaStreamTrack::~WebPlatformMediaStreamTrack() {}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/platform/exported/web_mixed_content.cc b/third_party/blink/renderer/platform/exported/web_mixed_content.cc
index 6b8f43b..3595927 100644
--- a/third_party/blink/renderer/platform/exported/web_mixed_content.cc
+++ b/third_party/blink/renderer/platform/exported/web_mixed_content.cc
@@ -37,7 +37,7 @@
 // static
 WebMixedContentContextType WebMixedContent::ContextTypeFromRequestContext(
     mojom::RequestContextType context,
-    bool strict_mixed_content_checking_for_plugin) {
+    WebMixedContent::CheckModeForPlugin check_mode_for_plugin) {
   switch (context) {
     // "Optionally-blockable" mixed content
     case mojom::RequestContextType::AUDIO:
@@ -47,7 +47,8 @@
 
     // Plugins! Oh how dearly we love plugin-loaded content!
     case mojom::RequestContextType::PLUGIN: {
-      return strict_mixed_content_checking_for_plugin
+      return check_mode_for_plugin ==
+                     WebMixedContent::CheckModeForPlugin::kStrict
                  ? WebMixedContentContextType::kBlockable
                  : WebMixedContentContextType::kOptionallyBlockable;
     }
diff --git a/third_party/blink/renderer/platform/exported/web_screen_info_test.cc b/third_party/blink/renderer/platform/exported/web_screen_info_test.cc
index b694f3e..8ba3c25 100644
--- a/third_party/blink/renderer/platform/exported/web_screen_info_test.cc
+++ b/third_party/blink/renderer/platform/exported/web_screen_info_test.cc
@@ -37,7 +37,8 @@
   screen_info1.is_monochrome = false;
   screen_info1.rect = gfx::Rect(1024, 1024);
   screen_info1.available_rect = gfx::Rect(1024, 1024);
-  screen_info1.orientation_type = blink::kWebScreenOrientationLandscapePrimary;
+  screen_info1.orientation_type =
+      blink::mojom::ScreenOrientation::kLandscapePrimary;
   screen_info1.orientation_angle = 90;
 
   EXPECT_NE(screen_info1, screen_info2);
@@ -48,7 +49,8 @@
   screen_info2.is_monochrome = false;
   screen_info2.rect = gfx::Rect(1024, 1024);
   screen_info2.available_rect = gfx::Rect(1024, 1024);
-  screen_info2.orientation_type = blink::kWebScreenOrientationLandscapePrimary;
+  screen_info2.orientation_type =
+      blink::mojom::ScreenOrientation::kLandscapePrimary;
   screen_info2.orientation_angle = 90;
 
   EXPECT_EQ(screen_info1, screen_info2);
diff --git a/third_party/blink/renderer/platform/fonts/font_data_for_range_set.h b/third_party/blink/renderer/platform/fonts/font_data_for_range_set.h
index 7e218d6..bb2d024 100644
--- a/third_party/blink/renderer/platform/fonts/font_data_for_range_set.h
+++ b/third_party/blink/renderer/platform/fonts/font_data_for_range_set.h
@@ -58,6 +58,12 @@
   bool HasFontData() const { return font_data_.get(); }
   const SimpleFontData* FontData() const { return font_data_.get(); }
 
+  // TODO(xiaochengh): |FontData::IsLoadingFallback()| returns true if the
+  // FontData is a pending custom font. We should rename it for better clarity.
+  bool IsPendingCustomFont() const {
+    return font_data_ && font_data_->IsLoadingFallback();
+  }
+
  protected:
   scoped_refptr<SimpleFontData> font_data_;
   scoped_refptr<UnicodeRangeSet> range_set_;
diff --git a/third_party/blink/renderer/platform/fonts/font_fallback_iterator.cc b/third_party/blink/renderer/platform/fonts/font_fallback_iterator.cc
index e1778f3..a6fbd916 100644
--- a/third_party/blink/renderer/platform/fonts/font_fallback_iterator.cc
+++ b/third_party/blink/renderer/platform/fonts/font_fallback_iterator.cc
@@ -37,7 +37,11 @@
     const FontDataForRangeSet* segmented_face) {
   for (auto* it = hint_list.begin(); it != hint_list.end(); ++it) {
     if (segmented_face->Contains(*it)) {
-      if (!AlreadyLoadingRangeForHintChar(*it))
+      // If it's a pending custom font, we need to make sure it can render any
+      // new characters, otherwise we may trigger a redundant load. In other
+      // cases (already loaded or not a custom font), we can use it right away.
+      if (!segmented_face->IsPendingCustomFont() ||
+          !AlreadyLoadingRangeForHintChar(*it))
         return true;
     }
   }
diff --git a/third_party/blink/renderer/platform/heap/collection_support/heap_hash_table_backing.h b/third_party/blink/renderer/platform/heap/collection_support/heap_hash_table_backing.h
index e27bd743..f0c45df6 100644
--- a/third_party/blink/renderer/platform/heap/collection_support/heap_hash_table_backing.h
+++ b/third_party/blink/renderer/platform/heap/collection_support/heap_hash_table_backing.h
@@ -111,7 +111,8 @@
   template <WTF::WeakHandlingFlag WeakHandling = WTF::kNoWeakHandling>
   static void Trace(Visitor* visitor, const void* self) {
     if (!Traits::kCanTraceConcurrently && self) {
-      if (visitor->DeferredTraceIfConcurrent({self, &Trace}))
+      if (visitor->DeferredTraceIfConcurrent({self, &Trace},
+                                             GetBackingStoreSize(self)))
         return;
     }
 
@@ -123,6 +124,15 @@
   }
 
  private:
+  static size_t GetBackingStoreSize(const void* backing_store) {
+    const HeapObjectHeader* header =
+        HeapObjectHeader::FromPayload(backing_store);
+    return header->IsLargeObject<HeapObjectHeader::AccessMode::kAtomic>()
+               ? static_cast<LargeObjectPage*>(PageFromObject(header))
+                     ->ObjectSize()
+               : header->size<HeapObjectHeader::AccessMode::kAtomic>();
+  }
+
   template <typename ValueType>
   struct GetWeakTraceDescriptorImpl {
     static TraceDescriptor GetWeakTraceDescriptor(const void* backing) {
diff --git a/third_party/blink/renderer/platform/heap/collection_support/heap_vector_backing.h b/third_party/blink/renderer/platform/heap/collection_support/heap_vector_backing.h
index dd28ac8..07c97e2 100644
--- a/third_party/blink/renderer/platform/heap/collection_support/heap_vector_backing.h
+++ b/third_party/blink/renderer/platform/heap/collection_support/heap_vector_backing.h
@@ -110,7 +110,8 @@
 
   static void Trace(Visitor* visitor, const void* self) {
     if (!Traits::kCanTraceConcurrently && self) {
-      if (visitor->DeferredTraceIfConcurrent({self, &Trace}))
+      if (visitor->DeferredTraceIfConcurrent({self, &Trace},
+                                             GetBackingStoreSize(self)))
         return;
     }
 
@@ -122,6 +123,16 @@
                                   void>::Trace(visitor, self);
     }
   }
+
+ private:
+  static size_t GetBackingStoreSize(const void* backing_store) {
+    const HeapObjectHeader* header =
+        HeapObjectHeader::FromPayload(backing_store);
+    return header->IsLargeObject<HeapObjectHeader::AccessMode::kAtomic>()
+               ? static_cast<LargeObjectPage*>(PageFromObject(header))
+                     ->ObjectSize()
+               : header->size<HeapObjectHeader::AccessMode::kAtomic>();
+  }
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/heap/heap.cc b/third_party/blink/renderer/platform/heap/heap.cc
index 3d66482b..bca56ff 100644
--- a/third_party/blink/renderer/platform/heap/heap.cc
+++ b/third_party/blink/renderer/platform/heap/heap.cc
@@ -400,8 +400,9 @@
         // with smaller objects).
         finished = DrainWorklistWithDeadline<kDefaultDeadlineCheckInterval / 5>(
             deadline, not_safe_to_concurrently_trace_worklist_.get(),
-            [visitor](const MarkingItem& item) {
-              item.callback(visitor, item.base_object_payload);
+            [visitor](const NotSafeToConcurrentlyTraceItem& item) {
+              item.desc.callback(visitor, item.desc.base_object_payload);
+              visitor->AccountMarkedBytes(item.bailout_size);
             },
             WorklistTaskId::MutatorThread);
         if (!finished)
diff --git a/third_party/blink/renderer/platform/heap/heap.h b/third_party/blink/renderer/platform/heap/heap.h
index 78b541f..0f7d696 100644
--- a/third_party/blink/renderer/platform/heap/heap.h
+++ b/third_party/blink/renderer/platform/heap/heap.h
@@ -82,6 +82,11 @@
   const void* parameter;
 };
 
+struct NotSafeToConcurrentlyTraceItem {
+  TraceDescriptor desc;
+  size_t bailout_size;
+};
+
 using V8Reference = const TraceWrapperV8Reference<v8::Value>*;
 
 // Segment size of 512 entries necessary to avoid throughput regressions. Since
@@ -102,7 +107,7 @@
     Worklist<BackingStoreCallbackItem, 16 /* local entries */>;
 using V8ReferencesWorklist = Worklist<V8Reference, 16 /* local entries */>;
 using NotSafeToConcurrentlyTraceWorklist =
-    Worklist<MarkingItem, 64 /* local entries */>;
+    Worklist<NotSafeToConcurrentlyTraceItem, 64 /* local entries */>;
 
 class PLATFORM_EXPORT HeapAllocHooks {
   STATIC_ONLY(HeapAllocHooks);
diff --git a/third_party/blink/renderer/platform/heap/marking_visitor.h b/third_party/blink/renderer/platform/heap/marking_visitor.h
index 069c4a4c..41e4ca6 100644
--- a/third_party/blink/renderer/platform/heap/marking_visitor.h
+++ b/third_party/blink/renderer/platform/heap/marking_visitor.h
@@ -72,6 +72,7 @@
   // actually tracing through an already marked object. Logically, this means
   // accounting for the bytes when transitioning from grey to black.
   ALWAYS_INLINE void AccountMarkedBytes(HeapObjectHeader*);
+  ALWAYS_INLINE void AccountMarkedBytes(size_t);
 
  protected:
   MarkingVisitorBase(ThreadState*, MarkingMode, int task_id);
@@ -101,10 +102,14 @@
 
 ALWAYS_INLINE void MarkingVisitorBase::AccountMarkedBytes(
     HeapObjectHeader* header) {
-  marked_bytes_ +=
+  AccountMarkedBytes(
       header->IsLargeObject<HeapObjectHeader::AccessMode::kAtomic>()
           ? static_cast<LargeObjectPage*>(PageFromObject(header))->ObjectSize()
-          : header->size<HeapObjectHeader::AccessMode::kAtomic>();
+          : header->size<HeapObjectHeader::AccessMode::kAtomic>());
+}
+
+ALWAYS_INLINE void MarkingVisitorBase::AccountMarkedBytes(size_t marked_bytes) {
+  marked_bytes_ += marked_bytes;
 }
 
 ALWAYS_INLINE bool MarkingVisitorBase::MarkHeaderNoTracing(
@@ -240,8 +245,15 @@
 
   bool IsConcurrent() const override { return true; }
 
-  bool DeferredTraceIfConcurrent(TraceDescriptor desc) override {
-    not_safe_to_concurrently_trace_worklist_.Push(desc);
+  bool DeferredTraceIfConcurrent(TraceDescriptor desc,
+                                 size_t bailout_size) override {
+    not_safe_to_concurrently_trace_worklist_.Push({desc, bailout_size});
+    // The object is bailed out from concurrent marking, so updating
+    // marked_bytes_ to reflect how many bytes were actually traced.
+    // This deducted bytes will be added to the mutator thread marking
+    // visitor's marked_bytes_ count when the object is popped from
+    // the bailout worklist.
+    marked_bytes_ -= bailout_size;
     return true;
   }
 
diff --git a/third_party/blink/renderer/platform/heap/visitor.h b/third_party/blink/renderer/platform/heap/visitor.h
index 15ca5db..8a2f644 100644
--- a/third_party/blink/renderer/platform/heap/visitor.h
+++ b/third_party/blink/renderer/platform/heap/visitor.h
@@ -272,7 +272,9 @@
   //
   // This can be used to defer processing data structures to the main thread
   // when support for concurrent processing is missing.
-  virtual bool DeferredTraceIfConcurrent(TraceDescriptor desc) { return false; }
+  virtual bool DeferredTraceIfConcurrent(TraceDescriptor, size_t) {
+    return false;
+  }
 
  protected:
   // Visits an object through a strong reference.
diff --git a/third_party/blink/renderer/platform/loader/fetch/url_loader/web_bundle_subresource_loader.cc b/third_party/blink/renderer/platform/loader/fetch/url_loader/web_bundle_subresource_loader.cc
index c5544581..8e47656e 100644
--- a/third_party/blink/renderer/platform/loader/fetch/url_loader/web_bundle_subresource_loader.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/url_loader/web_bundle_subresource_loader.cc
@@ -135,6 +135,12 @@
             std::make_unique<mojo::DataPipeDrainer>(this,
                                                     std::move(bundle_body))) {}
 
+  ~LinkWebBundleDataSource() override {
+    // The receiver must be closed before destructing pending callbacks in
+    // |pending_reads_| / |pending_reads_to_data_pipe_|.
+    data_source_receiver_.reset();
+  }
+
   LinkWebBundleDataSource(const LinkWebBundleDataSource&) = delete;
   LinkWebBundleDataSource& operator=(const LinkWebBundleDataSource&) = delete;
 
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_audio_track.cc b/third_party/blink/renderer/platform/mediastream/media_stream_audio_track.cc
index 6918337..cabcd97 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_audio_track.cc
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_audio_track.cc
@@ -26,7 +26,7 @@
 }  // namespace
 
 MediaStreamAudioTrack::MediaStreamAudioTrack(bool is_local_track)
-    : WebPlatformMediaStreamTrack(is_local_track), is_enabled_(1) {
+    : MediaStreamTrackPlatform(is_local_track), is_enabled_(1) {
   SendLogMessage(
       base::StringPrintf("MediaStreamAudioTrack([this=%p] {is_local_track=%s})",
                          this, (is_local_track ? "true" : "false")));
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_audio_track.h b/third_party/blink/renderer/platform/mediastream/media_stream_audio_track.h
index deb29be..4b8d7cae6 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_audio_track.h
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_audio_track.h
@@ -13,8 +13,8 @@
 #include "base/memory/weak_ptr.h"
 #include "base/synchronization/lock.h"
 #include "base/threading/thread_checker.h"
-#include "third_party/blink/public/platform/modules/mediastream/web_platform_media_stream_track.h"
 #include "third_party/blink/renderer/platform/mediastream/media_stream_audio_deliverer.h"
+#include "third_party/blink/renderer/platform/mediastream/media_stream_track_platform.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
 
 namespace blink {
@@ -27,8 +27,7 @@
 // MediaStreamAudioSource to one or more WebMediaStreamAudioSinks. An instance
 // of this class is owned by MediaStreamComponent/WebMediaStreamTrack, and
 // clients should use From() to gain access to a MediaStreamAudioTrack.
-class PLATFORM_EXPORT MediaStreamAudioTrack
-    : public WebPlatformMediaStreamTrack {
+class PLATFORM_EXPORT MediaStreamAudioTrack : public MediaStreamTrackPlatform {
  public:
   explicit MediaStreamAudioTrack(bool is_local_track);
 
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_component.cc b/third_party/blink/renderer/platform/mediastream/media_stream_component.cc
index f06ef654..dc693c5 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_component.cc
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_component.cc
@@ -79,7 +79,7 @@
 }
 
 void MediaStreamComponent::GetSettings(
-    WebMediaStreamTrack::Settings& settings) {
+    MediaStreamTrackPlatform::Settings& settings) {
   DCHECK(platform_track_);
   source_->GetSettings(settings);
   platform_track_->GetSettings(settings);
@@ -104,7 +104,7 @@
     return;
   content_hint_ = hint;
 
-  WebPlatformMediaStreamTrack* native_track = GetPlatformTrack();
+  MediaStreamTrackPlatform* native_track = GetPlatformTrack();
   if (native_track)
     native_track->SetContentHint(ContentHint());
 }
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_component.h b/third_party/blink/renderer/platform/mediastream/media_stream_component.h
index cfde7d4..4751054 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_component.h
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_component.h
@@ -35,10 +35,10 @@
 #include <memory>
 
 #include "third_party/blink/public/platform/modules/mediastream/web_media_stream_track.h"
-#include "third_party/blink/public/platform/modules/mediastream/web_platform_media_stream_track.h"
 #include "third_party/blink/renderer/platform/audio/audio_source_provider.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/mediastream/media_constraints.h"
+#include "third_party/blink/renderer/platform/mediastream/media_stream_track_platform.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 #include "third_party/blink/renderer/platform/wtf/threading_primitives.h"
@@ -93,14 +93,14 @@
     source_provider_.Wrap(provider);
   }
 
-  WebPlatformMediaStreamTrack* GetPlatformTrack() const {
+  MediaStreamTrackPlatform* GetPlatformTrack() const {
     return platform_track_.get();
   }
   void SetPlatformTrack(
-      std::unique_ptr<WebPlatformMediaStreamTrack> platform_track) {
+      std::unique_ptr<MediaStreamTrackPlatform> platform_track) {
     platform_track_ = std::move(platform_track);
   }
-  void GetSettings(WebMediaStreamTrack::Settings&);
+  void GetSettings(MediaStreamTrackPlatform::Settings&);
 
   void Trace(Visitor*) const;
 
@@ -136,7 +136,7 @@
   WebMediaStreamTrack::ContentHintType content_hint_ =
       WebMediaStreamTrack::ContentHintType::kNone;
   MediaConstraints constraints_;
-  std::unique_ptr<WebPlatformMediaStreamTrack> platform_track_;
+  std::unique_ptr<MediaStreamTrackPlatform> platform_track_;
 };
 
 typedef HeapVector<Member<MediaStreamComponent>> MediaStreamComponentVector;
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_descriptor.cc b/third_party/blink/renderer/platform/mediastream/media_stream_descriptor.cc
index b1ecc5db..f20bee5 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_descriptor.cc
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_descriptor.cc
@@ -32,6 +32,7 @@
 #include "third_party/blink/renderer/platform/mediastream/media_stream_descriptor.h"
 
 #include "third_party/blink/public/platform/modules/mediastream/web_media_stream.h"
+#include "third_party/blink/public/platform/web_string.h"
 #include "third_party/blink/renderer/platform/heap/heap.h"
 #include "third_party/blink/renderer/platform/wtf/uuid.h"
 
@@ -63,7 +64,7 @@
   // Iterate over a copy of |observers_| to avoid re-entrancy issues.
   Vector<WebMediaStreamObserver*> observers = observers_;
   for (auto*& observer : observers)
-    observer->TrackAdded(component->Id());
+    observer->TrackAdded(WebString(component->Id()));
 }
 
 void MediaStreamDescriptor::RemoveComponent(MediaStreamComponent* component) {
@@ -84,7 +85,7 @@
   // Iterate over a copy of |observers_| to avoid re-entrancy issues.
   Vector<WebMediaStreamObserver*> observers = observers_;
   for (auto*& observer : observers)
-    observer->TrackRemoved(component->Id());
+    observer->TrackRemoved(WebString(component->Id()));
 }
 
 void MediaStreamDescriptor::AddRemoteTrack(MediaStreamComponent* component) {
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_source.cc b/third_party/blink/renderer/platform/mediastream/media_stream_source.cc
index 9034fa2..a8dab81 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_source.cc
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_source.cc
@@ -71,9 +71,8 @@
 }
 
 void GetSourceSettings(const blink::WebMediaStreamSource& web_source,
-                       blink::WebMediaStreamTrack::Settings& settings) {
-  blink::MediaStreamAudioSource* const source =
-      blink::MediaStreamAudioSource::From(web_source);
+                       MediaStreamTrackPlatform::Settings& settings) {
+  auto* const source = blink::MediaStreamAudioSource::From(web_source);
   if (!source)
     return;
 
@@ -245,7 +244,8 @@
   return true;
 }
 
-void MediaStreamSource::GetSettings(WebMediaStreamTrack::Settings& settings) {
+void MediaStreamSource::GetSettings(
+    MediaStreamTrackPlatform::Settings& settings) {
   settings.device_id = Id();
   settings.group_id = GroupId();
 
@@ -253,22 +253,22 @@
     switch (*echo_cancellation_mode_) {
       case EchoCancellationMode::kDisabled:
         settings.echo_cancellation = false;
-        settings.echo_cancellation_type.Reset();
+        settings.echo_cancellation_type = String();
         break;
       case EchoCancellationMode::kBrowser:
         settings.echo_cancellation = true;
         settings.echo_cancellation_type =
-            WebString::FromASCII(blink::kEchoCancellationTypeBrowser);
+            String::FromUTF8(blink::kEchoCancellationTypeBrowser);
         break;
       case EchoCancellationMode::kAec3:
         settings.echo_cancellation = true;
         settings.echo_cancellation_type =
-            WebString::FromASCII(blink::kEchoCancellationTypeAec3);
+            String::FromUTF8(blink::kEchoCancellationTypeAec3);
         break;
       case EchoCancellationMode::kSystem:
         settings.echo_cancellation = true;
         settings.echo_cancellation_type =
-            WebString::FromASCII(blink::kEchoCancellationTypeSystem);
+            String::FromUTF8(blink::kEchoCancellationTypeSystem);
         break;
     }
   }
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_source.h b/third_party/blink/renderer/platform/mediastream/media_stream_source.h
index 830dc68..8cfb5b10 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_source.h
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_source.h
@@ -41,6 +41,7 @@
 #include "third_party/blink/public/platform/modules/mediastream/web_platform_media_stream_source.h"
 #include "third_party/blink/renderer/platform/audio/audio_destination_consumer.h"
 #include "third_party/blink/renderer/platform/mediastream/media_constraints.h"
+#include "third_party/blink/renderer/platform/mediastream/media_stream_track_platform.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
@@ -102,7 +103,7 @@
                                     bool auto_gain_control,
                                     bool noise_supression);
 
-  void GetSettings(WebMediaStreamTrack::Settings&);
+  void GetSettings(MediaStreamTrackPlatform::Settings&);
 
   struct Capabilities {
     // Vector is used to store an optional range for the below numeric
@@ -120,8 +121,8 @@
     Vector<int32_t> sample_rate;
     Vector<double> latency;
 
-    WebMediaStreamTrack::FacingMode facing_mode =
-        WebMediaStreamTrack::FacingMode::kNone;
+    MediaStreamTrackPlatform::FacingMode facing_mode =
+        MediaStreamTrackPlatform::FacingMode::kNone;
     String device_id;
     String group_id;
   };
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_track_platform.cc b/third_party/blink/renderer/platform/mediastream/media_stream_track_platform.cc
new file mode 100644
index 0000000..659d0138
--- /dev/null
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_track_platform.cc
@@ -0,0 +1,25 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/platform/mediastream/media_stream_track_platform.h"
+#include "third_party/blink/renderer/platform/mediastream/media_stream_component.h"
+
+namespace blink {
+
+// static
+MediaStreamTrackPlatform* MediaStreamTrackPlatform::GetTrack(
+    const WebMediaStreamTrack& track) {
+  if (track.IsNull())
+    return nullptr;
+
+  MediaStreamComponent& component = *track;
+  return component.GetPlatformTrack();
+}
+
+MediaStreamTrackPlatform::MediaStreamTrackPlatform(bool is_local_track)
+    : is_local_track_(is_local_track) {}
+
+MediaStreamTrackPlatform::~MediaStreamTrackPlatform() {}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_track_platform.h b/third_party/blink/renderer/platform/mediastream/media_stream_track_platform.h
new file mode 100644
index 0000000..1444c0c
--- /dev/null
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_track_platform.h
@@ -0,0 +1,91 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_MEDIASTREAM_MEDIA_STREAM_TRACK_PLATFORM_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_MEDIASTREAM_MEDIA_STREAM_TRACK_PLATFORM_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "third_party/blink/public/platform/modules/mediastream/web_media_stream_track.h"
+#include "third_party/blink/renderer/platform/platform_export.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+
+// MediaStreamTrackPlatform is a low-level object backing a
+// WebMediaStreamTrack.
+class PLATFORM_EXPORT MediaStreamTrackPlatform {
+ public:
+  enum class FacingMode { kNone, kUser, kEnvironment, kLeft, kRight };
+
+  struct Settings {
+    bool HasFrameRate() const { return frame_rate >= 0.0; }
+    bool HasWidth() const { return width >= 0; }
+    bool HasHeight() const { return height >= 0; }
+    bool HasAspectRatio() const { return aspect_ratio >= 0.0; }
+    bool HasFacingMode() const { return facing_mode != FacingMode::kNone; }
+    bool HasSampleRate() const { return sample_rate >= 0; }
+    bool HasSampleSize() const { return sample_size >= 0; }
+    bool HasChannelCount() const { return channel_count >= 0; }
+    bool HasLatency() const { return latency >= 0; }
+    bool HasVideoKind() const { return !video_kind.IsNull(); }
+    // The variables are read from
+    // MediaStreamTrack::GetSettings only.
+    double frame_rate = -1.0;
+    int32_t width = -1;
+    int32_t height = -1;
+    double aspect_ratio = -1.0;
+    String device_id;
+    String group_id;
+    FacingMode facing_mode = FacingMode::kNone;
+    String resize_mode;
+    base::Optional<bool> echo_cancellation;
+    base::Optional<bool> auto_gain_control;
+    base::Optional<bool> noise_supression;
+    String echo_cancellation_type;
+    int32_t sample_rate = -1;
+    int32_t sample_size = -1;
+    int32_t channel_count = -1;
+    double latency = -1.0;
+
+    // Media Capture Depth Stream Extensions.
+    String video_kind;
+
+    // Screen Capture extensions
+    base::Optional<media::mojom::DisplayCaptureSurfaceType> display_surface;
+    base::Optional<bool> logical_surface;
+    base::Optional<media::mojom::CursorCaptureType> cursor;
+  };
+
+  explicit MediaStreamTrackPlatform(bool is_local_track);
+  virtual ~MediaStreamTrackPlatform();
+
+  static MediaStreamTrackPlatform* GetTrack(const WebMediaStreamTrack& track);
+
+  virtual void SetEnabled(bool enabled) = 0;
+
+  virtual void SetContentHint(
+      WebMediaStreamTrack::ContentHintType content_hint) = 0;
+
+  // If |callback| is not null, it is invoked when the track has stopped.
+  virtual void StopAndNotify(base::OnceClosure callback) = 0;
+
+  void Stop() { StopAndNotify(base::OnceClosure()); }
+
+  // TODO(hta): Make method pure virtual when all tracks have the method.
+  virtual void GetSettings(Settings& settings) {}
+
+  bool is_local_track() const { return is_local_track_; }
+
+ private:
+  const bool is_local_track_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MediaStreamTrackPlatform);
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_MEDIASTREAM_MEDIA_STREAM_TRACK_PLATFORM_H_
diff --git a/third_party/blink/renderer/platform/widget/compositing/layer_tree_settings.cc b/third_party/blink/renderer/platform/widget/compositing/layer_tree_settings.cc
index 41430c3..309cd8b 100644
--- a/third_party/blink/renderer/platform/widget/compositing/layer_tree_settings.cc
+++ b/third_party/blink/renderer/platform/widget/compositing/layer_tree_settings.cc
@@ -157,6 +157,8 @@
   const base::CommandLine& cmd = *base::CommandLine::ForCurrentProcess();
   cc::LayerTreeSettings settings;
 
+  settings.force_preferred_interval_for_video =
+      ::features::IsForcePreferredIntervalForVideoEnabled();
   settings.enable_synchronized_scrolling =
       base::FeatureList::IsEnabled(::features::kSynchronizedScrolling);
   Platform* platform = Platform::Current();
diff --git a/third_party/blink/renderer/platform/wtf/deque.h b/third_party/blink/renderer/platform/wtf/deque.h
index 1863689..0681555 100644
--- a/third_party/blink/renderer/platform/wtf/deque.h
+++ b/third_party/blink/renderer/platform/wtf/deque.h
@@ -695,11 +695,11 @@
   // Bail out for concurrent marking.
   if (!VectorTraits<T>::kCanTraceConcurrently && buffer) {
     if (visitor->DeferredTraceIfConcurrent(
-            {this, [](blink::Visitor* visitor, const void* object) {
-               reinterpret_cast<const Deque<T, inlineCapacity, Allocator>*>(
-                   object)
-                   ->Trace(visitor);
-             }}))
+            {this,
+             [](blink::Visitor* visitor, const void* object) {
+               reinterpret_cast<const Deque*>(object)->Trace(visitor);
+             }},
+            sizeof(Deque)))
       return;
   }
 
diff --git a/third_party/blink/renderer/platform/wtf/hash_table.h b/third_party/blink/renderer/platform/wtf/hash_table.h
index b68c8ad..dc9ecd1 100644
--- a/third_party/blink/renderer/platform/wtf/hash_table.h
+++ b/third_party/blink/renderer/platform/wtf/hash_table.h
@@ -2166,12 +2166,11 @@
   // bail out for concurrent marking
   if (!Traits::kCanTraceConcurrently && table) {
     if (visitor->DeferredTraceIfConcurrent(
-            {this, [](blink::Visitor* visitor, const void* object) {
-               reinterpret_cast<
-                   const HashTable<Key, Value, Extractor, HashFunctions, Traits,
-                                   KeyTraits, Allocator>*>(object)
-                   ->Trace(visitor);
-             }}))
+            {this,
+             [](blink::Visitor* visitor, const void* object) {
+               reinterpret_cast<const HashTable*>(object)->Trace(visitor);
+             }},
+            sizeof(HashTable)))
       return;
   }
 
diff --git a/third_party/blink/renderer/platform/wtf/linked_hash_set.h b/third_party/blink/renderer/platform/wtf/linked_hash_set.h
index 0d9320b..301b9a4 100644
--- a/third_party/blink/renderer/platform/wtf/linked_hash_set.h
+++ b/third_party/blink/renderer/platform/wtf/linked_hash_set.h
@@ -345,12 +345,11 @@
 
     if (!NodeHashTraits::kCanTraceConcurrently && table) {
       if (visitor->DeferredTraceIfConcurrent(
-              {this, [](blink::Visitor* visitor, const void* object) {
-                 reinterpret_cast<const LinkedHashSet<ValueArg, HashFunctions,
-                                                      TraitsArg, Allocator>*>(
-                     object)
-                     ->Trace(visitor);
-               }}))
+              {this,
+               [](blink::Visitor* visitor, const void* object) {
+                 reinterpret_cast<const LinkedHashSet*>(object)->Trace(visitor);
+               }},
+              sizeof(LinkedHashSet)))
         return;
     }
 
diff --git a/third_party/blink/renderer/platform/wtf/list_hash_set.h b/third_party/blink/renderer/platform/wtf/list_hash_set.h
index db6f59f..c824c3b2 100644
--- a/third_party/blink/renderer/platform/wtf/list_hash_set.h
+++ b/third_party/blink/renderer/platform/wtf/list_hash_set.h
@@ -1097,11 +1097,11 @@
 
   if (!NodeTraits::kCanTraceConcurrently && table) {
     if (visitor->DeferredTraceIfConcurrent(
-            {this, [](blink::Visitor* visitor, const void* object) {
-               reinterpret_cast<const ListHashSet<T, inlineCapacity, U, V>*>(
-                   object)
-                   ->Trace(visitor);
-             }}))
+            {this,
+             [](blink::Visitor* visitor, const void* object) {
+               reinterpret_cast<const ListHashSet*>(object)->Trace(visitor);
+             }},
+            sizeof(ListHashSet)))
       return;
   }
 
diff --git a/third_party/blink/renderer/platform/wtf/vector.h b/third_party/blink/renderer/platform/wtf/vector.h
index 11955fb..f39c3a6b 100644
--- a/third_party/blink/renderer/platform/wtf/vector.h
+++ b/third_party/blink/renderer/platform/wtf/vector.h
@@ -2146,11 +2146,11 @@
   // Bail out for concurrent marking.
   if (!VectorTraits<T>::kCanTraceConcurrently) {
     if (visitor->DeferredTraceIfConcurrent(
-            {this, [](blink::Visitor* visitor, const void* object) {
-               reinterpret_cast<const Vector<T, inlineCapacity, Allocator>*>(
-                   object)
-                   ->Trace(visitor);
-             }}))
+            {this,
+             [](blink::Visitor* visitor, const void* object) {
+               reinterpret_cast<const Vector*>(object)->Trace(visitor);
+             }},
+            sizeof(Vector)))
       return;
   }
 
diff --git a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
index d1b507b..53f7a43d 100755
--- a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
+++ b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
@@ -137,14 +137,17 @@
             'base::as_signed',
             'base::as_unsigned',
             'base::checked_cast',
-            'base::strict_cast',
             'base::saturated_cast',
+            'base::strict_cast',
+            'base::Ceil',
+            'base::Floor',
+            'base::IsTypeInRangeForNumericType',
+            'base::IsValueInRangeForNumericType',
+            'base::IsValueNegative',
+            'base::MakeStrictNum',
+            'base::Round',
             'base::SafeUnsignedAbs',
             'base::StrictNumeric',
-            'base::MakeStrictNum',
-            'base::IsValueInRangeForNumericType',
-            'base::IsTypeInRangeForNumericType',
-            'base::IsValueNegative',
 
             # //base/strings/char_traits.h.
             'base::CharTraits',
diff --git a/third_party/blink/tools/blinkpy/third_party/README.chromium b/third_party/blink/tools/blinkpy/third_party/README.chromium
index 569ee89..55ce0a81 100644
--- a/third_party/blink/tools/blinkpy/third_party/README.chromium
+++ b/third_party/blink/tools/blinkpy/third_party/README.chromium
@@ -22,7 +22,7 @@
 Name: web-platform-tests - Test Suites for Web Platform specifications
 Short Name: wpt
 URL: https://github.com/web-platform-tests/wpt/
-Version: b13c1d42b3aaf52a4e7b0b18795aeefb17a62eb6
+Version: 149ce86ecb408e9df28e1f55c236ae4388e77cc0
 License: LICENSES FOR W3C TEST SUITES (https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html)
 License File: wpt/wpt/LICENSE.md
 Security Critical: no
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/checkout.sh b/third_party/blink/tools/blinkpy/third_party/wpt/checkout.sh
index b117cd6..b9e124e 100755
--- a/third_party/blink/tools/blinkpy/third_party/wpt/checkout.sh
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/checkout.sh
@@ -9,7 +9,7 @@
 
 TARGET_DIR=$DIR/wpt
 REMOTE_REPO="https://github.com/web-platform-tests/wpt.git"
-WPT_HEAD=b13c1d42b3aaf52a4e7b0b18795aeefb17a62eb6
+WPT_HEAD=149ce86ecb408e9df28e1f55c236ae4388e77cc0
 
 function clone {
   # Remove existing repo if already exists.
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/ci/commands.json b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/ci/commands.json
index c9cd7c4..929d2c09 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/ci/commands.json
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/ci/commands.json
@@ -46,5 +46,15 @@
         "pyyaml",
         "taskcluster"
     ]
+  },
+  "tc-sink-task": {
+    "path": "tc/sink_task.py",
+    "parser": "get_parser",
+    "script": "run",
+    "help": "Run the sink task",
+    "virtualenv": true,
+    "install": [
+        "taskcluster"
+    ]
   }
 }
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/gitignore/gitignore.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/gitignore/gitignore.py
index 0f3d9450..2a27615 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/gitignore/gitignore.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/gitignore/gitignore.py
@@ -1,7 +1,7 @@
 import re
 import os
 import itertools
-from six import itervalues, iteritems
+from six import ensure_binary, itervalues, iteritems
 from collections import defaultdict
 
 MYPY = False
@@ -26,70 +26,72 @@
 
 
 def fnmatch_translate(pat):
-    # type: (str) -> Tuple[bool, Pattern[str]]
+    # type: (bytes) -> Tuple[bool, Pattern[bytes]]
     parts = []
     seq = None
     i = 0
-    any_char = "[^/]"
-    if pat[0] == "/":
-        parts.append("^")
+    any_char = b"[^/]"
+    if pat[0:1] == b"/":
+        parts.append(b"^")
         pat = pat[1:]
     else:
         # By default match the entire path up to a /
         # but if / doesn't appear in the pattern we will mark is as
         # a name pattern and just produce a pattern that matches against
         # the filename
-        parts.append("^(?:.*/)?")
+        parts.append(b"^(?:.*/)?")
 
     name_pattern = True
-    if pat[-1] == "/":
+    if pat[-1:] == b"/":
         # If the last character is / match this directory or any subdirectory
         pat = pat[:-1]
-        suffix = "(?:/|$)"
+        suffix = b"(?:/|$)"
     else:
-        suffix = "$"
+        suffix = b"$"
     while i < len(pat):
-        c = pat[i]
-        if c == "\\":
+        c = pat[i:i+1]
+        if c == b"\\":
             if i < len(pat) - 1:
                 i += 1
-                c = pat[i]
+                c = pat[i:i+1]
                 parts.append(re.escape(c))
             else:
                 raise ValueError
         elif seq is not None:
             # TODO: this doesn't really handle invalid sequences in the right way
-            if c == "]":
+            if c == b"]":
                 seq = None
-                if parts[-1] == "[":
+                if parts[-1:] == b"[":
                     parts = parts[:-1]
-                elif parts[-1] == "^" and parts[-2] == "[":
+                elif parts[-1:] == b"^" and parts[-2:-1] == b"[":
                     parts = parts[:-2]
                 else:
                     parts.append(c)
-            elif c == "-":
+            elif c == b"-":
                 parts.append(c)
+            elif c == b"[":
+                raise ValueError
             else:
-                parts += re.escape(c)
-        elif c == "[":
-            parts.append("[")
-            if i < len(pat) - 1 and pat[i+1] in ("!", "^"):
-                parts.append("^")
+                parts.append(re.escape(c))
+        elif c == b"[":
+            parts.append(b"[")
+            if i < len(pat) - 1 and pat[i+1:i+2] in (b"!", b"^"):
+                parts.append(b"^")
                 i += 1
             seq = i
-        elif c == "*":
-            if i < len(pat) - 1 and pat[i+1] == "*":
-                if i > 0 and pat[i-1] != "/":
+        elif c == b"*":
+            if i < len(pat) - 1 and pat[i+1:i+2] == b"*":
+                if i > 0 and pat[i-1:i] != b"/":
                     raise ValueError
-                parts.append(".*")
+                parts.append(b".*")
                 i += 1
-                if i < len(pat) - 1 and pat[i+1] != "/":
+                if i < len(pat) - 1 and pat[i+1:i+2] != b"/":
                     raise ValueError
             else:
-                parts.append(any_char + "*")
-        elif c == "?":
+                parts.append(any_char + b"*")
+        elif c == b"?":
             parts.append(any_char)
-        elif c == "/" and not seq:
+        elif c == b"/" and not seq:
             name_pattern = False
             parts.append(c)
         else:
@@ -97,31 +99,31 @@
         i += 1
 
     if name_pattern:
-        parts[0] = "^"
+        parts[0] = b"^"
 
     if seq is not None:
         raise ValueError
     parts.append(suffix)
     try:
-        return name_pattern, re.compile("".join(parts))
+        return name_pattern, re.compile(b"".join(parts))
     except Exception:
         raise ValueError
 
 # Regexp matching rules that have to be converted to patterns
-pattern_re = re.compile(r".*[\*\[\?]")
+pattern_re = re.compile(br".*[\*\[\?]")
 
 
 def parse_line(line):
-    # type: (str) -> Optional[Tuple[bool, bool, bool, Union[Tuple[str, ...], Tuple[bool, Pattern[str]]]]]
+    # type: (bytes) -> Optional[Tuple[bool, bool, bool, Union[Tuple[bytes, ...], Tuple[bool, Pattern[bytes]]]]]
     line = line.rstrip()
-    if not line or line[0] == "#":
+    if not line or line[0:1] == b"#":
         return None
 
-    invert = line[0] == "!"
+    invert = line[0:1] == b"!"
     if invert:
         line = line[1:]
 
-    dir_only = line[-1] == "/"
+    dir_only = line[-1:] == b"/"
 
     if dir_only:
         line = line[:-1]
@@ -129,7 +131,7 @@
     # Could make a special case for **/foo, but we don't have any patterns like that
     if not invert and not pattern_re.match(line):
         literal = True
-        pattern = tuple(line.rsplit("/", 1))  # type: Union[Tuple[str, ...], Tuple[bool, Pattern[str]]]
+        pattern = tuple(line.rsplit(b"/", 1))  # type: Union[Tuple[bytes, ...], Tuple[bool, Pattern[bytes]]]
     else:
         pattern = fnmatch_translate(line)
         literal = False
@@ -139,9 +141,9 @@
 
 class PathFilter(object):
     def __init__(self, root, extras=None, cache=None):
-        # type: (str, Optional[List[str]], Optional[MutableMapping[str, bool]]) -> None
+        # type: (bytes, Optional[List[bytes]], Optional[MutableMapping[bytes, bool]]) -> None
         if root:
-            ignore_path = os.path.join(root, ".gitignore")  # type: Optional[str]
+            ignore_path = os.path.join(root, b".gitignore")  # type: Optional[bytes]
         else:
             ignore_path = None
         if not ignore_path and not extras:
@@ -149,32 +151,32 @@
             return
         self.trivial = False
 
-        self.literals_file = defaultdict(dict)  # type: Dict[Optional[str], Dict[str, List[Tuple[bool, Pattern[str]]]]]
-        self.literals_dir = defaultdict(dict)  # type: Dict[Optional[str], Dict[str, List[Tuple[bool, Pattern[str]]]]]
-        self.patterns_file = []  # type: List[Tuple[Tuple[bool, Pattern[str]], List[Tuple[bool, Pattern[str]]]]]
-        self.patterns_dir = []  # type: List[Tuple[Tuple[bool, Pattern[str]], List[Tuple[bool, Pattern[str]]]]]
-        self.cache = cache or {}  # type: MutableMapping[str, bool]
+        self.literals_file = defaultdict(dict)  # type: Dict[Optional[bytes], Dict[bytes, List[Tuple[bool, Pattern[bytes]]]]]
+        self.literals_dir = defaultdict(dict)  # type: Dict[Optional[bytes], Dict[bytes, List[Tuple[bool, Pattern[bytes]]]]]
+        self.patterns_file = []  # type: List[Tuple[Tuple[bool, Pattern[bytes]], List[Tuple[bool, Pattern[bytes]]]]]
+        self.patterns_dir = []  # type: List[Tuple[Tuple[bool, Pattern[bytes]], List[Tuple[bool, Pattern[bytes]]]]]
+        self.cache = cache or {}  # type: MutableMapping[bytes, bool]
 
         if extras is None:
             extras = []
 
         if ignore_path and os.path.exists(ignore_path):
-            args = ignore_path, extras  # type: Tuple[Optional[str], List[str]]
+            args = ignore_path, extras  # type: Tuple[Optional[bytes], List[bytes]]
         else:
             args = None, extras
         self._read_ignore(*args)
 
     def _read_ignore(self, ignore_path, extras):
-        # type: (Optional[str], List[str]) -> None
+        # type: (Optional[bytes], List[bytes]) -> None
         if ignore_path is not None:
-            with open(ignore_path) as f:
+            with open(ignore_path, "rb") as f:
                 for line in f:
                     self._read_line(line)
         for line in extras:
             self._read_line(line)
 
     def _read_line(self, line):
-        # type: (str) -> None
+        # type: (bytes) -> None
         parsed = parse_line(line)
         if not parsed:
             return
@@ -186,13 +188,13 @@
             # overriden by an exclude rule
             assert not literal
             if MYPY:
-                rule = cast(Tuple[bool, Pattern[str]], rule)
+                rule = cast(Tuple[bool, Pattern[bytes]], rule)
             if not dir_only:
                 rules_iter = itertools.chain(
                     itertools.chain(*(iteritems(item) for item in itervalues(self.literals_dir))),
                     itertools.chain(*(iteritems(item) for item in itervalues(self.literals_file))),
                     self.patterns_dir,
-                    self.patterns_file)  # type: Iterable[Tuple[Any, List[Tuple[bool, Pattern[str]]]]]
+                    self.patterns_file)  # type: Iterable[Tuple[Any, List[Tuple[bool, Pattern[bytes]]]]]
             else:
                 rules_iter = itertools.chain(
                     itertools.chain(*(iteritems(item) for item in itervalues(self.literals_dir))),
@@ -203,9 +205,9 @@
         else:
             if literal:
                 if MYPY:
-                    rule = cast(Tuple[str, ...], rule)
+                    rule = cast(Tuple[bytes, ...], rule)
                 if len(rule) == 1:
-                    dir_name, pattern = None, rule[0]  # type: Tuple[Optional[str], str]
+                    dir_name, pattern = None, rule[0]  # type: Tuple[Optional[bytes], bytes]
                 else:
                     dir_name, pattern = rule
                 self.literals_dir[dir_name][pattern] = []
@@ -213,31 +215,31 @@
                     self.literals_file[dir_name][pattern] = []
             else:
                 if MYPY:
-                    rule = cast(Tuple[bool, Pattern[str]], rule)
+                    rule = cast(Tuple[bool, Pattern[bytes]], rule)
                 self.patterns_dir.append((rule, []))
                 if not dir_only:
                     self.patterns_file.append((rule, []))
 
     def filter(self,
-               iterator  # type: Iterable[Tuple[str, List[Tuple[str, T]], List[Tuple[str, T]]]]
+               iterator  # type: Iterable[Tuple[bytes, List[Tuple[bytes, T]], List[Tuple[bytes, T]]]]
                ):
-        # type: (...) -> Iterable[Tuple[str, List[Tuple[str, T]], List[Tuple[str, T]]]]
+        # type: (...) -> Iterable[Tuple[bytes, List[Tuple[bytes, T]], List[Tuple[bytes, T]]]]
         empty = {}  # type: Dict[Any, Any]
         for dirpath, dirnames, filenames in iterator:
             orig_dirpath = dirpath
-            if os.path.sep != "/":
-                dirpath = dirpath.replace(os.path.sep, "/")
+            if ensure_binary(os.path.sep) != b"/":
+                dirpath = dirpath.replace(ensure_binary(os.path.sep), b"/")
 
-            keep_dirs = []  # type: List[Tuple[str, T]]
-            keep_files = []  # type: List[Tuple[str, T]]
+            keep_dirs = []  # type: List[Tuple[bytes, T]]
+            keep_files = []  # type: List[Tuple[bytes, T]]
 
             for iter_items, literals, patterns, target, suffix in [
-                    (dirnames, self.literals_dir, self.patterns_dir, keep_dirs, "/"),
-                    (filenames, self.literals_file, self.patterns_file, keep_files, "")]:
+                    (dirnames, self.literals_dir, self.patterns_dir, keep_dirs, b"/"),
+                    (filenames, self.literals_file, self.patterns_file, keep_files, b"")]:
                 for item in iter_items:
                     name = item[0]
                     if dirpath:
-                        path = "%s/%s" % (dirpath, name) + suffix
+                        path = b"%s/%s" % (dirpath, name) + suffix
                     else:
                         path = name + suffix
                     if path in self.cache:
@@ -269,13 +271,13 @@
                             target.append(item)
 
             dirnames[:] = keep_dirs
-            assert not any(".git" == name for name, _ in dirnames)
+            assert not any(b".git" == name for name, _ in dirnames)
             yield orig_dirpath, dirnames, keep_files
 
     def __call__(self,
-                 iterator  # type: Iterable[Tuple[str, List[Tuple[str, T]], List[Tuple[str, T]]]]
+                 iterator  # type: Iterable[Tuple[bytes, List[Tuple[bytes, T]], List[Tuple[bytes, T]]]]
                  ):
-        # type: (...) -> Iterable[Tuple[str, List[Tuple[str, T]], List[Tuple[str, T]]]]
+        # type: (...) -> Iterable[Tuple[bytes, List[Tuple[bytes, T]], List[Tuple[bytes, T]]]]
         if self.trivial:
             return iterator
 
@@ -283,5 +285,5 @@
 
 
 def has_ignore(dirpath):
-    # type: (str) -> bool
-    return os.path.exists(os.path.join(dirpath, ".gitignore"))
+    # type: (bytes) -> bool
+    return os.path.exists(os.path.join(dirpath, b".gitignore"))
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/lint/fnmatch.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/lint/fnmatch.py
index 3d9ee23..0c45029 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/lint/fnmatch.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/lint/fnmatch.py
@@ -6,23 +6,23 @@
 MYPY = False
 if MYPY:
     # MYPY is set to True when run under Mypy.
-    from typing import AnyStr
     from typing import Iterable
     from typing import List
+    from typing import Text
 
 
 __all__ = ["fnmatch", "fnmatchcase", "filter", "translate"]
 
 
 def fnmatch(name, pat):
-    # type: (AnyStr, AnyStr) -> bool
+    # type: (Text, Text) -> bool
     name = os.path.normcase(name)
     pat = os.path.normcase(pat)
     return fnmatchcase(name, pat)
 
 
 def fnmatchcase(name, pat):
-    # type: (AnyStr, AnyStr) -> bool
+    # type: (Text, Text) -> bool
     if '?' not in pat and '[' not in pat:
         wildcards = pat.count("*")
         if wildcards == 0:
@@ -35,7 +35,7 @@
 
 
 def filter(names, pat):
-    # type: (Iterable[AnyStr], AnyStr) -> List[AnyStr]
+    # type: (Iterable[Text], Text) -> List[Text]
     return [n for n in names if fnmatch(n, pat)]
 
 
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/lint/lint.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/lint/lint.py
index fc1a816..14052e7 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/lint/lint.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/lint/lint.py
@@ -3,6 +3,7 @@
 import abc
 import argparse
 import ast
+import io
 import json
 import logging
 import os
@@ -21,7 +22,7 @@
 from ..manifest.vcs import walk
 
 from ..manifest.sourcefile import SourceFile, js_meta_re, python_meta_re, space_chars, get_any_variants
-from six import binary_type, iteritems, itervalues, with_metaclass
+from six import binary_type, ensure_binary, ensure_text, iteritems, itervalues, with_metaclass
 from six.moves import range
 from six.moves.urllib.parse import urlsplit, urljoin
 
@@ -39,7 +40,6 @@
     from typing import Text
     from typing import Tuple
     from typing import Type
-    from typing import Union
 
     # The Ignorelist is a two level dictionary. The top level is indexed by
     # error names (e.g. 'TRAILING WHITESPACE'). Each of those then has a map of
@@ -51,6 +51,7 @@
 
 logger = None  # type: Optional[logging.Logger]
 
+
 def setup_logging(prefix=False):
     # type: (bool) -> None
     global logger
@@ -89,24 +90,27 @@
 
 %s: %s"""
 
+
 def all_filesystem_paths(repo_root, subdir=None):
-    # type: (str, Optional[str]) -> Iterable[str]
-    path_filter = PathFilter(repo_root, extras=[str(".git/")])
+    # type: (Text, Optional[Text]) -> Iterable[Text]
+    path_filter = PathFilter(repo_root.encode("utf8"),
+                             extras=[ensure_binary(".git/")])
     if subdir:
-        expanded_path = subdir
+        expanded_path = subdir.encode("utf8")
+        subdir_str = expanded_path
     else:
-        expanded_path = repo_root
+        expanded_path = repo_root.encode("utf8")
     for dirpath, dirnames, filenames in path_filter(walk(expanded_path)):
         for filename, _ in filenames:
             path = os.path.join(dirpath, filename)
             if subdir:
-                path = os.path.join(subdir, path)
+                path = os.path.join(subdir_str, path)
             assert not os.path.isabs(path), path
-            yield path
+            yield ensure_text(path)
 
 
 def _all_files_equal(paths):
-    # type: (Iterable[str]) -> bool
+    # type: (Iterable[Text]) -> bool
     """
     Checks all the paths are files that are byte-for-byte identical
 
@@ -145,21 +149,21 @@
 
 
 def check_path_length(repo_root, path):
-    # type: (str, str) -> List[rules.Error]
+    # type: (Text, Text) -> List[rules.Error]
     if len(path) + 1 > 150:
         return [rules.PathLength.error(path, (path, len(path) + 1))]
     return []
 
 
 def check_file_type(repo_root, path):
-    # type: (str, str) -> List[rules.Error]
+    # type: (Text, Text) -> List[rules.Error]
     if os.path.islink(path):
         return [rules.FileType.error(path, (path, "symlink"))]
     return []
 
 
 def check_worker_collision(repo_root, path):
-    # type: (str, str) -> List[rules.Error]
+    # type: (Text, Text) -> List[rules.Error]
     endings = [(".any.html", ".any.js"),
                (".any.worker.html", ".any.js"),
                (".worker.html", ".worker.js")]
@@ -170,7 +174,7 @@
 
 
 def check_gitignore_file(repo_root, path):
-    # type: (str, str) -> List[rules.Error]
+    # type: (Text, Text) -> List[rules.Error]
     if not path.endswith(".gitignore"):
         return []
 
@@ -190,7 +194,7 @@
 
 
 def check_ahem_copy(repo_root, path):
-    # type: (str, str) -> List[rules.Error]
+    # type: (Text, Text) -> List[rules.Error]
     lpath = path.lower()
     if "ahem" in lpath and lpath.endswith(".ttf"):
         return [rules.AhemCopy.error(path)]
@@ -198,7 +202,7 @@
 
 
 def check_git_ignore(repo_root, paths):
-    # type: (str, List[str]) -> List[rules.Error]
+    # type: (Text, List[Text]) -> List[rules.Error]
     errors = []
     with tempfile.TemporaryFile('w+') as f:
         f.write('\n'.join(paths))
@@ -206,12 +210,13 @@
         try:
             matches = subprocess.check_output(
                 ["git", "check-ignore", "--verbose", "--no-index", "--stdin"], stdin=f)
-            for match in matches.strip().split('\n'):
-                match_filter, path = match.split()
-                _, _, filter_string = match_filter.split(':')
+            for match in matches.strip().split(b'\n'):
+                match_filter, path_bytes = match.split()
+                _, _, filter_string = match_filter.split(b':')
                 # If the matching filter reported by check-ignore is a special-case exception,
                 # that's fine. Otherwise, it requires a new special-case exception.
-                if filter_string[0] != '!':
+                if filter_string[0:1] != b'!':
+                    path = path_bytes.decode("utf8")
                     errors.append(rules.IgnoredPath.error(path, (path,)))
         except subprocess.CalledProcessError:
             # Nonzero return code means that no match exists.
@@ -225,7 +230,7 @@
 
 
 def check_css_globally_unique(repo_root, paths):
-    # type: (str, List[str]) -> List[rules.Error]
+    # type: (Text, List[Text]) -> List[rules.Error]
     """
     Checks that CSS filenames are sufficiently unique
 
@@ -243,32 +248,29 @@
     :returns: a list of errors found in ``paths``
 
     """
-    test_files = defaultdict(set)  # type: Dict[Union[bytes, Text], Set[str]]
-    ref_files = defaultdict(set)  # type: Dict[Union[bytes, Text], Set[str]]
-    support_files = defaultdict(set)  # type: Dict[Union[bytes, Text], Set[str]]
+    test_files = defaultdict(set)  # type: Dict[Text, Set[Text]]
+    ref_files = defaultdict(set)  # type: Dict[Text, Set[Text]]
+    support_files = defaultdict(set)  # type: Dict[Text, Set[Text]]
 
     for path in paths:
         if os.name == "nt":
-            if isinstance(path, binary_type):
-                path = path.replace(b"\\", b"/")
-            else:
-                path = path.replace(u"\\", u"/")
+            path = path.replace(u"\\", u"/")
 
-        if not path.startswith("css/"):
+        if not path.startswith(u"css/"):
             continue
 
-        source_file = SourceFile(repo_root, path, "/")
+        source_file = SourceFile(repo_root, path, u"/")
         if source_file.name_is_non_test:
             # If we're name_is_non_test for a reason apart from support, ignore it.
             # We care about support because of the requirement all support files in css/ to be in
             # a support directory; see the start of check_parsed.
-            offset = path.find("/support/")
+            offset = path.find(u"/support/")
             if offset == -1:
                 continue
 
             parts = source_file.dir_path.split(os.path.sep)
             if (parts[0] in source_file.root_dir_non_test or
-                any(item in source_file.dir_non_test - {"support"} for item in parts) or
+                any(item in source_file.dir_non_test - {u"support"} for item in parts) or
                 any(parts[:len(non_test_path)] == list(non_test_path) for non_test_path in source_file.dir_path_non_test)):
                 continue
 
@@ -277,11 +279,8 @@
         elif source_file.name_is_reference:
             ref_files[source_file.name].add(path)
         else:
-            test_name = source_file.name  # type: Union[bytes, Text]
-            if isinstance(test_name, bytes):
-                test_name = test_name.replace(b'-manual', b'')
-            else:
-                test_name = test_name.replace(u'-manual', u'')
+            test_name = source_file.name  # type: Text
+            test_name = test_name.replace(u'-manual', u'')
             test_files[test_name].add(path)
 
     errors = []
@@ -290,9 +289,9 @@
         if len(colliding) > 1:
             if not _all_files_equal([os.path.join(repo_root, x) for x in colliding]):
                 # Only compute by_spec if there are prima-facie collisions because of cost
-                by_spec = defaultdict(set)  # type: Dict[Text, Set[str]]
+                by_spec = defaultdict(set)  # type: Dict[Text, Set[Text]]
                 for path in colliding:
-                    source_file = SourceFile(repo_root, path, "/")
+                    source_file = SourceFile(repo_root, path, u"/")
                     for link in source_file.spec_links:
                         for r in (drafts_csswg_re, w3c_tr_re, w3c_dev_re):
                             m = r.match(link)
@@ -324,7 +323,7 @@
 
 
 def check_unique_testharness_basenames(repo_root, paths):
-    # type: (str, List[str]) -> List[rules.Error]
+    # type: (Text, List[Text]) -> List[rules.Error]
     """
     Checks that all testharness files have unique basename paths.
 
@@ -359,7 +358,7 @@
 
 
 def parse_ignorelist(f):
-    # type: (IO[bytes]) -> Tuple[Ignorelist, Set[Text]]
+    # type: (IO[Text]) -> Tuple[Ignorelist, Set[Text]]
     """
     Parse the ignorelist file given by `f`, and return the parsed structure.
 
@@ -440,7 +439,7 @@
 
 
 def check_regexp_line(repo_root, path, f):
-    # type: (str, str, IO[bytes]) -> List[rules.Error]
+    # type: (Text, Text, IO[bytes]) -> List[rules.Error]
     errors = []  # type: List[rules.Error]
 
     applicable_regexps = [regexp for regexp in regexps if regexp.applies(path)]
@@ -454,7 +453,7 @@
 
 
 def check_parsed(repo_root, path, f):
-    # type: (str, str, IO[bytes]) -> List[rules.Error]
+    # type: (Text, Text, IO[bytes]) -> List[rules.Error]
     source_file = SourceFile(repo_root, path, "/", contents=f.read())
 
     errors = []  # type: List[rules.Error]
@@ -630,7 +629,7 @@
 ast_checkers = [item() for item in [OpenModeCheck]]
 
 def check_python_ast(repo_root, path, f):
-    # type: (str, str, IO[bytes]) -> List[rules.Error]
+    # type: (Text, Text, IO[bytes]) -> List[rules.Error]
     # *.quic.py are Python 3 only and cannot be parsed by Python 2.
     if not path.endswith(".py") or path.endswith(".quic.py"):
         return []
@@ -652,7 +651,7 @@
 
 
 def check_global_metadata(value):
-    # type: (str) -> Iterable[Tuple[Type[rules.Rule], Tuple[Any, ...]]]
+    # type: (bytes) -> Iterable[Tuple[Type[rules.Rule], Tuple[Any, ...]]]
     global_values = {item.strip().decode("utf8") for item in value.split(b",") if item.strip()}
 
     # TODO: this could check for duplicates and such
@@ -662,7 +661,7 @@
 
 
 def check_script_metadata(repo_root, path, f):
-    # type: (str, str, IO[bytes]) -> List[rules.Error]
+    # type: (Text, Text, IO[bytes]) -> List[rules.Error]
     if path.endswith((".worker.js", ".any.js")):
         meta_re = js_meta_re
         broken_metadata = broken_js_metadata
@@ -705,15 +704,15 @@
     return errors
 
 
-ahem_font_re = re.compile(b"font.*:.*ahem", flags=re.IGNORECASE)
+ahem_font_re = re.compile(br"font.*:.*ahem", flags=re.IGNORECASE)
 # Ahem can appear either in the global location or in the support
 # directory for legacy Mozilla imports
-ahem_stylesheet_re = re.compile(b"\/fonts\/ahem\.css|support\/ahem.css",
+ahem_stylesheet_re = re.compile(br"\/fonts\/ahem\.css|support\/ahem.css",
                                 flags=re.IGNORECASE)
 
 
 def check_ahem_system_font(repo_root, path, f):
-    # type: (str, str, IO[bytes]) -> List[rules.Error]
+    # type: (Text, Text, IO[bytes]) -> List[rules.Error]
     if not path.endswith((".html", ".htm", ".xht", ".xhtml")):
         return []
     contents = f.read()
@@ -724,7 +723,7 @@
 
 
 def check_path(repo_root, path):
-    # type: (str, str) -> List[rules.Error]
+    # type: (Text, Text) -> List[rules.Error]
     """
     Runs lints that check the file path.
 
@@ -740,7 +739,7 @@
 
 
 def check_all_paths(repo_root, paths):
-    # type: (str, List[str]) -> List[rules.Error]
+    # type: (Text, List[Text]) -> List[rules.Error]
     """
     Runs lints that check all paths globally.
 
@@ -756,7 +755,7 @@
 
 
 def check_file_contents(repo_root, path, f):
-    # type: (str, str, IO[bytes]) -> List[rules.Error]
+    # type: (Text, Text, IO[bytes]) -> List[rules.Error]
     """
     Runs lints that check the file contents.
 
@@ -824,25 +823,23 @@
 
 
 def changed_files(wpt_root):
-    # type: (str) -> List[Text]
+    # type: (Text) -> List[Text]
     revish = testfiles.get_revish(revish=None)
     changed, _ = testfiles.files_changed(revish, None, include_uncommitted=True, include_new=True)
     return [os.path.relpath(item, wpt_root) for item in changed]
 
 
 def lint_paths(kwargs, wpt_root):
-    # type: (Dict[str, Any], str) -> List[str]
-    if kwargs.get(str("paths")):
+    # type: (Dict[Text, Any], Text) -> List[Text]
+    if kwargs.get("paths"):
         paths = []
-        for path in kwargs.get(str("paths"), []):
+        for path in kwargs.get("paths", []):
             if os.path.isdir(path):
                 path_dir = list(all_filesystem_paths(wpt_root, path))
                 paths.extend(path_dir)
             elif os.path.isfile(path):
                 paths.append(os.path.relpath(os.path.abspath(path), wpt_root))
-
-
-    elif kwargs[str("all")]:
+    elif kwargs["all"]:
         paths = list(all_filesystem_paths(wpt_root))
     else:
         changed_paths = changed_files(wpt_root)
@@ -852,7 +849,7 @@
             if path == "lint.ignore" or path.startswith("tools/lint/"):
                 force_all = True
                 break
-        paths = (list(changed_paths) if not force_all  # type: ignore
+        paths = (list(changed_paths) if not force_all
                  else list(all_filesystem_paths(wpt_root)))
 
     return paths
@@ -867,47 +864,51 @@
                         help="Output machine-readable JSON format")
     parser.add_argument("--markdown", action="store_true",
                         help="Output markdown")
-    parser.add_argument("--repo-root", help="The WPT directory. Use this "
+    parser.add_argument("--repo-root", type=ensure_text,
+                        help="The WPT directory. Use this "
                         "option if the lint script exists outside the repository")
-    parser.add_argument("--ignore-glob", help="Additional file glob to ignore.")
+    parser.add_argument("--ignore-glob", type=ensure_text, action="append",
+                        help="Additional file glob to ignore (repeat to add more)")
     parser.add_argument("--all", action="store_true", help="If no paths are passed, try to lint the whole "
                         "working directory, not just files that changed")
     return parser
 
 
-def main(**kwargs):
+def main(**kwargs_str):
     # type: (**Any) -> int
+    kwargs = {ensure_text(key): value for key, value in iteritems(kwargs_str)}
+
     assert logger is not None
-    if kwargs.get(str("json")) and kwargs.get(str("markdown")):
+    if kwargs.get("json") and kwargs.get("markdown"):
         logger.critical("Cannot specify --json and --markdown")
         sys.exit(2)
 
-    repo_root = kwargs.get(str('repo_root')) or localpaths.repo_root
-    output_format = {(True, False): str("json"),
-                     (False, True): str("markdown"),
-                     (False, False): str("normal")}[(kwargs.get(str("json"), False),
-                                                     kwargs.get(str("markdown"), False))]
+    repo_root = kwargs.get('repo_root') or localpaths.repo_root
+    output_format = {(True, False): "json",
+                     (False, True): "markdown",
+                     (False, False): "normal"}[(kwargs.get("json", False),
+                                                kwargs.get("markdown", False))]
 
     if output_format == "markdown":
         setup_logging(True)
 
     paths = lint_paths(kwargs, repo_root)
 
-    ignore_glob = kwargs.get(str("ignore_glob")) or str()
+    ignore_glob = kwargs.get("ignore_glob", [])
 
-    return lint(repo_root, paths, output_format, str(ignore_glob))
+    return lint(repo_root, paths, output_format, ignore_glob)
 
 
-def lint(repo_root, paths, output_format, ignore_glob=str()):
-    # type: (str, List[str], str, str) -> int
+def lint(repo_root, paths, output_format, ignore_glob=None):
+    # type: (Text, List[Text], Text, Optional[List[Text]]) -> int
     error_count = defaultdict(int)  # type: Dict[Text, int]
     last = None
 
-    with open(os.path.join(repo_root, "lint.ignore")) as f:
+    with io.open(os.path.join(repo_root, "lint.ignore"), "r") as f:
         ignorelist, skipped_files = parse_ignorelist(f)
 
     if ignore_glob:
-        skipped_files.add(ignore_glob)
+        skipped_files |= set(ignore_glob)
 
     output_errors = {"json": output_errors_json,
                      "markdown": output_errors_markdown,
@@ -948,8 +949,8 @@
         last = process_errors(errors) or last
 
         if not os.path.isdir(abs_path):
-            with open(abs_path, 'rb') as f:
-                errors = check_file_contents(repo_root, path, f)
+            with io.open(abs_path, 'rb') as test_file:
+                errors = check_file_contents(repo_root, path, test_file)
                 last = process_errors(errors) or last
 
     errors = check_all_paths(repo_root, paths)
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/lint/rules.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/lint/rules.py
index 695f6cd..6ffd749 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/lint/rules.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/lint/rules.py
@@ -354,7 +354,7 @@
         self._re = re.compile(self.pattern)  # type: Pattern[bytes]
 
     def applies(self, path):
-        # type: (str) -> bool
+        # type: (Text) -> bool
         return (self.file_extensions is None or
                 os.path.splitext(path)[1] in self.file_extensions)
 
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/localpaths.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/localpaths.py
index ce3b41e..93cff2b 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/localpaths.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/localpaths.py
@@ -2,7 +2,6 @@
 import sys
 
 here = os.path.abspath(os.path.split(__file__)[0])
-repo_root = os.path.abspath(os.path.join(here, os.pardir))
 
 sys.path.insert(0, os.path.join(here))
 sys.path.insert(0, os.path.join(here, "wptserve"))
@@ -28,3 +27,7 @@
 
 if sys.version_info[0] == 2:
     sys.path.insert(0, os.path.join(here, "third_party", "enum"))
+
+# We can't import six until we've set the path above.
+from six import ensure_text
+repo_root = ensure_text(os.path.abspath(os.path.join(here, os.pardir)))
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/XMLParser.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/XMLParser.py
index 6f5ff4d3..80aa3b5 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/XMLParser.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/XMLParser.py
@@ -6,6 +6,8 @@
 from xml.parsers import expat
 import xml.etree.ElementTree as etree  # noqa: N813
 
+from six import text_type
+
 MYPY = False
 if MYPY:
     # MYPY is set to True when run under Mypy.
@@ -24,9 +26,9 @@
     err.position = e.lineno, e.offset
     raise err
 
-_names = {}  # type: Dict[str, str]
+_names = {}  # type: Dict[Text, Text]
 def _fixname(key):
-    # type: (str) -> str
+    # type: (Text) -> Text
     try:
         name = _names[key]
     except KeyError:
@@ -54,7 +56,7 @@
     Python does, rather than just those supported by expat.
     """
     def __init__(self, encoding=None):
-        # type: (Optional[str]) -> None
+        # type: (Optional[Text]) -> None
         self._parser = expat.ParserCreate(encoding, "}")
         self._target = etree.TreeBuilder()
         # parser settings
@@ -63,6 +65,9 @@
         self._parser.SetParamEntityParsing(expat.XML_PARAM_ENTITY_PARSING_UNLESS_STANDALONE)
         # parser callbacks
         self._parser.XmlDeclHandler = self._xml_decl
+        # mypy generates a type error in py2 because it wants
+        # StartElementHandler to take str, List[str]. But the code
+        # seems to always pass in Text to this function
         self._parser.StartElementHandler = self._start
         self._parser.EndElementHandler = self._end
         self._parser.CharacterDataHandler = self._data
@@ -70,32 +75,33 @@
         self._parser.SkippedEntityHandler = self._skipped  # type: ignore
         # used for our horrible re-encoding hack
         self._fed_data = []  # type: Optional[List[bytes]]
-        self._read_encoding = None  # type: Optional[str]
+        self._read_encoding = None  # type: Optional[Text]
 
     def _xml_decl(self, version, encoding, standalone):
-        # type: (str, Optional[str], int) -> None
+        # type: (Text, Optional[Text], int) -> None
         self._read_encoding = encoding
 
     def _start(self, tag, attrib_in):
-        # type: (str, List[str]) -> etree.Element
+        # type: (Text, List[str]) -> etree.Element
+        assert isinstance(tag, text_type)
         self._fed_data = None
         tag = _fixname(tag)
-        attrib = OrderedDict()  # type: Dict[Union[str, Text], Union[str, Text]]
+        attrib = OrderedDict()  # type: Dict[Union[bytes, Text], Union[bytes, Text]]
         if attrib_in:
             for i in range(0, len(attrib_in), 2):
                 attrib[_fixname(attrib_in[i])] = attrib_in[i+1]
         return self._target.start(tag, attrib)
 
     def _data(self, text):
-        # type: (str) -> None
+        # type: (Text) -> None
         self._target.data(text)
 
     def _end(self, tag):
-        # type: (str) -> etree.Element
+        # type: (Text) -> etree.Element
         return self._target.end(_fixname(tag))
 
     def _external(self, context, base, system_id, public_id):
-        # type: (str, Optional[str], Optional[str], Optional[str]) -> bool
+        # type: (Text, Optional[Text], Optional[Text], Optional[Text]) -> bool
         if public_id in {
                 "-//W3C//DTD XHTML 1.0 Transitional//EN",
                 "-//W3C//DTD XHTML 1.1//EN",
@@ -117,7 +123,7 @@
         return True
 
     def _skipped(self, name, is_parameter_entity):
-        # type: (str, bool) -> None
+        # type: (Text, bool) -> None
         err = expat.error("undefined entity %s: line %d, column %d" %
                           (name, self._parser.ErrorLineNumber,
                            self._parser.ErrorColumnNumber))
@@ -127,7 +133,7 @@
         raise err
 
     def feed(self, data):
-        # type: (str) -> None
+        # type: (bytes) -> None
         if self._fed_data is not None:
             self._fed_data.append(data)
         try:
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/download.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/download.py
index f8be03ef..9d76318 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/download.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/download.py
@@ -35,12 +35,12 @@
 
 
 def abs_path(path):
-    # type: (str) -> str
+    # type: (Text) -> Text
     return os.path.abspath(os.path.expanduser(path))
 
 
 def should_download(manifest_path, rebuild_time=timedelta(days=5)):
-    # type: (str, timedelta) -> bool
+    # type: (Text, timedelta) -> bool
     if not os.path.exists(manifest_path):
         return True
     mtime = datetime.fromtimestamp(os.path.getmtime(manifest_path))
@@ -51,7 +51,7 @@
 
 
 def merge_pr_tags(repo_root, max_count=50):
-    # type: (str, int) -> List[Text]
+    # type: (Text, int) -> List[Text]
     gitfunc = git(repo_root)
     tags = []  # type: List[Text]
     if gitfunc is None:
@@ -64,7 +64,7 @@
 
 
 def score_name(name):
-    # type: (str) -> Optional[int]
+    # type: (Text) -> Optional[int]
     """Score how much we like each filename, lower wins, None rejects"""
 
     # Accept both ways of naming the manifest asset, even though
@@ -111,7 +111,7 @@
 
 
 def download_manifest(
-        manifest_path,  # type: str
+        manifest_path,  # type: Text
         tags_func,  # type: Callable[[], List[Text]]
         url_func,  # type: Callable[[List[Text]], Optional[List[Text]]]
         force=False  # type: bool
@@ -159,7 +159,8 @@
             fileobj = io.BytesIO(resp.read())
             try:
                 with gzip.GzipFile(fileobj=fileobj) as gzf:
-                    decompressed = gzf.read()  # type: ignore
+                    data = read_gzf(gzf)  # type: ignore
+                    decompressed = data
             except IOError:
                 logger.warning("Failed to decompress downloaded file")
                 continue
@@ -180,6 +181,12 @@
     return True
 
 
+def read_gzf(gzf):  # type: ignore
+    # This is working around a mypy problem in Python 2:
+    # "Call to untyped function "read" in typed context"
+    return gzf.read()
+
+
 def create_parser():
     # type: () -> argparse.ArgumentParser
     parser = argparse.ArgumentParser()
@@ -194,7 +201,7 @@
 
 
 def download_from_github(path, tests_root, force=False):
-    # type: (str, str, bool) -> bool
+    # type: (Text, Text, bool) -> bool
     return download_manifest(path, lambda: merge_pr_tags(tests_root), github_url,
                              force=force)
 
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/item.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/item.py
index e232925..efb49d7 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/item.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/item.py
@@ -198,7 +198,7 @@
 
     @property
     def script_metadata(self):
-        # type: () -> Optional[Text]
+        # type: () -> Optional[List[Tuple[Text, Text]]]
         return self._extras.get("script_metadata")
 
     def to_json(self):
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/manifest.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/manifest.py
index c0832d7..449cd24 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/manifest.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/manifest.py
@@ -7,12 +7,10 @@
 from multiprocessing import Pool, cpu_count
 from six import (
     PY3,
-    binary_type,
     ensure_text,
     iteritems,
     itervalues,
     string_types,
-    text_type,
 )
 
 from . import vcs
@@ -64,15 +62,19 @@
     pass
 
 
-item_classes = {"testharness": TestharnessTest,
-                "reftest": RefTest,
-                "print-reftest": PrintRefTest,
-                "crashtest": CrashTest,
-                "manual": ManualTest,
-                "wdspec": WebDriverSpecTest,
-                "conformancechecker": ConformanceCheckerTest,
-                "visual": VisualTest,
-                "support": SupportFile}  # type: Dict[str, Type[ManifestItem]]
+class InvalidCacheError(Exception):
+    pass
+
+
+item_classes = {u"testharness": TestharnessTest,
+                u"reftest": RefTest,
+                u"print-reftest": PrintRefTest,
+                u"crashtest": CrashTest,
+                u"manual": ManualTest,
+                u"wdspec": WebDriverSpecTest,
+                u"conformancechecker": ConformanceCheckerTest,
+                u"visual": VisualTest,
+                u"support": SupportFile}  # type: Dict[Text, Type[ManifestItem]]
 
 
 def compute_manifest_items(source_file):
@@ -82,11 +84,13 @@
     file_hash = source_file.hash
     return rel_path_parts, new_type, set(manifest_items), file_hash
 
+
 if MYPY:
     ManifestDataType = Dict[Any, TypeData]
 else:
     ManifestDataType = dict
 
+
 class ManifestData(ManifestDataType):
     def __init__(self, manifest):
         # type: (Manifest) -> None
@@ -99,7 +103,7 @@
         self.json_obj = None  # type: None
 
     def __setitem__(self, key, value):
-        # type: (str, TypeData) -> None
+        # type: (Text, TypeData) -> None
         if self.initialized:
             raise AttributeError
         dict.__setitem__(self, key, value)
@@ -115,7 +119,7 @@
         return rv
 
     def type_by_path(self):
-        # type: () -> Dict[Tuple[Text, ...], str]
+        # type: () -> Dict[Tuple[Text, ...], Text]
         rv = {}
         for item_type, item_data in iteritems(self):
             for item in item_data:
@@ -123,26 +127,25 @@
         return rv
 
 
-
 class Manifest(object):
-    def __init__(self, tests_root=None, url_base="/"):
-        # type: (Optional[str], Text) -> None
+    def __init__(self, tests_root, url_base="/"):
+        # type: (Text, Text) -> None
         assert url_base is not None
         self._data = ManifestData(self)  # type: ManifestData
-        self.tests_root = tests_root  # type: Optional[str]
+        self.tests_root = tests_root  # type: Text
         self.url_base = url_base  # type: Text
 
     def __iter__(self):
-        # type: () -> Iterator[Tuple[str, Text, Set[ManifestItem]]]
+        # type: () -> Iterator[Tuple[Text, Text, Set[ManifestItem]]]
         return self.itertypes()
 
     def itertypes(self, *types):
-        # type: (*str) -> Iterator[Tuple[str, Text, Set[ManifestItem]]]
+        # type: (*Text) -> Iterator[Tuple[Text, Text, Set[ManifestItem]]]
         for item_type in (types or sorted(self._data.keys())):
             for path in self._data[item_type]:
-                str_path = os.sep.join(path)
+                rel_path = os.sep.join(path)
                 tests = self._data[item_type][path]
-                yield item_type, str_path, tests
+                yield item_type, rel_path, tests
 
     def iterpath(self, path):
         # type: (Text) -> Iterable[ManifestItem]
@@ -166,7 +169,7 @@
                         yield test
 
     def update(self, tree, parallel=True):
-        # type: (Iterable[Tuple[Union[SourceFile, bytes], bool]], bool) -> bool
+        # type: (Iterable[Tuple[Text, Optional[Text], bool]], bool) -> bool
         """Update the manifest given an iterable of items that make up the updated manifest.
 
         The iterable must either generate tuples of the form (SourceFile, True) for paths
@@ -182,32 +185,42 @@
         data = self._data
 
         types = data.type_by_path()
-        deleted = set(types)
+        remaining_manifest_paths = set(types)
 
         to_update = []
 
-        for source_file_or_path, update in tree:
-            if not update:
-                assert isinstance(source_file_or_path, (binary_type, text_type))
-                path = ensure_text(source_file_or_path)
-                deleted.remove(tuple(path.split(os.path.sep)))
-            else:
-                assert not isinstance(source_file_or_path, (binary_type, text_type))
-                source_file = source_file_or_path
-                rel_path_parts = source_file.rel_path_parts
-                assert isinstance(rel_path_parts, tuple)
+        for path, file_hash, updated in tree:
+            path_parts = tuple(path.split(os.path.sep))
+            is_new = path_parts not in remaining_manifest_paths
 
-                is_new = rel_path_parts not in deleted  # type: bool
+            if not updated and is_new:
+                # This is kind of a bandaid; if we ended up here the cache
+                # was invalid but we've been using it anyway. That's obviously
+                # bad; we should fix the underlying issue that we sometimes
+                # use an invalid cache. But at least this fixes the immediate
+                # problem
+                raise InvalidCacheError
+
+            if not updated:
+                remaining_manifest_paths.remove(path_parts)
+            else:
+                assert self.tests_root is not None
+                source_file = SourceFile(self.tests_root,
+                                         path,
+                                         self.url_base,
+                                         file_hash)
+
                 hash_changed = False  # type: bool
 
                 if not is_new:
-                    deleted.remove(rel_path_parts)
-                    old_type = types[rel_path_parts]
-                    old_hash = data[old_type].hashes[rel_path_parts]
-                    file_hash = source_file.hash  # type: Text
+                    if file_hash is None:
+                        file_hash = source_file.hash
+                    remaining_manifest_paths.remove(path_parts)
+                    old_type = types[path_parts]
+                    old_hash = data[old_type].hashes[path_parts]
                     if old_hash != file_hash:
                         hash_changed = True
-                        del data[old_type][rel_path_parts]
+                        del data[old_type][path_parts]
 
                 if is_new or hash_changed:
                     to_update.append(source_file)
@@ -238,9 +251,9 @@
             data[new_type][rel_path_parts] = manifest_items
             data[new_type].hashes[rel_path_parts] = file_hash
 
-        if deleted:
+        if remaining_manifest_paths:
             changed = True
-            for rel_path_parts in deleted:
+            for rel_path_parts in remaining_manifest_paths:
                 for test_data in itervalues(data):
                     if rel_path_parts in test_data:
                         del test_data[rel_path_parts]
@@ -274,7 +287,7 @@
 
     @classmethod
     def from_json(cls, tests_root, obj, types=None, callee_owns_obj=False):
-        # type: (str, Dict[Text, Any], Optional[Container[Text]], bool) -> Manifest
+        # type: (Text, Dict[Text, Any], Optional[Container[Text]], bool) -> Manifest
         """Load a manifest from a JSON object
 
         This loads a manifest for a given local test_root path from an
@@ -311,19 +324,19 @@
 
 
 def load(tests_root, manifest, types=None):
-    # type: (str, Union[IO[bytes], str], Optional[Container[Text]]) -> Optional[Manifest]
+    # type: (Text, Union[IO[bytes], Text], Optional[Container[Text]]) -> Optional[Manifest]
     logger = get_logger()
 
     logger.warning("Prefer load_and_update instead")
     return _load(logger, tests_root, manifest, types)
 
 
-__load_cache = {}  # type: Dict[str, Manifest]
+__load_cache = {}  # type: Dict[Text, Manifest]
 
 
 def _load(logger,  # type: Logger
-          tests_root,  # type: str
-          manifest,  # type: Union[IO[bytes], str]
+          tests_root,  # type: Text
+          manifest,  # type: Union[IO[bytes], Text]
           types=None,  # type: Optional[Container[Text]]
           allow_cached=True  # type: bool
           ):
@@ -360,13 +373,13 @@
     return rv
 
 
-def load_and_update(tests_root,  # type: bytes
-                    manifest_path,  # type: bytes
+def load_and_update(tests_root,  # type: Union[Text, bytes]
+                    manifest_path,  # type: Union[Text, bytes]
                     url_base,  # type: Text
                     update=True,  # type: bool
                     rebuild=False,  # type: bool
-                    metadata_path=None,  # type: Optional[bytes]
-                    cache_root=None,  # type: Optional[bytes]
+                    metadata_path=None,  # type: Optional[Union[Text, bytes]]
+                    cache_root=None,  # type: Optional[Union[Text, bytes]]
                     working_copy=True,  # type: bool
                     types=None,  # type: Optional[Container[Text]]
                     write_manifest=True,  # type: bool
@@ -374,6 +387,43 @@
                     parallel=True  # type: bool
                     ):
     # type: (...) -> Manifest
+
+    # This function is now a facade for the purposes of type conversion, so that
+    # the external API can accept paths as text or (utf8) bytes, but internal
+    # functions always use Text.
+
+    metadata_path_text = ensure_text(metadata_path) if metadata_path is not None else None
+    cache_root_text = ensure_text(cache_root) if cache_root is not None else None
+
+    return _load_and_update(ensure_text(tests_root),
+                            ensure_text(manifest_path),
+                            url_base,
+                            update=update,
+                            rebuild=rebuild,
+                            metadata_path=metadata_path_text,
+                            cache_root=cache_root_text,
+                            working_copy=working_copy,
+                            types=types,
+                            write_manifest=write_manifest,
+                            allow_cached=allow_cached,
+                            parallel=parallel)
+
+
+def _load_and_update(tests_root,  # type: Text
+                     manifest_path,  # type: Text
+                     url_base,  # type: Text
+                     update=True,  # type: bool
+                     rebuild=False,  # type: bool
+                     metadata_path=None,  # type: Optional[Text]
+                     cache_root=None,  # type: Optional[Text]
+                     working_copy=True,  # type: bool
+                     types=None,  # type: Optional[Container[Text]]
+                     write_manifest=True,  # type: bool
+                     allow_cached=True,  # type: bool
+                     parallel=True  # type: bool
+                     ):
+    # type: (...) -> Manifest
+
     logger = get_logger()
 
     manifest = None
@@ -399,9 +449,18 @@
         update = True
 
     if rebuild or update:
-        tree = vcs.get_tree(tests_root, manifest, manifest_path, cache_root,
-                            working_copy, rebuild)
-        changed = manifest.update(tree, parallel)
+        for retry in range(2):
+            try:
+                tree = vcs.get_tree(tests_root, manifest, manifest_path, cache_root,
+                                    working_copy, rebuild)
+                changed = manifest.update(tree, parallel)
+                break
+            except InvalidCacheError:
+                logger.warning("Manifest cache was invalid, doing a complete rebuild")
+                rebuild = True
+        else:
+            # If we didn't break there was an error
+            raise
         if write_manifest and changed:
             write(manifest, manifest_path)
         tree.dump_caches()
@@ -410,7 +469,7 @@
 
 
 def write(manifest, manifest_path):
-    # type: (Manifest, bytes) -> None
+    # type: (Manifest, Text) -> None
     dir_name = os.path.dirname(manifest_path)
     if not os.path.exists(dir_name):
         os.makedirs(dir_name)
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/sourcefile.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/sourcefile.py
index 524eb0b..09316bd 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/sourcefile.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/sourcefile.py
@@ -2,7 +2,8 @@
 import re
 import os
 from collections import deque
-from six import binary_type, ensure_text, iteritems, text_type
+from io import BytesIO
+from six import binary_type, iteritems, text_type
 from six.moves.urllib.parse import urljoin
 from fnmatch import fnmatch
 
@@ -10,7 +11,6 @@
 if MYPY:
     # MYPY is set to True when run under Mypy.
     from typing import Any
-    from typing import AnyStr
     from typing import BinaryIO
     from typing import Callable
     from typing import Deque
@@ -43,7 +43,7 @@
                    TestharnessTest,
                    VisualTest,
                    WebDriverSpecTest)
-from .utils import ContextManagerBytesIO, cached_property
+from .utils import cached_property
 
 wd_pattern = "*.py"
 js_meta_re = re.compile(br"//\s*META:\s*(\w*)=(.*)$")
@@ -185,22 +185,22 @@
 
 
 class SourceFile(object):
-    parsers = {"html":_parse_html,
-               "xhtml":_parse_xml,
-               "svg":_parse_xml}  # type: Dict[Text, Callable[[BinaryIO], ElementTree.ElementTree]]
+    parsers = {u"html":_parse_html,
+               u"xhtml":_parse_xml,
+               u"svg":_parse_xml}  # type: Dict[Text, Callable[[BinaryIO], ElementTree.ElementTree]]
 
-    root_dir_non_test = {"common"}
+    root_dir_non_test = {u"common"}
 
-    dir_non_test = {"resources",
-                    "support",
-                    "tools"}
+    dir_non_test = {u"resources",
+                    u"support",
+                    u"tools"}
 
-    dir_path_non_test = {("css21", "archive"),
-                         ("css", "CSS2", "archive"),
-                         ("css", "common")}  # type: Set[Tuple[bytes, ...]]
+    dir_path_non_test = {(u"css21", u"archive"),
+                         (u"css", u"CSS2", u"archive"),
+                         (u"css", u"common")}  # type: Set[Tuple[Text, ...]]
 
-    def __init__(self, tests_root, rel_path_str, url_base, hash=None, contents=None):
-        # type: (AnyStr, AnyStr, Text, Optional[Text], Optional[bytes]) -> None
+    def __init__(self, tests_root, rel_path, url_base, hash=None, contents=None):
+        # type: (Text, Text, Text, Optional[Text], Optional[bytes]) -> None
         """Object representing a file in a source tree.
 
         :param tests_root: Path to the root of the source tree
@@ -209,7 +209,6 @@
         :param contents: Byte array of the contents of the file or ``None``.
         """
 
-        rel_path = ensure_text(rel_path_str)
         assert not os.path.isabs(rel_path), rel_path
         if os.name == "nt":
             # do slash normalization on Windows
@@ -224,7 +223,7 @@
 
         meta_flags = name.split(".")[1:]
 
-        self.tests_root = ensure_text(tests_root)  # type: Text
+        self.tests_root = tests_root  # type: Text
         self.rel_path = rel_path  # type: Text
         self.dir_path = dir_path  # type: Text
         self.filename = filename  # type: Text
@@ -249,7 +248,7 @@
         return rv
 
     def name_prefix(self, prefix):
-        # type: (bytes) -> bool
+        # type: (Text) -> bool
         """Check if the filename starts with a given prefix
 
         :param prefix: The prefix to check"""
@@ -270,13 +269,8 @@
         * the contents specified in the constructor, if any;
         * a File object opened for reading the file contents.
         """
-
         if self.contents is not None:
-            wrapped = ContextManagerBytesIO(self.contents)
-            if MYPY:
-                file_obj = cast(BinaryIO, wrapped)
-            else:
-                file_obj = wrapped
+            file_obj = BytesIO(self.contents)  # type: BinaryIO
         else:
             file_obj = open(self.path, 'rb')
         return file_obj
@@ -337,11 +331,11 @@
         """Check if the file name matches the conditions for the file to
         be a non-test file"""
         return (self.is_dir() or
-                self.name_prefix("MANIFEST") or
-                self.filename == "META.yml" or
-                self.filename.startswith(".") or
-                self.filename.endswith(".headers") or
-                self.filename.endswith(".ini") or
+                self.name_prefix(u"MANIFEST") or
+                self.filename == u"META.yml" or
+                self.filename.startswith(u".") or
+                self.filename.endswith(u".headers") or
+                self.filename.endswith(u".ini") or
                 self.in_non_test_dir())
 
     @property
@@ -441,14 +435,14 @@
 
         if not ext:
             return None
-        if ext[0] == ".":
+        if ext[0] == u".":
             ext = ext[1:]
-        if ext in ["html", "htm"]:
-            return "html"
-        if ext in ["xhtml", "xht", "xml"]:
-            return "xhtml"
-        if ext == "svg":
-            return "svg"
+        if ext in [u"html", u"htm"]:
+            return u"html"
+        if ext in [u"xhtml", u"xht", u"xml"]:
+            return u"xhtml"
+        if ext == u"svg":
+            return u"svg"
         return None
 
     @cached_property
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/typedata.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/typedata.py
index 13a041e5..01bb827 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/typedata.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/typedata.py
@@ -1,6 +1,5 @@
-from collections import MutableMapping
-
 from six import itervalues, iteritems
+from six.moves.collections_abc import MutableMapping
 
 
 MYPY = False
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/utils.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/utils.py
index aefc2c9..36c1a98 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/utils.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/utils.py
@@ -2,16 +2,12 @@
 import platform
 import subprocess
 
-from six import BytesIO
-
 MYPY = False
 if MYPY:
     # MYPY is set to True when run under Mypy.
     from typing import Text
     from typing import Callable
-    from typing import AnyStr
     from typing import Any
-    from typing import BinaryIO
     from typing import Generic
     from typing import TypeVar
     from typing import Optional
@@ -22,72 +18,62 @@
     T = object()
     Generic[T] = object
 
+
 def rel_path_to_url(rel_path, url_base="/"):
-    # type: (bytes, Text) -> Text
+    # type: (Text, Text) -> Text
     assert not os.path.isabs(rel_path), rel_path
-    if url_base[0] != "/":
-        url_base = "/" + url_base
-    if url_base[-1] != "/":
-        url_base += "/"
-    return url_base + rel_path.replace(os.sep, "/")
+    if url_base[0] != u"/":
+        url_base = u"/" + url_base
+    if url_base[-1] != u"/":
+        url_base += u"/"
+    return url_base + rel_path.replace(os.sep, u"/")
 
 
 def from_os_path(path):
-    # type: (AnyStr) -> AnyStr
-    assert os.path.sep == "/" or platform.system() == "Windows"
-    if "/" == os.path.sep:
+    # type: (Text) -> Text
+    assert os.path.sep == u"/" or platform.system() == "Windows"
+    if u"/" == os.path.sep:
         rv = path
     else:
-        rv = path.replace(os.path.sep, "/")
-    if "\\" in rv:
+        rv = path.replace(os.path.sep, u"/")
+    if u"\\" in rv:
         raise ValueError("path contains \\ when separator is %s" % os.path.sep)
     return rv
 
 
 def to_os_path(path):
-    # type: (AnyStr) -> AnyStr
-    assert os.path.sep == "/" or platform.system() == "Windows"
-    if "\\" in path:
+    # type: (Text) -> Text
+    assert os.path.sep == u"/" or platform.system() == "Windows"
+    if u"\\" in path:
         raise ValueError("normalised path contains \\")
-    if "/" == os.path.sep:
+    if u"/" == os.path.sep:
         return path
-    return path.replace("/", os.path.sep)
+    return path.replace(u"/", os.path.sep)
 
 
 def git(path):
-    # type: (bytes) -> Optional[Callable[..., Text]]
+    # type: (Text) -> Optional[Callable[..., Text]]
     def gitfunc(cmd, *args):
-        # type: (bytes, *bytes) -> Text
-        full_cmd = ["git", cmd] + list(args)
+        # type: (Text, *Text) -> Text
+        full_cmd = [u"git", cmd] + list(args)
         try:
             return subprocess.check_output(full_cmd, cwd=path, stderr=subprocess.STDOUT).decode('utf8')
         except Exception as e:
             if platform.uname()[0] == "Windows" and isinstance(e, WindowsError):
-                full_cmd[0] = "git.bat"
+                full_cmd[0] = u"git.bat"
                 return subprocess.check_output(full_cmd, cwd=path, stderr=subprocess.STDOUT).decode('utf8')
             else:
                 raise
 
     try:
         # this needs to be a command that fails if we aren't in a git repo
-        gitfunc("rev-parse", "--show-toplevel")
+        gitfunc(u"rev-parse", u"--show-toplevel")
     except (subprocess.CalledProcessError, OSError):
         return None
     else:
         return gitfunc
 
 
-class ContextManagerBytesIO(BytesIO):  # type: ignore
-    def __enter__(self):
-        # type: () -> BinaryIO
-        return self  # type: ignore
-
-    def __exit__(self, *args, **kwargs):
-        # type: (*Any, **Any) -> bool
-        self.close()
-        return True
-
-
 class cached_property(Generic[T]):
     def __init__(self, func):
         # type: (Callable[[Any], T]) -> None
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/vcs.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/vcs.py
index 7c0feeb..3dfd7c9 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/vcs.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/vcs.py
@@ -3,11 +3,10 @@
 import os
 import stat
 from collections import deque
-from collections import MutableMapping
 
 from six import with_metaclass, PY2
+from six.moves.collections_abc import MutableMapping
 
-from .sourcefile import SourceFile
 from .utils import git
 
 try:
@@ -20,7 +19,7 @@
 MYPY = False
 if MYPY:
     # MYPY is set to True when run under Mypy.
-    from typing import Dict, Optional, List, Set, Text, Iterable, Any, Tuple, Union, Iterator
+    from typing import Dict, Optional, List, Set, Text, Iterable, Any, Tuple, Iterator
     from .manifest import Manifest  # cyclic import under MYPY guard
     if PY2:
         stat_result = Any
@@ -30,10 +29,10 @@
 
 def get_tree(tests_root, manifest, manifest_path, cache_root,
              working_copy=True, rebuild=False):
-    # type: (bytes, Manifest, Optional[bytes], Optional[bytes], bool, bool) -> FileSystem
+    # type: (Text, Manifest, Optional[Text], Optional[Text], bool, bool) -> FileSystem
     tree = None
     if cache_root is None:
-        cache_root = os.path.join(tests_root, ".wptcache")
+        cache_root = os.path.join(tests_root, u".wptcache")
     if not os.path.exists(cache_root):
         try:
             os.makedirs(cache_root)
@@ -54,7 +53,7 @@
 
 class GitHasher(object):
     def __init__(self, path):
-        # type: (bytes) -> None
+        # type: (Text) -> None
         self.git = git(path)
 
     def _local_changes(self):
@@ -90,37 +89,39 @@
 
 
 class FileSystem(object):
-    def __init__(self, root, url_base, cache_path, manifest_path=None, rebuild=False):
-        # type: (bytes, Text, Optional[bytes], Optional[bytes], bool) -> None
-        self.root = os.path.abspath(root)
+    def __init__(self, tests_root, url_base, cache_path, manifest_path=None, rebuild=False):
+        # type: (Text, Text, Optional[Text], Optional[Text], bool) -> None
+        self.tests_root = tests_root
         self.url_base = url_base
         self.ignore_cache = None
         self.mtime_cache = None
+        tests_root_bytes = tests_root.encode("utf8")
         if cache_path is not None:
             if manifest_path is not None:
-                self.mtime_cache = MtimeCache(cache_path, root, manifest_path, rebuild)
-            if gitignore.has_ignore(root):
-                self.ignore_cache = GitIgnoreCache(cache_path, root, rebuild)
-        self.path_filter = gitignore.PathFilter(self.root,
-                                                extras=[".git/"],
+                self.mtime_cache = MtimeCache(cache_path, tests_root, manifest_path, rebuild)
+            if gitignore.has_ignore(tests_root_bytes):
+                self.ignore_cache = GitIgnoreCache(cache_path, tests_root, rebuild)
+        self.path_filter = gitignore.PathFilter(tests_root_bytes,
+                                                extras=[b".git/"],
                                                 cache=self.ignore_cache)
-        git = GitHasher(root)
+        git = GitHasher(tests_root)
         if git is not None:
             self.hash_cache = git.hash_cache()
         else:
             self.hash_cache = {}
 
     def __iter__(self):
-        # type: () -> Iterator[Tuple[Union[bytes, SourceFile], bool]]
+        # type: () -> Iterator[Tuple[Text, Optional[Text], bool]]
         mtime_cache = self.mtime_cache
-        for dirpath, dirnames, filenames in self.path_filter(walk(self.root)):
+        for dirpath, dirnames, filenames in self.path_filter(
+                walk(self.tests_root.encode("utf8"))):
             for filename, path_stat in filenames:
-                path = os.path.join(dirpath, filename)
+                path = os.path.join(dirpath, filename).decode("utf8")
                 if mtime_cache is None or mtime_cache.updated(path, path_stat):
-                    hash = self.hash_cache.get(path, None)
-                    yield SourceFile(self.root, path, self.url_base, hash), True
+                    file_hash = self.hash_cache.get(path, None)
+                    yield path, file_hash, True
                 else:
-                    yield path, False
+                    yield path, None, False
 
     def dump_caches(self):
         # type: () -> None
@@ -131,7 +132,7 @@
 
 class CacheFile(with_metaclass(abc.ABCMeta)):
     def __init__(self, cache_root, tests_root, rebuild=False):
-        # type: (bytes, bytes, bool) -> None
+        # type: (Text, Text, bool) -> None
         self.tests_root = tests_root
         if not os.path.exists(cache_root):
             os.makedirs(cache_root)
@@ -141,7 +142,7 @@
 
     @abc.abstractproperty
     def file_name(self):
-        # type: () -> bytes
+        # type: () -> Text
         pass
 
     def dump(self):
@@ -152,8 +153,8 @@
             json.dump(self.data, f, indent=1)
 
     def load(self, rebuild=False):
-        # type: (bool) -> Dict[Any, Any]
-        data = {}  # type: Dict[Any, Any]
+        # type: (bool) -> Dict[Text, Any]
+        data = {}  # type: Dict[Text, Any]
         try:
             if not rebuild:
                 with open(self.path, 'r') as f:
@@ -167,22 +168,22 @@
         return data
 
     def check_valid(self, data):
-        # type: (Dict[Any, Any]) -> Dict[Any, Any]
+        # type: (Dict[Text, Any]) -> Dict[Text, Any]
         """Check if the cached data is valid and return an updated copy of the
         cache containing only data that can be used."""
         return data
 
 
 class MtimeCache(CacheFile):
-    file_name = "mtime.json"
+    file_name = u"mtime.json"
 
     def __init__(self, cache_root, tests_root, manifest_path, rebuild=False):
-        # type: (bytes, bytes, bytes, bool) -> None
+        # type: (Text, Text, Text, bool) -> None
         self.manifest_path = manifest_path
         super(MtimeCache, self).__init__(cache_root, tests_root, rebuild)
 
     def updated(self, rel_path, stat):
-        # type: (bytes, stat_result) -> bool
+        # type: (Text, stat_result) -> bool
         """Return a boolean indicating whether the file changed since the cache was last updated.
 
         This implicitly updates the cache with the new mtime data."""
@@ -195,12 +196,12 @@
 
     def check_valid(self, data):
         # type: (Dict[Any, Any]) -> Dict[Any, Any]
-        if data.get("/tests_root") != self.tests_root:
+        if data.get(u"/tests_root") != self.tests_root:
             self.modified = True
         else:
             if self.manifest_path is not None and os.path.exists(self.manifest_path):
                 mtime = os.path.getmtime(self.manifest_path)
-                if data.get("/manifest_path") != [self.manifest_path, mtime]:
+                if data.get(u"/manifest_path") != [self.manifest_path, mtime]:
                     self.modified = True
             else:
                 self.modified = True
@@ -228,10 +229,10 @@
         # type: (Dict[Any, Any]) -> Dict[Any, Any]
         ignore_path = os.path.join(self.tests_root, ".gitignore")
         mtime = os.path.getmtime(ignore_path)
-        if data.get("/gitignore_file") != [ignore_path, mtime]:
+        if data.get(u"/gitignore_file") != [ignore_path, mtime]:
             self.modified = True
             data = {}
-            data["/gitignore_file"] = [ignore_path, mtime]
+            data[u"/gitignore_file"] = [ignore_path, mtime]
         return data
 
     def __contains__(self, key):
@@ -239,23 +240,23 @@
         return key in self.data
 
     def __getitem__(self, key):
-        # type: (bytes) -> bool
+        # type: (Text) -> bool
         v = self.data[key]
         assert isinstance(v, bool)
         return v
 
     def __setitem__(self, key, value):
-        # type: (bytes, bool) -> None
+        # type: (Text, bool) -> None
         if self.data.get(key) != value:
             self.modified = True
             self.data[key] = value
 
     def __delitem__(self, key):
-        # type: (bytes) -> None
+        # type: (Text) -> None
         del self.data[key]
 
     def __iter__(self):
-        # type: () -> Iterator[bytes]
+        # type: () -> Iterator[Text]
         return iter(self.data)
 
     def __len__(self):
@@ -286,7 +287,7 @@
     relpath = os.path.relpath
 
     root = os.path.abspath(root)
-    stack = deque([(root, "")])
+    stack = deque([(root, b"")])
 
     while stack:
         dir_path, rel_path = stack.popleft()
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/third_party/six/six.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/third_party/six/six.py
index d0aece8..83f6978 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/third_party/six/six.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/third_party/six/six.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2010-2019 Benjamin Peterson
+# Copyright (c) 2010-2020 Benjamin Peterson
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
 # of this software and associated documentation files (the "Software"), to deal
@@ -29,7 +29,7 @@
 import types
 
 __author__ = "Benjamin Peterson <benjamin@python.org>"
-__version__ = "1.13.0"
+__version__ = "1.15.0"
 
 
 # Useful for very coarse version differentiation.
@@ -259,7 +259,7 @@
     MovedModule("copyreg", "copy_reg"),
     MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
     MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"),
-    MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"),
+    MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread" if sys.version_info < (3, 9) else "_thread"),
     MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
     MovedModule("http_cookies", "Cookie", "http.cookies"),
     MovedModule("html_entities", "htmlentitydefs", "html.entities"),
@@ -644,9 +644,11 @@
     if sys.version_info[1] <= 1:
         _assertRaisesRegex = "assertRaisesRegexp"
         _assertRegex = "assertRegexpMatches"
+        _assertNotRegex = "assertNotRegexpMatches"
     else:
         _assertRaisesRegex = "assertRaisesRegex"
         _assertRegex = "assertRegex"
+        _assertNotRegex = "assertNotRegex"
 else:
     def b(s):
         return s
@@ -668,6 +670,7 @@
     _assertCountEqual = "assertItemsEqual"
     _assertRaisesRegex = "assertRaisesRegexp"
     _assertRegex = "assertRegexpMatches"
+    _assertNotRegex = "assertNotRegexpMatches"
 _add_doc(b, """Byte literal""")
 _add_doc(u, """Text literal""")
 
@@ -684,6 +687,10 @@
     return getattr(self, _assertRegex)(*args, **kwargs)
 
 
+def assertNotRegex(self, *args, **kwargs):
+    return getattr(self, _assertNotRegex)(*args, **kwargs)
+
+
 if PY3:
     exec_ = getattr(moves.builtins, "exec")
 
@@ -719,16 +726,7 @@
 """)
 
 
-if sys.version_info[:2] == (3, 2):
-    exec_("""def raise_from(value, from_value):
-    try:
-        if from_value is None:
-            raise value
-        raise value from from_value
-    finally:
-        value = None
-""")
-elif sys.version_info[:2] > (3, 2):
+if sys.version_info[:2] > (3,):
     exec_("""def raise_from(value, from_value):
     try:
         raise value from from_value
@@ -808,13 +806,33 @@
 _add_doc(reraise, """Reraise an exception.""")
 
 if sys.version_info[0:2] < (3, 4):
+    # This does exactly the same what the :func:`py3:functools.update_wrapper`
+    # function does on Python versions after 3.2. It sets the ``__wrapped__``
+    # attribute on ``wrapper`` object and it doesn't raise an error if any of
+    # the attributes mentioned in ``assigned`` and ``updated`` are missing on
+    # ``wrapped`` object.
+    def _update_wrapper(wrapper, wrapped,
+                        assigned=functools.WRAPPER_ASSIGNMENTS,
+                        updated=functools.WRAPPER_UPDATES):
+        for attr in assigned:
+            try:
+                value = getattr(wrapped, attr)
+            except AttributeError:
+                continue
+            else:
+                setattr(wrapper, attr, value)
+        for attr in updated:
+            getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
+        wrapper.__wrapped__ = wrapped
+        return wrapper
+    _update_wrapper.__doc__ = functools.update_wrapper.__doc__
+
     def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
               updated=functools.WRAPPER_UPDATES):
-        def wrapper(f):
-            f = functools.wraps(wrapped, assigned, updated)(f)
-            f.__wrapped__ = wrapped
-            return f
-        return wrapper
+        return functools.partial(_update_wrapper, wrapped=wrapped,
+                                 assigned=assigned, updated=updated)
+    wraps.__doc__ = functools.wraps.__doc__
+
 else:
     wraps = functools.wraps
 
@@ -872,12 +890,11 @@
       - `str` -> encoded to `bytes`
       - `bytes` -> `bytes`
     """
+    if isinstance(s, binary_type):
+        return s
     if isinstance(s, text_type):
         return s.encode(encoding, errors)
-    elif isinstance(s, binary_type):
-        return s
-    else:
-        raise TypeError("not expecting type '%s'" % type(s))
+    raise TypeError("not expecting type '%s'" % type(s))
 
 
 def ensure_str(s, encoding='utf-8', errors='strict'):
@@ -891,12 +908,15 @@
       - `str` -> `str`
       - `bytes` -> decoded to `str`
     """
-    if not isinstance(s, (text_type, binary_type)):
-        raise TypeError("not expecting type '%s'" % type(s))
+    # Optimization: Fast return for the common case.
+    if type(s) is str:
+        return s
     if PY2 and isinstance(s, text_type):
-        s = s.encode(encoding, errors)
+        return s.encode(encoding, errors)
     elif PY3 and isinstance(s, binary_type):
-        s = s.decode(encoding, errors)
+        return s.decode(encoding, errors)
+    elif not isinstance(s, (text_type, binary_type)):
+        raise TypeError("not expecting type '%s'" % type(s))
     return s
 
 
@@ -919,7 +939,6 @@
         raise TypeError("not expecting type '%s'" % type(s))
 
 
-
 def python_2_unicode_compatible(klass):
     """
     A class decorator that defines __unicode__ and __str__ methods under Python 2.
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/revlist.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/revlist.py
index 1893fde..bd85612 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/revlist.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/revlist.py
@@ -33,19 +33,22 @@
 
 
 def get_tagged_revisions(pattern):
-    # type: (bytes) -> List[..., Dict]
+    # type: (Text) -> List[..., Dict]
     '''
     Returns the tagged revisions indexed by the committer date.
     '''
     git = get_git_cmd(wpt_root)
     args = [
         pattern,
-        '--sort=-committerdate',
-        '--format=%(refname:lstrip=2) %(objectname) %(committerdate:raw)',
-        '--count=100000'
+        u'--sort=-committerdate',
+        u'--format=%(refname:lstrip=2) %(objectname) %(committerdate:raw)',
+        u'--count=100000'
     ]
-    for line in git("for-each-ref", *args).splitlines():
-        tag, commit, date, _ = line.split(" ")
+    ref_list = git(u"for-each-ref", *args)
+    for line in ref_list.splitlines():
+        if not line:
+            continue
+        tag, commit, date, _ = line.split(u" ")
         date = int(date)
         yield tag, commit, date
 
@@ -81,7 +84,7 @@
     # Expected result: N,M,K,J,H,G,F,C,A
 
     cutoff_date = calculate_cutoff_date(until, epoch, epoch_offset)
-    for _, commit, date in get_tagged_revisions("refs/tags/merge_pr_*"):
+    for _, commit, date in get_tagged_revisions(u"refs/tags/merge_pr_*"):
         if count >= max_count:
             return
         if date < cutoff_date:
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/testfiles.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/testfiles.py
index f2960e8..c990aa2 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/testfiles.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/testfiles.py
@@ -2,15 +2,14 @@
 import logging
 import os
 import re
-import subprocess
 import sys
 
-import six
 from collections import OrderedDict
-from six import iteritems
+from six import ensure_text, ensure_str, iteritems
 
 try:
     from ..manifest import manifest
+    from ..manifest.utils import git as get_git_cmd
 except ValueError:
     # if we're not within the tools package, the above is an import from above
     # the top-level which raises ValueError, so reimport it with an absolute
@@ -20,12 +19,12 @@
     # paths set up correctly to handle both and MYPY has no knowledge of our
     # sys.path magic
     from manifest import manifest  # type: ignore
+    from manifest.utils import git as get_git_cmd  # type: ignore
 
 MYPY = False
 if MYPY:
     # MYPY is set to True when run under Mypy.
     from typing import Any
-    from typing import Callable
     from typing import Dict
     from typing import Iterable
     from typing import List
@@ -35,30 +34,13 @@
     from typing import Set
     from typing import Text
     from typing import Tuple
-    from typing import Union
 
-here = os.path.dirname(__file__)
+here = ensure_text(os.path.dirname(__file__))
 wpt_root = os.path.abspath(os.path.join(here, os.pardir, os.pardir))
 
 logger = logging.getLogger()
 
 
-def get_git_cmd(repo_path):
-    # type: (bytes) -> Callable[..., Text]
-    """Create a function for invoking git commands as a subprocess."""
-    def git(cmd, *args):
-        # type: (Text, *Union[bytes, Text]) -> Text
-        full_cmd = [u"git", cmd] + list(item.decode("utf8") if isinstance(item, bytes) else item for item in args)  # type: List[Text]
-        try:
-            logger.debug(" ".join(full_cmd))
-            return subprocess.check_output(full_cmd, cwd=repo_path).decode("utf8").strip()
-        except subprocess.CalledProcessError as e:
-            logger.critical("Git command exited with status %i" % e.returncode)
-            logger.critical(e.output)
-            sys.exit(1)
-    return git
-
-
 def display_branch_point():
     # type: () -> None
     print(branch_point())
@@ -67,6 +49,9 @@
 def branch_point():
     # type: () -> Optional[Text]
     git = get_git_cmd(wpt_root)
+    if git is None:
+        raise Exception("git not found")
+
     if (os.environ.get("GITHUB_PULL_REQUEST", "false") == "false" and
         os.environ.get("GITHUB_BRANCH") == "master"):
         # For builds on the master branch just return the HEAD commit
@@ -86,7 +71,7 @@
 
         # get everything in refs/heads and refs/remotes that doesn't include HEAD
         not_heads = [item for item in git("rev-parse", "--not", "--branches", "--remotes").split("\n")
-                     if item != "^%s" % head]
+                     if item and item != "^%s" % head]
 
         # get all commits on HEAD but not reachable from anything in not_heads
         commits = git("rev-list", "--topo-order", "--parents", "HEAD", *not_heads)
@@ -130,28 +115,33 @@
             logger.debug("Using first commit on another branch as the branch point")
 
     logger.debug("Branch point from master: %s" % branch_point)
+    if branch_point:
+        branch_point = branch_point.strip()
     return branch_point
 
 
 def compile_ignore_rule(rule):
-    # type: (str) -> Pattern[str]
-    rule = rule.replace(os.path.sep, "/")
-    parts = rule.split("/")
+    # type: (Text) -> Pattern[Text]
+    rule = rule.replace(ensure_text(os.path.sep), u"/")
+    parts = rule.split(u"/")
     re_parts = []
     for part in parts:
-        if part.endswith("**"):
-            re_parts.append(re.escape(part[:-2]) + ".*")
-        elif part.endswith("*"):
-            re_parts.append(re.escape(part[:-1]) + "[^/]*")
+        if part.endswith(u"**"):
+            re_parts.append(re.escape(part[:-2]) + u".*")
+        elif part.endswith(u"*"):
+            re_parts.append(re.escape(part[:-1]) + u"[^/]*")
         else:
             re_parts.append(re.escape(part))
-    return re.compile("^%s$" % "/".join(re_parts))
+    return re.compile(u"^%s$" % u"/".join(re_parts))
 
 
 def repo_files_changed(revish, include_uncommitted=False, include_new=False):
-    # type: (str, bool, bool) -> Set[Text]
+    # type: (Text, bool, bool) -> Set[Text]
     git = get_git_cmd(wpt_root)
-    files_list = git("diff", "--name-only", "-z", revish).split("\0")
+    if git is None:
+        raise Exception("git not found")
+
+    files_list = git("diff", "--name-only", "-z", revish).split(u"\0")
     assert not files_list[-1]
     files = set(files_list[:-1])
 
@@ -175,7 +165,7 @@
 
 
 def exclude_ignored(files, ignore_rules):
-    # type: (Iterable[Text], Optional[Sequence[str]]) -> Tuple[List[Text], List[Text]]
+    # type: (Iterable[Text], Optional[Sequence[Text]]) -> Tuple[List[Text], List[Text]]
     if ignore_rules is None:
         ignore_rules = []
     compiled_ignore_rules = [compile_ignore_rule(item) for item in ignore_rules]
@@ -195,8 +185,8 @@
     return changed, ignored
 
 
-def files_changed(revish,  # type: str
-                  ignore_rules=None,  # type: Optional[Sequence[str]]
+def files_changed(revish,  # type: Text
+                  ignore_rules=None,  # type: Optional[Sequence[Text]]
                   include_uncommitted=False,  # type: bool
                   include_new=False  # type: bool
                   ):
@@ -217,29 +207,29 @@
 
 
 def _in_repo_root(full_path):
-    # type: (Union[bytes, Text]) -> bool
+    # type: (Text) -> bool
     rel_path = os.path.relpath(full_path, wpt_root)
     path_components = rel_path.split(os.sep)
     return len(path_components) < 2
 
 
 def load_manifest(manifest_path=None, manifest_update=True):
-    # type: (Optional[str], bool) -> manifest.Manifest
+    # type: (Optional[Text], bool) -> manifest.Manifest
     if manifest_path is None:
-        manifest_path = os.path.join(wpt_root, "MANIFEST.json")
+        manifest_path = os.path.join(wpt_root, u"MANIFEST.json")
     return manifest.load_and_update(wpt_root, manifest_path, "/",
                                     update=manifest_update)
 
 
 def affected_testfiles(files_changed,  # type: Iterable[Text]
-                       skip_dirs=None,  # type: Optional[Set[str]]
-                       manifest_path=None,  # type: Optional[str]
+                       skip_dirs=None,  # type: Optional[Set[Text]]
+                       manifest_path=None,  # type: Optional[Text]
                        manifest_update=True  # type: bool
                        ):
-    # type: (...) -> Tuple[Set[Text], Set[str]]
+    # type: (...) -> Tuple[Set[Text], Set[Text]]
     """Determine and return list of test files that reference changed files."""
     if skip_dirs is None:
-        skip_dirs = {"conformance-checkers", "docs", "tools"}
+        skip_dirs = {u"conformance-checkers", u"docs", u"tools"}
     affected_testfiles = set()
     # Exclude files that are in the repo root, because
     # they are not part of any test.
@@ -282,7 +272,7 @@
                                 for interface in interfaces_changed]
 
     def affected_by_wdspec(test):
-        # type: (str) -> bool
+        # type: (Text) -> bool
         affected = False
         if test in wdspec_test_files:
             for support_full_path, _ in nontest_changed_paths:
@@ -298,7 +288,7 @@
         return affected
 
     def affected_by_interfaces(file_contents):
-        # type: (Union[bytes, Text]) -> bool
+        # type: (Text) -> bool
         if len(interfaces_changed_names) > 0:
             if 'idlharness.js' in file_contents:
                 for interface in interfaces_changed_names:
@@ -376,27 +366,26 @@
 
 
 def get_revish(**kwargs):
-    # type: (**Any) -> bytes
+    # type: (**Any) -> Text
     revish = kwargs.get("revish")
     if revish is None:
-        revish = "%s..HEAD" % branch_point()
-    if isinstance(revish, six.text_type):
-        revish = revish.encode("utf8")
-    assert isinstance(revish, six.binary_type)
-    return revish
+        revish = u"%s..HEAD" % branch_point()
+    return ensure_text(revish).strip()
 
 
 def run_changed_files(**kwargs):
     # type: (**Any) -> None
     revish = get_revish(**kwargs)
-    changed, _ = files_changed(revish, kwargs["ignore_rules"],
+    changed, _ = files_changed(revish,
+                               kwargs["ignore_rules"],
                                include_uncommitted=kwargs["modified"],
                                include_new=kwargs["new"])
 
-    separator = "\0" if kwargs["null"] else "\n"
+    separator = u"\0" if kwargs["null"] else u"\n"
 
     for item in sorted(changed):
-        sys.stdout.write(os.path.relpath(six.ensure_str(item), wpt_root) + separator)
+        line = os.path.relpath(item, wpt_root) + separator
+        sys.stdout.write(ensure_str(line))
 
 
 def run_tests_affected(**kwargs):
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/utils.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/utils.py
index 9a6c064..18551481 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/utils.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/utils.py
@@ -5,15 +5,17 @@
 import zipfile
 from io import BytesIO
 
-try:
-    from typing import Any, Callable
-except ImportError:
-    pass
+MYPY = False
+if MYPY:
+    from typing import Any
+    from typing import Callable
+    from typing import Dict
 
 logger = logging.getLogger(__name__)
 
 
 class Kwargs(dict):
+    # type: Dict[Any, Any]
     def set_if_none(self,
                     name,            # type: str
                     value,           # type: Any
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/virtualenv.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/virtualenv.py
index 18edcc0..51b97cea 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/virtualenv.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/virtualenv.py
@@ -55,9 +55,13 @@
 
     @property
     def pip_path(self):
-        path = find_executable("pip", self.bin_path)
+        if sys.version_info.major >= 3:
+            pip_executable = "pip3"
+        else:
+            pip_executable = "pip2"
+        path = find_executable(pip_executable, self.bin_path)
         if path is None:
-            raise ValueError("pip not found")
+            raise ValueError("%s not found" % pip_executable)
         return path
 
     @property
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wptserve/wptserve/config.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wptserve/wptserve/config.py
index 7766565..4d653f5 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wptserve/wptserve/config.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wptserve/wptserve/config.py
@@ -1,8 +1,9 @@
 import copy
 import logging
 import os
+from collections import defaultdict
 
-from collections import defaultdict, Mapping
+from six.moves.collections_abc import Mapping
 from six import integer_types, iteritems, itervalues, string_types
 
 from . import sslutils
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/wpt b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/wpt
index 9930d77..329ea668 100755
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/wpt
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/wpt
@@ -10,7 +10,7 @@
     if (args.py3 or py3only) and sys.version_info.major < 3:
         from subprocess import call
         try:
-            sys.exit(call(['python3', sys.argv[0]] + [args.command] + extra))
+            sys.exit(call(['python3'] + sys.argv))
         except OSError as e:
             if e.errno == 2:
                 sys.stderr.write("python3 is needed to run this command\n")
diff --git a/third_party/blink/tools/blinkpy/w3c/common.py b/third_party/blink/tools/blinkpy/w3c/common.py
index 2f91bb9..845aac5 100644
--- a/third_party/blink/tools/blinkpy/w3c/common.py
+++ b/third_party/blink/tools/blinkpy/w3c/common.py
@@ -78,6 +78,7 @@
         'MANIFEST.json',  # MANIFEST.json is automatically regenerated.
         'OWNERS',  # https://crbug.com/584660 https://crbug.com/702283
         'reftest.list',  # https://crbug.com/582838
+        'DIR_METADATA',  # https://crbug.com/1103374
     ]
     return (basename in skipped_basenames or is_testharness_baseline(basename)
             or basename.startswith('.'))
diff --git a/third_party/blink/tools/blinkpy/w3c/common_unittest.py b/third_party/blink/tools/blinkpy/w3c/common_unittest.py
index 4dc69bb..697cd2fb 100644
--- a/third_party/blink/tools/blinkpy/w3c/common_unittest.py
+++ b/third_party/blink/tools/blinkpy/w3c/common_unittest.py
@@ -88,6 +88,7 @@
 
     def test_is_basename_skipped(self):
         self.assertTrue(is_basename_skipped('MANIFEST.json'))
+        self.assertTrue(is_basename_skipped('DIR_METADATA'))
         self.assertTrue(is_basename_skipped('OWNERS'))
         self.assertTrue(is_basename_skipped('reftest.list'))
         self.assertTrue(is_basename_skipped('.gitignore'))
@@ -106,6 +107,8 @@
         self.assertFalse(
             is_file_exportable(CHROMIUM_WPT_DIR + 'MANIFEST.json'))
         self.assertFalse(is_file_exportable(CHROMIUM_WPT_DIR + 'dom/OWNERS'))
+        self.assertFalse(
+            is_file_exportable(CHROMIUM_WPT_DIR + 'dom/DIR_METADATA'))
 
     def test_is_file_exportable_asserts_path(self):
         # Rejects basenames.
diff --git a/third_party/blink/tools/blinkpy/w3c/test_copier_unittest.py b/third_party/blink/tools/blinkpy/w3c/test_copier_unittest.py
index 3072ea7..a87b460 100644
--- a/third_party/blink/tools/blinkpy/w3c/test_copier_unittest.py
+++ b/third_party/blink/tools/blinkpy/w3c/test_copier_unittest.py
@@ -42,6 +42,7 @@
     '/blink/w3c/dir/has_shebang.txt': '#!',
     '/blink/w3c/dir/README.txt': '',
     '/blink/w3c/dir/OWNERS': '',
+    '/blink/w3c/dir/DIR_METADATA': '',
     '/blink/w3c/dir/reftest.list': '',
     MOCK_WEB_TESTS + 'external/README.txt': '',
     MOCK_WEB_TESTS + 'W3CImportExpectations': '',
diff --git a/third_party/blink/tools/blinkpy/w3c/test_importer_unittest.py b/third_party/blink/tools/blinkpy/w3c/test_importer_unittest.py
index 0153d8e..c1e691c 100644
--- a/third_party/blink/tools/blinkpy/w3c/test_importer_unittest.py
+++ b/third_party/blink/tools/blinkpy/w3c/test_importer_unittest.py
@@ -2,13 +2,11 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import datetime
 import json
 import unittest
 
 from blinkpy.common.checkout.git_mock import MockGit
 from blinkpy.common.host_mock import MockHost
-from blinkpy.common.net.git_cl import CLStatus
 from blinkpy.common.net.git_cl import TryJobStatus
 from blinkpy.common.net.git_cl_mock import MockGitCL
 from blinkpy.common.net.network_transaction import NetworkTimeout
@@ -23,7 +21,6 @@
 from blinkpy.w3c.test_importer import TestImporter, ROTATIONS_URL, TBR_FALLBACK
 from blinkpy.w3c.wpt_github_mock import MockWPTGitHub
 from blinkpy.w3c.wpt_manifest import BASE_MANIFEST_NAME
-from blinkpy.web_tests.builder_list import BuilderList
 from blinkpy.web_tests.port.android import PRODUCTS_TO_EXPECTATION_FILE_PATHS
 
 MOCK_WEB_TESTS = '/mock-checkout/' + RELATIVE_WEB_TESTS
@@ -592,6 +589,7 @@
         host.filesystem.write_text_file(dest_path + '/foo-test-expected.txt',
                                         '')
         host.filesystem.write_text_file(dest_path + '/OWNERS', '')
+        host.filesystem.write_text_file(dest_path + '/DIR_METADATA', '')
         host.filesystem.write_text_file(dest_path + '/bar/baz/OWNERS', '')
         # When the destination path is cleared, OWNERS files and baselines
         # are kept.
@@ -600,4 +598,5 @@
         self.assertTrue(
             host.filesystem.exists(dest_path + '/foo-test-expected.txt'))
         self.assertTrue(host.filesystem.exists(dest_path + '/OWNERS'))
+        self.assertTrue(host.filesystem.exists(dest_path + '/DIR_METADATA'))
         self.assertTrue(host.filesystem.exists(dest_path + '/bar/baz/OWNERS'))
diff --git a/third_party/blink/web_tests/FlagExpectations/force-device-scale-factor=1.5 b/third_party/blink/web_tests/FlagExpectations/force-device-scale-factor=1.5
index 94b18d0..c1ebbe6 100644
--- a/third_party/blink/web_tests/FlagExpectations/force-device-scale-factor=1.5
+++ b/third_party/blink/web_tests/FlagExpectations/force-device-scale-factor=1.5
@@ -3,3 +3,6 @@
 
 # Skip tests for high DPI test suite.
 css3/selectors3/* [ Skip ]
+
+# Image not rendering properly
+crbug.com/976224 images/color-profile-svg-foreign-object-expected.png [ Failure ]
diff --git a/third_party/blink/web_tests/LeakExpectations b/third_party/blink/web_tests/LeakExpectations
index 1aff5af..0fff2449 100644
--- a/third_party/blink/web_tests/LeakExpectations
+++ b/third_party/blink/web_tests/LeakExpectations
@@ -161,6 +161,22 @@
 # Also crbug.com/867532
 crbug.com/1098782 [ Linux ] virtual/omt-worker-fetch/external/wpt/workers/modules/dedicated-worker-import.any.worker.html [ Pass Timeout Crash ]
 
+# Sheriff 2020-07-14
+crbug.com/1068175 [ Linux ] external/wpt/referrer-policy/gen/worker-module.http-rp/strict-origin-when-cross-origin/worker-classic.http.html [ Pass Timeout ]
+crbug.com/1068175 [ Linux ] virtual/cors/external/wpt/referrer-policy/gen/worker-module.http-rp/strict-origin/worker-classic.http.html [ Pass Timeout ]
+crbug.com/1068175 [ Linux ] virtual/omt-worker-fetch/external/wpt/referrer-policy/gen/worker-classic.http-rp/no-referrer/worker-classic.http.html [ Pass Timeout ]
+crbug.com/1068175 [ Linux ] external/wpt/referrer-policy/gen/worker-module.http-rp/unsafe-url/worker-classic.http.html [ Pass Timeout ]
+crbug.com/1068175 [ Linux ] external/wpt/referrer-policy/gen/worker-module.http-rp/unset/worker-module.http.html [ Pass Timeout ]
+crbug.com/1068175 [ Linux ] external/wpt/referrer-policy/gen/worker-module.http-rp/no-referrer-when-downgrade/worker-classic.http.html [ Pass Timeout ]
+crbug.com/1068175 [ Linux ] external/wpt/referrer-policy/gen/worker-module.http-rp/origin-when-cross-origin/worker-classic.http.html [ Pass Timeout ]
+crbug.com/1068175 [ Linux ] external/wpt/referrer-policy/gen/worker-module.http-rp/strict-origin/worker-classic.http.html [ Pass Timeout ]
+crbug.com/1068175 [ Linux ] external/wpt/referrer-policy/gen/worker-classic.http-rp/strict-origin-when-cross-origin/worker-module.http.html [ Pass Timeout ]
+crbug.com/1068175 [ Linux ] virtual/omt-worker-fetch/external/wpt/referrer-policy/gen/worker-classic.http-rp/unset/worker-classic.http.html [ Pass Timeout ]
+crbug.com/1068175 [ Linux ] external/wpt/referrer-policy/gen/worker-module.http-rp/origin/worker-classic.http.html [ Pass Timeout ]
+crbug.com/1068175 [ Linux ] external/wpt/referrer-policy/gen/worker-classic.http-rp/same-origin/worker-module.http.html [ Pass Timeout ]
+crbug.com/1068175 [ Linux ] external/wpt/referrer-policy/gen/worker-classic.http-rp/origin-when-cross-origin/worker-classic.http.html [ Pass Timeout ]
+crbug.com/1068175 [ Linux ] virtual/cors/external/wpt/referrer-policy/gen/worker-classic.http-rp/origin-when-cross-origin/worker-classic.http.html [ Pass Timeout ]
+
 # This test triggers existing leaky behavior, but this test also catches
 # a prior crash.
 crbug.com/1103082 [ Linux ] fast/forms/select/select-change-layout-object-crash.html [ Failure ]
diff --git a/third_party/blink/web_tests/NeverFixTests b/third_party/blink/web_tests/NeverFixTests
index f08063d2..ce8f6ea1 100644
--- a/third_party/blink/web_tests/NeverFixTests
+++ b/third_party/blink/web_tests/NeverFixTests
@@ -414,6 +414,17 @@
 [ Mac10.10 ] external/wpt/css/css-text/hyphens/hyphens-manual-inline-012.html [ Skip ]
 [ Mac10.10 ] external/wpt/css/css-text/hyphens/hyphens-none-013.html [ Skip ]
 
+# The spec is not web-compatbile, but no consensus at CSS WG yet.
+# crbug.com/267056
+# https://github.com/w3c/csswg-drafts/issues/1518
+external/wpt/css/css-text/letter-spacing/letter-spacing-100.html [ Skip ]
+external/wpt/css/css-text/letter-spacing/letter-spacing-101.html [ Skip ]
+external/wpt/css/css-text/letter-spacing/letter-spacing-102.html [ Skip ]
+external/wpt/css/css-text/letter-spacing/letter-spacing-103.html [ Skip ]
+external/wpt/css/css-text/letter-spacing/letter-spacing-104.html [ Skip ]
+external/wpt/css/css-text/letter-spacing/letter-spacing-105.html [ Skip ]
+external/wpt/css/css-text/letter-spacing/letter-spacing-106.html [ Skip ]
+
 # CSS Text features that are not planned to implement.
 external/wpt/css/css-text/parsing/word-boundary-detection-computed.html [ Skip ]
 external/wpt/css/css-text/parsing/word-boundary-detection-invalid.html [ Skip ]
diff --git a/third_party/blink/web_tests/SlowTests b/third_party/blink/web_tests/SlowTests
index 513ddd6..abb1cec 100644
--- a/third_party/blink/web_tests/SlowTests
+++ b/third_party/blink/web_tests/SlowTests
@@ -544,3 +544,5 @@
 
 crbug.com/1093849 external/wpt/dom/nodes/Element-classlist.html [ Slow ]
 crbug.com/1093853 external/wpt/dom/ranges/Range-surroundContents.html [ Slow ]
+
+crbug.com/1104091 [ Linux ] webcodecs/videoframe-imagebitmap.html [ Slow ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 0b477f8..d2a8cf7c 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -444,7 +444,7 @@
 crbug.com/1050993 [ Linux ] virtual/gpu-rasterization/images/drag-image-descendant-painting-sibling.html [ Crash Pass Timeout ]
 
 # Flaky test.
-crbug.com/1054894 http/tests/images/image-decode-in-frame.html [ Pass Timeout ]
+crbug.com/1054894 [ Mac ] http/tests/images/image-decode-in-frame.html [ Pass Failure ]
 
 # ====== Paint team owned tests to here ======
 
@@ -2700,30 +2700,9 @@
 crbug.com/626703 [ Linux ] external/wpt/css/css-grid/alignment/grid-baseline-justify-001.html [ Failure ]
 crbug.com/626703 [ Mac ] external/wpt/css/css-grid/alignment/grid-baseline-justify-001.html [ Failure ]
 crbug.com/626703 [ Win ] external/wpt/css/css-grid/alignment/grid-baseline-justify-001.html [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/css/css-text/letter-spacing/letter-spacing-100.html [ Failure ]
-crbug.com/626703 [ Mac ] external/wpt/css/css-text/letter-spacing/letter-spacing-100.html [ Failure ]
-crbug.com/626703 [ Win ] external/wpt/css/css-text/letter-spacing/letter-spacing-100.html [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/css/css-text/letter-spacing/letter-spacing-102.html [ Failure ]
-crbug.com/626703 [ Mac ] external/wpt/css/css-text/letter-spacing/letter-spacing-102.html [ Failure ]
-crbug.com/626703 [ Win ] external/wpt/css/css-text/letter-spacing/letter-spacing-102.html [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/css/css-text/letter-spacing/letter-spacing-105.html [ Failure ]
-crbug.com/626703 [ Mac ] external/wpt/css/css-text/letter-spacing/letter-spacing-105.html [ Failure ]
-crbug.com/626703 [ Win ] external/wpt/css/css-text/letter-spacing/letter-spacing-105.html [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/css/css-text/letter-spacing/letter-spacing-104.html [ Failure ]
-crbug.com/626703 [ Mac ] external/wpt/css/css-text/letter-spacing/letter-spacing-104.html [ Failure ]
-crbug.com/626703 [ Win ] external/wpt/css/css-text/letter-spacing/letter-spacing-104.html [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/css/css-text/letter-spacing/letter-spacing-106.html [ Failure ]
-crbug.com/626703 [ Mac ] external/wpt/css/css-text/letter-spacing/letter-spacing-106.html [ Failure ]
-crbug.com/626703 [ Win ] external/wpt/css/css-text/letter-spacing/letter-spacing-106.html [ Failure ]
 crbug.com/626703 [ Linux ] virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-baseline-align-001.html [ Failure ]
 crbug.com/626703 [ Mac ] virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-baseline-align-001.html [ Failure ]
 crbug.com/626703 [ Win ] virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-baseline-align-001.html [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/css/css-text/letter-spacing/letter-spacing-101.html [ Failure ]
-crbug.com/626703 [ Mac ] external/wpt/css/css-text/letter-spacing/letter-spacing-101.html [ Failure ]
-crbug.com/626703 [ Win ] external/wpt/css/css-text/letter-spacing/letter-spacing-101.html [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/css/css-text/letter-spacing/letter-spacing-103.html [ Failure ]
-crbug.com/626703 [ Mac ] external/wpt/css/css-text/letter-spacing/letter-spacing-103.html [ Failure ]
-crbug.com/626703 [ Win ] external/wpt/css/css-text/letter-spacing/letter-spacing-103.html [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/css/css-grid/alignment/grid-baseline-align-001.html [ Failure ]
 crbug.com/626703 [ Mac ] external/wpt/css/css-grid/alignment/grid-baseline-align-001.html [ Failure ]
 crbug.com/626703 [ Win ] external/wpt/css/css-grid/alignment/grid-baseline-align-001.html [ Failure ]
@@ -6074,7 +6053,6 @@
 
 # Sheriff 2019-10-16
 crbug.com/1014812 external/wpt/animation-worklet/playback-rate.https.html [ Pass Timeout Failure ]
-crbug.com/1014950 [ Mac ] http/tests/devtools/tracing/timeline-js/timeline-js-line-level-profile-no-url-end-to-end.js [ Pass Timeout ]
 
 # Sheriff 2019-10-18
 crbug.com/1015859 [ Linux ] http/tests/devtools/a11y-axe-core/performance/landing-page-a11y-test.js [ Pass Failure ]
@@ -6161,7 +6139,6 @@
 crbug.com/878878 [ Win ] virtual/threaded/fast/scroll-snap/snaps-after-keyboard-scrolling.html [ Pass Timeout ]
 crbug.com/878878 [ Mac ] virtual/threaded/fast/scroll-snap/snaps-after-scrollbar-scrolling.html [ Pass Timeout Failure ]
 crbug.com/878878 [ Win ] virtual/threaded/fast/scroll-snap/snaps-after-scrollbar-scrolling.html [ Pass Timeout Failure ]
-crbug.com/878878 [ Mac ] virtual/threaded/fast/scroll-snap/snaps-for-different-key-granularity.html [ Timeout ]
 
 crbug.com/878878 virtual/threaded/fast/scroll-snap/animate-fling-to-snap-points.html [ Failure Pass Timeout ]
 crbug.com/878878 virtual/threaded/fast/scroll-snap/snap-scrolls-visual-viewport.html [ Failure Pass ]
@@ -6332,7 +6309,6 @@
 #crbug.com/1044823 [ Release ] http/tests/devtools/extensions/extensions-timeline-api.js [ Pass Timeout ]
 crbug.com/1044829 [ Release Win ] http/tests/devtools/cache-storage/cache-deletion.js [ Pass Timeout ]
 crbug.com/1044829 [ Release Mac ] http/tests/devtools/cache-storage/cache-deletion.js [ Pass Timeout ]
-crbug.com/1043847 [ Release Win ] virtual/threaded/fast/scroll-snap/snaps-for-different-key-granularity.html [ Pass Timeout ]
 crbug.com/1043901 [ Release Win ] external/wpt/preload/preload-with-type.html [ Pass Timeout ]
 crbug.com/1044418 [ Release Mac ] http/tests/devtools/elements/shadow/elements-panel-shadow-selection-on-refresh-2.js [ Pass Timeout ]
 
@@ -6676,10 +6652,6 @@
 crbug.com/1084256 [ Linux ] http/tests/misc/insert-iframe-into-xml-document-before-xsl-transform.html [ Pass Failure ]
 crbug.com/1084276 [ Mac ] http/tests/security/offscreencanvas-placeholder-read-blocked-no-crossorigin.html [ Pass Failure ]
 
-# Sheriff 2020-05-19
-crbug.com/1084509 [ Win ] fast/forms/select/listbox-appearance-basic.html [ Pass Failure ]
-crbug.com/1084509 [ Win ] fast/forms/select/select-disabled-appearance.html [ Pass Failure ]
-
 # Disabled for enabling Wasm BigInt support
 crbug.com/1097765 external/wpt/wasm/jsapi/constructor/instantiate-bad-imports.any.html [ Failure Pass ]
 crbug.com/1097765 external/wpt/wasm/jsapi/constructor/instantiate-bad-imports.any.worker.html [ Failure Pass ]
@@ -6876,9 +6848,6 @@
 # Additionally disabled on mac due to crbug.com/988248
 crbug.com/1083302 media/controls/volumechange-muted-attribute.html [ Pass Failure ]
 
-# Sheriff 2020-07-09
-crbug.com/1103701 [ Win7 ] fast/forms/month/month-picker-appearance-zoom150.html [ Pass Failure ]
-
 # Sheriff 2020-07-10
 crbug.com/1104118 [ Win ] fast/forms/color/color-suggestion-picker-appearance-value-attribute.html [ Pass Failure ]
 crbug.com/1104125 [ Mac ] external/wpt/clipboard-apis/feature-policy/clipboard-read/clipboard-read-enabled-on-self-origin-by-feature-policy.tentative.https.sub.html [ Pass Failure ]
@@ -6890,15 +6859,29 @@
 
 crbug.com/1104186 [ Win ] fast/forms/color-scheme/suggestion-picker/time-suggestion-picker-appearance.html [ Pass Failure ]
 
-crbug.com/1104195 inspector-protocol/css/css-set-effective-property-value.js [ Pass Failure ]
-crbug.com/1104333 http/tests/devtools/bindings/inline-styles-binding.js [ Pass Failure ]
+crbug.com/1104195 inspector-protocol/css/css-set-effective-property-value.js [ Pass Failure Crash ]
+crbug.com/1104333 http/tests/devtools/bindings/inline-styles-binding.js [ Pass Failure Crash ]
 
 # Sheriff 2020-07-13
 
 crbug.com/1104910 [ Mac ] external/wpt/webaudio/the-audio-api/the-oscillatornode-interface/osc-basic-waveform.html [ Pass Failure ]
-crbug.com/1104910 [ Mac10.13 ] fast/dom/cssTarget-crash.html [ Pass Failure ]
-crbug.com/1104910 [ Mac10.13 ] fast/peerconnection/RTCPeerConnection-reload-interesting-usage.html [ Pass Failure ]
-crbug.com/1104910 [ Win ] http/tests/devtools/tracing/timeline-js/timeline-js-line-level-profile-no-url-end-to-end.js [ Pass Failure ]
-crbug.com/1104910 [ Linux ] inspector-protocol/stylesheet-tracking-restart.js [ Pass Failure ]
-crbug.com/1104910 [ Linux ] virtual/web-components-v0-disabled/fast/dom/cssTarget-crash.html [ Pass Failure ]
-crbug.com/1104910 [ Win ] virtual/cache-storage-eager-reading/external/wpt/service-workers/service-worker/registration-updateviacache.https.html [ Pass Failure ] 
\ No newline at end of file
+crbug.com/1104910 fast/dom/cssTarget-crash.html [ Pass Timeout Failure ]
+crbug.com/1104910 fast/peerconnection/RTCPeerConnection-reload-interesting-usage.html [ Pass Failure ]
+crbug.com/1104910 inspector-protocol/stylesheet-tracking-restart.js [ Pass Failure ]
+crbug.com/1104910 virtual/web-components-v0-disabled/fast/dom/cssTarget-crash.html [ Pass Timeout Failure ]
+crbug.com/1104910 [ Win ] virtual/cache-storage-eager-reading/external/wpt/service-workers/service-worker/registration-updateviacache.https.html [ Pass Failure ]
+crbug.com/1104910 [ Win ] http/tests/devtools/elements/styles-1/dynamic-style-tag.js [ Pass Failure ]
+crbug.com/1104910 [ Mac10.15 ] editing/selection/selection-background.html [ Pass Failure ]
+crbug.com/1104910 [ Linux ] external/wpt/referrer-policy/gen/worker-module.http-rp/unsafe-url/worker-classic.http.html [ Pass Failure ]
+crbug.com/1104910 [ Linux ] external/wpt/referrer-policy/gen/worker-module.http-rp/unset/worker-module.http.html [ Pass Failure ]
+
+# Sheriff 2020-07-14
+
+crbug.com/1105270 [ Mac10.15 ] fast/events/open-window-from-another-frame.html [ Pass Timeout ]
+crbug.com/1105271 [ Mac ] virtual/prefer_compositing_to_lcd_text/scrollbars/custom-scrollbar-adjust-on-inactive-pseudo.html [ Pass Failure ]
+crbug.com/1105274 http/tests/devtools/tracing/timeline-js/timeline-js-line-level-profile-no-url-end-to-end.js [ Pass Timeout Failure ]
+crbug.com/1105275 [ Mac ] fast/dom/Window/window-onFocus.html [ Pass Failure ]
+
+crbug.com/1105276 [ Mac ] virtual/threaded/fast/scroll-snap/snaps-after-keyboard-scrolling-rtl.html [ Pass Failure ]
+crbug.com/1105278 [ Mac ] external/wpt/clipboard-apis/feature-policy/clipboard-write/clipboard-write-enabled-on-self-origin-by-feature-policy.tentative.https.sub.html [ Pass Failure ]
+crbug.com/1105279 [ Mac ] virtual/threaded/fast/scroll-snap/snaps-for-different-key-granularity.html [ Pass Timeout Failure ]
diff --git a/third_party/blink/web_tests/android/ClankWPTOverrideExpectations b/third_party/blink/web_tests/android/ClankWPTOverrideExpectations
index 6b3a8a0..bfe5985 100644
--- a/third_party/blink/web_tests/android/ClankWPTOverrideExpectations
+++ b/third_party/blink/web_tests/android/ClankWPTOverrideExpectations
@@ -3192,28 +3192,28 @@
 crbug.com/1050754 external/wpt/orientation-sensor/RelativeOrientationSensor-iframe-access.https.html [ Failure ]
 crbug.com/1050754 external/wpt/orientation-sensor/RelativeOrientationSensor.https.html [ Failure ]
 crbug.com/1050754 external/wpt/origin-isolation/about-blank.https.sub.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/getter-data-url.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/getter-removed-iframe.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/getter-sandboxed-iframe.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/getter-special-cases/data-url.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/getter-special-cases/removed-iframe.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/getter-special-cases/sandboxed-iframe.sub.https.html [ Failure ]
 crbug.com/1050754 external/wpt/origin-isolation/going-back.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-no-child-bad-subdomain.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-no-child-yes-same.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-no-child-yes-subdomain.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-no-child-yes-with-params-subdomain.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-no-child1-no-child2-yes-children-different.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-no-child1-no-child2-yes-children-same.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-no-child1-yes-child2-no-children-same.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child-no-same.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child-no-subdomain.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child-yes-same.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child-yes-subdomain.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child1-no-child2-no-children-different.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child1-no-child2-no-children-same.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child1-no-child2-yes-children-different.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child1-no-child2-yes-children-same.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child1-yes-child2-no-children-same.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child1-yes-child2-yes-children-different.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child1-yes-child2-yes-children-same.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/1-iframe/parent-no-child-bad-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/1-iframe/parent-no-child-yes-same.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/1-iframe/parent-no-child-yes-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/1-iframe/parent-no-child-yeswithparams-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/1-iframe/parent-yes-child-no-same.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/1-iframe/parent-yes-child-no-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/1-iframe/parent-yes-child-yes-same.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/1-iframe/parent-yes-child-yes-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-no-child1-no-subdomain-child2-yes-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-no-child1-no-subdomain1-child2-yes-subdomain2.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-no-child1-yes-subdomain-child2-no-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html [ Failure ]
 crbug.com/1050754 external/wpt/origin-isolation/removing-iframes.sub.https.html [ Failure ]
 crbug.com/1050754 external/wpt/origin-policy/bad-server/bad-headers.https.html [ Failure ]
 crbug.com/1050754 external/wpt/origin-policy/bad-server/manifest-404.https.html [ Failure ]
diff --git a/third_party/blink/web_tests/android/WeblayerWPTOverrideExpectations b/third_party/blink/web_tests/android/WeblayerWPTOverrideExpectations
index 72f3477..5a8fd7a 100644
--- a/third_party/blink/web_tests/android/WeblayerWPTOverrideExpectations
+++ b/third_party/blink/web_tests/android/WeblayerWPTOverrideExpectations
@@ -3064,28 +3064,28 @@
 crbug.com/1050754 external/wpt/orientation-sensor/RelativeOrientationSensor-iframe-access.https.html [ Failure ]
 crbug.com/1050754 external/wpt/orientation-sensor/RelativeOrientationSensor.https.html [ Failure ]
 crbug.com/1050754 external/wpt/origin-isolation/about-blank.https.sub.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/getter-data-url.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/getter-removed-iframe.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/getter-sandboxed-iframe.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/getter-special-cases/data-url.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/getter-special-cases/removed-iframe.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/getter-special-cases/sandboxed-iframe.sub.https.html [ Failure ]
 crbug.com/1050754 external/wpt/origin-isolation/going-back.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-no-child-bad-subdomain.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-no-child-yes-same.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-no-child-yes-subdomain.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-no-child-yes-with-params-subdomain.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-no-child1-no-child2-yes-children-different.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-no-child1-no-child2-yes-children-same.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-no-child1-yes-child2-no-children-same.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child-no-same.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child-no-subdomain.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child-yes-same.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child-yes-subdomain.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child1-no-child2-no-children-different.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child1-no-child2-no-children-same.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child1-no-child2-yes-children-different.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child1-no-child2-yes-children-same.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child1-yes-child2-no-children-same.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child1-yes-child2-yes-children-different.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child1-yes-child2-yes-children-same.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/1-iframe/parent-no-child-bad-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/1-iframe/parent-no-child-yes-same.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/1-iframe/parent-no-child-yes-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/1-iframe/parent-no-child-yeswithparams-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/1-iframe/parent-yes-child-no-same.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/1-iframe/parent-yes-child-no-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/1-iframe/parent-yes-child-yes-same.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/1-iframe/parent-yes-child-yes-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-no-child1-no-subdomain-child2-yes-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-no-child1-no-subdomain1-child2-yes-subdomain2.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-no-child1-yes-subdomain-child2-no-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html [ Failure ]
 crbug.com/1050754 external/wpt/origin-isolation/removing-iframes.sub.https.html [ Failure ]
 crbug.com/1050754 external/wpt/origin-policy/bad-server/bad-headers.https.html [ Failure ]
 crbug.com/1050754 external/wpt/origin-policy/bad-server/manifest-404.https.html [ Failure ]
diff --git a/third_party/blink/web_tests/android/WebviewWPTOverrideExpectations b/third_party/blink/web_tests/android/WebviewWPTOverrideExpectations
index edcf6c83..8611bb5 100644
--- a/third_party/blink/web_tests/android/WebviewWPTOverrideExpectations
+++ b/third_party/blink/web_tests/android/WebviewWPTOverrideExpectations
@@ -3319,30 +3319,32 @@
 crbug.com/1050754 external/wpt/orientation-sensor/AbsoluteOrientationSensor.https.html [ Failure ]
 crbug.com/1050754 external/wpt/orientation-sensor/RelativeOrientationSensor-iframe-access.https.html [ Failure ]
 crbug.com/1050754 external/wpt/orientation-sensor/RelativeOrientationSensor.https.html [ Failure ]
+
 crbug.com/1050754 external/wpt/origin-isolation/about-blank.https.sub.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/getter-data-url.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/getter-removed-iframe.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/getter-sandboxed-iframe.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/getter-special-cases/data-url.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/getter-special-cases/removed-iframe.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/getter-special-cases/sandboxed-iframe.sub.https.html [ Failure ]
 crbug.com/1050754 external/wpt/origin-isolation/going-back.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-no-child-bad-subdomain.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-no-child-yes-same.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-no-child-yes-subdomain.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-no-child-yes-with-params-subdomain.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-no-child1-no-child2-yes-children-different.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-no-child1-no-child2-yes-children-same.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-no-child1-yes-child2-no-children-same.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child-no-same.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child-no-subdomain.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child-yes-same.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child-yes-subdomain.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child1-no-child2-no-children-different.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child1-no-child2-no-children-same.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child1-no-child2-yes-children-different.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child1-no-child2-yes-children-same.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child1-yes-child2-no-children-same.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child1-yes-child2-yes-children-different.sub.https.html [ Failure ]
-crbug.com/1050754 external/wpt/origin-isolation/parent-yes-child1-yes-child2-yes-children-same.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/1-iframe/parent-no-child-bad-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/1-iframe/parent-no-child-yes-same.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/1-iframe/parent-no-child-yes-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/1-iframe/parent-no-child-yeswithparams-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/1-iframe/parent-yes-child-no-same.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/1-iframe/parent-yes-child-no-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/1-iframe/parent-yes-child-yes-same.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/1-iframe/parent-yes-child-yes-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-no-child1-no-subdomain-child2-yes-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-no-child1-no-subdomain1-child2-yes-subdomain2.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-no-child1-yes-subdomain-child2-no-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html [ Failure ]
+crbug.com/1050754 external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html [ Failure ]
 crbug.com/1050754 external/wpt/origin-isolation/removing-iframes.sub.https.html [ Failure ]
+
 crbug.com/1050754 external/wpt/origin-policy/bad-server/bad-headers.https.html [ Failure ]
 crbug.com/1050754 external/wpt/origin-policy/bad-server/manifest-404.https.html [ Failure ]
 crbug.com/1050754 external/wpt/origin-policy/bad-server/manifest-mimetype.https.html [ Failure ]
diff --git a/third_party/blink/web_tests/external/PRESUBMIT.py b/third_party/blink/web_tests/external/PRESUBMIT.py
index b1ea19f..6e51796 100644
--- a/third_party/blink/web_tests/external/PRESUBMIT.py
+++ b/third_party/blink/web_tests/external/PRESUBMIT.py
@@ -38,6 +38,7 @@
         'lint',
         '--repo-root=%s' % wpt_path,
         '--ignore-glob=*-expected.txt',
+        '--ignore-glob=DIR_METADATA',
     ] + paths_in_wpt
 
     proc = input_api.subprocess.Popen(
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index 492ed176..6b10422 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -78937,6 +78937,71 @@
        {}
       ]
      ],
+     "position-relative-001.html": [
+      "7ec9e4f767b4548e52e92b358b59e51376dea389",
+      [
+       null,
+       [
+        [
+         "/css/reference/ref-filled-green-100px-square.xht",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "position-relative-002.html": [
+      "7e176be987e7d6cb52bfa79ef58ec1ee93634375",
+      [
+       null,
+       [
+        [
+         "/css/reference/ref-filled-green-100px-square.xht",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "position-relative-003.html": [
+      "7a0040c40b534da6367fcc0d0a94ac3f7650bc50",
+      [
+       null,
+       [
+        [
+         "/css/reference/ref-filled-green-100px-square.xht",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "position-relative-004.html": [
+      "aac4520f728bb833b2ecaaf5c1a03e904e333040",
+      [
+       null,
+       [
+        [
+         "/css/reference/ref-filled-green-100px-square.xht",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "position-relative-005.html": [
+      "f1ad0846741704285603dff9d719790676f9e9fe",
+      [
+       null,
+       [
+        [
+         "/css/reference/ref-filled-green-100px-square.xht",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "position-relative-table-tbody-left-absolute-child.html": [
       "98e759a8c0a83817b3d691503e807ed5ed549936",
       [
@@ -211095,6 +211160,10 @@
         "0edc7e4bd74d1e83a352524970c9db1b65cf1e90",
         []
        ],
+       "pitch-detector.js": [
+        "375fef77c02ab12144978eaf7dc7555ea7ebade6",
+        []
+       ],
        "playing-the-media-resource": {
         "pause-move-to-other-document-expected.txt": [
          "aa9a81c300cf2ca6de1c1b7a5c613b6e578770de",
@@ -216395,7 +216464,7 @@
      []
     ],
     "pointerevents.idl": [
-     "8a42b5a8446b843c01f6a76c824bdf022b36b646",
+     "e977ce734205fe8ded6524d35887e036c2895322",
      []
     ],
     "pointerlock.idl": [
@@ -217870,6 +217939,10 @@
      "bf6e253b3df4dcc8981562b126bdfd58459a128d",
      []
     ],
+    "sine440.mp3": [
+     "4ded8b338025da1b6bec8d0c25d376bcaed5a64a",
+     []
+    ],
     "sound_0.mp3": [
      "a15d1de328f3f3325cb9589d423abf60d538d838",
      []
@@ -227864,7 +227937,7 @@
       ],
       "tasks": {
        "test.yml": [
-        "9396ce02afab697f3d63d5a55519f8536e356b0d",
+        "657abf1b8e30744468f090889063f7ee4c36147b",
         []
        ]
       },
@@ -227896,7 +227969,7 @@
         []
        ],
        "test_valid.py": [
-        "6ac76f484e14bc919102b7d76cb85f5d36daac66",
+        "204465a8297b4c564f2f30500c6c7af51e56a5ec",
         []
        ]
       }
@@ -245335,6 +245408,31 @@
       {}
      ]
     ],
+    "idbobjectstore_putall.tentative.any.js": [
+     "c66669595ea16b74000c11ad8163ec19c93e35fa",
+     [
+      "IndexedDB/idbobjectstore_putall.tentative.any.html",
+      {
+       "script_metadata": [
+        [
+         "script",
+         "support-promises.js"
+        ]
+       ]
+      }
+     ],
+     [
+      "IndexedDB/idbobjectstore_putall.tentative.any.worker.html",
+      {
+       "script_metadata": [
+        [
+         "script",
+         "support-promises.js"
+        ]
+       ]
+      }
+     ]
+    ],
     "idbrequest-onupgradeneeded.htm": [
      "73f22697f6eeb8d1a485cff382931f573470b81c",
      [
@@ -259111,6 +259209,15 @@
     ]
    },
    "clipboard-apis": {
+    "async-html-script-removal.https.html": [
+     "90dc44c9db20e31ceae6bead41b956754cfe9d9d",
+     [
+      null,
+      {
+       "testdriver": true
+      }
+     ]
+    ],
     "async-idlharness.https.html": [
      "a1a51c98690f1c83679c3ae71f3bafe99824f025",
      [
@@ -259154,6 +259261,15 @@
       }
      ]
     ],
+    "async-write-html-read-html.https.html": [
+     "7c271b9bd78d1b68b86a911923789c32f02210fe",
+     [
+      null,
+      {
+       "testdriver": true
+      }
+     ]
+    ],
     "async-write-image-read-image.https.html": [
      "281358dfc5f8d68d3d6d436fdd92dbc8fb8b7b5d",
      [
@@ -271007,6 +271123,13 @@
        {}
       ]
      ],
+     "fallback-url-to-local.html": [
+      "c30af93d149e1df045c53e2403ad3a4c56dd6ce3",
+      [
+       null,
+       {}
+      ]
+     ],
      "font-display": {
       "font-display-failure-fallback.html": [
        "00b528298b583cc74e013cdeff917f22290f908b",
@@ -273692,6 +273815,13 @@
         {}
        ]
       ],
+      "grid-auto-explicit-rows-001.html": [
+       "81b531ca347cfaaa47f6ef46f3791f0fff60a779",
+       [
+        null,
+        {}
+       ]
+      ],
       "grid-auto-fill-columns-001.html": [
        "44c60801010a5585f144a8bf5775ae86620d9529",
        [
@@ -338769,7 +338899,7 @@
         ]
        ],
        "historical.html": [
-        "d98a74df4e158d65672520a4eedb6929c76442ea",
+        "d7395632eb909722117dea9dbcf0f74d095ec865",
         [
          null,
          {}
@@ -339479,6 +339609,15 @@
          {}
         ]
        ],
+       "preserves-pitch.html": [
+        "dad5f94e07be59f05804a7e29e0978cc4e89b2b8",
+        [
+         null,
+         {
+          "testdriver": true
+         }
+        ]
+       ],
        "ready-states": {
         "autoplay-hidden.optional.html": [
          "c009f55bfcad3efe7bb8681b048f23f757ba8e03",
@@ -399032,7 +399171,7 @@
        ]
       ],
       "osc-basic-waveform.html": [
-       "bebcc6abc43779569c47c8f02866a610295e8ee1",
+       "b34c96855fdbb26428c64ed879d41ec787fb8d85",
        [
         null,
         {}
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/fallback-url-to-local.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/fallback-url-to-local.html
new file mode 100644
index 0000000..c30af93
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/fallback-url-to-local.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test that font fallback from a pending web font to an available local font works.</title>
+<link rel=help href="https://drafts.csswg.org/css-fonts-4/#font-matching-algorithm">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1101483">
+<link rel=author href="mailto:xiaochengh@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+@font-face {
+  font-family: remote-font;
+  src: url(/fonts/Revalia.woff?pipe=trickle(d1)) format(woff);
+}
+
+@font-face {
+  font-family: local-font;
+  src: local(Ahem);
+}
+
+#target {
+  font: 25px/1 remote-font, local-font, monospace;
+}
+</style>
+<span id="target">0123456789</span>
+<script>
+test(() => {
+  assert_false(document.fonts.check('25px/1 remote-font'));
+  assert_equals(target.offsetWidth, 250);
+}, 'We should use the local font to render the page when the primary remote font is loading');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-definition/grid-auto-explicit-rows-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-definition/grid-auto-explicit-rows-001.html
new file mode 100644
index 0000000..81b531c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-definition/grid-auto-explicit-rows-001.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<title>grid-auto applies to explicit rows</title>
+
+<link rel=author title="Tab Atkins-Bittner" href="https://www.xanthir.com/contact/">
+<link rel=help href="https://www.w3.org/TR/css-grid-1/#implicit-grids">
+<meta name=assert content="grid-auto-rows/columns should dictate the size of all rows past grid-template-rows/columns, even if they are part of the explicit grid as established by grid-template-areas; they're not just limited to implicit tracks.">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<style>
+.grid {
+ display: grid;
+ grid-template-areas: "a b c" "d e f" "g e h";
+ grid-template-rows: 11px 13px;
+ grid-auto-rows: 17px 19px;
+ grid-template-columns: 23px 29px;
+ grid-auto-columns: 31px 37px;
+}
+
+.grid > div {
+ background: rgba(0, 0, 0, .2);
+}
+#c1 { grid-area: 1 / 1 / 2 / 2; }
+#c2 { grid-area: 1 / 1 / 3 / 3; }
+#c3 { grid-area: 1 / 1 / 4 / 4; }
+#c4 { grid-area: 1 / 1 / 5 / 5; }
+</style>
+
+<div class=grid>
+ <div id=c1 data-expected-width=23 data-expected-height=11></div>
+ <div id=c2 data-expected-width=52 data-expected-height=24></div>
+ <div id=c3 data-expected-width=83 data-expected-height=41></div>
+ <div id=c4 data-expected-width=120 data-expected-height=60></div>
+</div>
+
+<script>
+checkLayout('.grid');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/historical.html b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/historical.html
index d98a74df..d7395632 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/historical.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/historical.html
@@ -36,6 +36,8 @@
 t('audio', 'video'); // added in r5636, replaced with muted in r5991.
 t('startDate'); // added in r7045, replaced with getStartDate() in r8113.
 t('mozSrcObject'); // never in the spec
+t('mozPreservesPitch'); // prefixed version should be removed.
+t('webkitPreservesPitch'); // prefixed version should be removed.
 
 // TextTrackCue constructor: added in r5723, removed in r7742.
 test(function() {
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/pitch-detector.js b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/pitch-detector.js
new file mode 100644
index 0000000..375fef7
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/pitch-detector.js
@@ -0,0 +1,48 @@
+// This should be removed when the webaudio/historical.html tests are passing.
+// Tracking bug: https://bugs.webkit.org/show_bug.cgi?id=204719
+window.AudioContext = window.AudioContext || window.webkitAudioContext;
+
+var FFT_SIZE = 2048;
+
+function getPitchDetector(media, t) {
+  var audioContext = new AudioContext();
+  t.add_cleanup(() => audioContext.close());
+
+  var sourceNode = audioContext.createMediaElementSource(media);
+
+  var analyser = audioContext.createAnalyser();
+  analyser.fftSize = FFT_SIZE;
+
+  sourceNode.connect(analyser);
+  analyser.connect(audioContext.destination);
+
+  // Returns the frequency value for the nth FFT bin.
+  var binConverter = (bin) => audioContext.sampleRate*(bin/FFT_SIZE);
+
+  return () => getPitch(analyser, binConverter);
+}
+
+function getPitch(analyser, binConverter) {
+  var buf = new Uint8Array(FFT_SIZE/2);
+  analyser.getByteFrequencyData(buf);
+  return findDominantFrequency(buf, binConverter);
+}
+
+// Returns the dominant frequency, +/- a certain margin.
+function findDominantFrequency(buf, binConverter) {
+  var max = 0;
+  var bin = 0;
+
+  for (var i=0;i<buf.length;i++) {
+    if(buf[i] > max) {
+      max = buf[i];
+      bin = i;
+    }
+  }
+
+  // The distance between bins is always constant and corresponds to
+  // (1/FFT_SIZE)th of the sample rate. Use the frequency value of the 1st bin
+  // as the margin directly, instead of calculating an average from the values
+  // of the neighboring bins.
+  return { value:binConverter(bin), margin:binConverter(1) };
+}
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/preserves-pitch.html b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/preserves-pitch.html
new file mode 100644
index 0000000..dad5f94
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/preserves-pitch.html
@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+<title>Test preservesPitch.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="pitch-detector.js"></script>
+<script>
+
+// Remove when media-elements/historical.html's preservePitch prefix tests are are passing.
+function getPreservesPitch(audio) {
+    if ("preservesPitch" in HTMLAudioElement.prototype) {
+        return audio.preservesPitch;
+    }
+    if ("mozPreservesPitch" in HTMLAudioElement.prototype) {
+        return audio.mozPreservesPitch;
+    }
+    if ("wekbitPreservesPitch" in HTMLAudioElement.prototype) {
+        return audio.wekbitPreservesPitch;
+    }
+    return undefined;
+}
+
+// Remove when media-elements/historical.html's preservePitch prefix tests are are passing.
+function setPreservesPitch(audio, value) {
+    if ("preservesPitch" in HTMLAudioElement.prototype) {
+        audio.preservesPitch = value;
+    } else if ("mozPreservesPitch" in HTMLAudioElement.prototype) {
+        audio.mozPreservesPitch = value;
+    } else if ("wekbitPreservesPitch" in HTMLAudioElement.prototype) {
+        audio.wekbitPreservesPitch = value;
+    }
+}
+
+test(function(t) {
+    assert_true("preservesPitch" in HTMLAudioElement.prototype);
+}, "Test that preservesPitch is present and unprefixed.");
+
+test(function(t) {
+    let audio = document.createElement('audio');
+    assert_true(getPreservesPitch(audio));
+
+    setPreservesPitch(audio, false);
+    assert_false(getPreservesPitch(audio));
+}, "Test that presevesPitch is on by default");
+
+function testPreservesPitch(preservesPitch, playbackRate, expectedPitch, description) {
+    promise_test(async t => {
+        let audio = document.createElement('audio');
+
+        var detector = getPitchDetector(audio, t);
+
+        // This file contains 5 seconds of a 440hz sine wave.
+        audio.src = "/media/sine440.mp3";
+
+        audio.playbackRate = playbackRate;
+        setPreservesPitch(audio, preservesPitch);
+
+        function promiseTimeUpdate() {
+            return new Promise((resolve) => audio.ontimeupdate = resolve);
+        }
+
+        function verifyPitch() {
+            var pitch = detector();
+
+            // 25Hz is larger than the margin we get from 48kHz and 44.1kHz
+            // audio being analyzed by a FFT of size 2048. If we get something
+            // different, there is an error within the test's calculations (or
+            // we might be dealing a larger sample rate).
+            assert_less_than(pitch.margin, 25,
+                "Test error: the margin should be reasonably small.")
+
+            assert_approx_equals(pitch.value, expectedPitch, pitch.margin,
+                "The actual pitch should be close to the expected pitch.");
+        }
+
+        await test_driver.bless("Play audio element", () => audio.play() )
+          .then(promiseTimeUpdate)
+          .then(verifyPitch);
+    }, description);
+}
+
+var REFERENCE_PITCH = 440;
+
+testPreservesPitch(true, 1.0, REFERENCE_PITCH,
+    "The default playbackRate should not affect pitch")
+
+testPreservesPitch(false, 1.0, REFERENCE_PITCH,
+    "The default playbackRate should not affect pitch, even with preservesPitch=false")
+
+testPreservesPitch(true, 2.0, REFERENCE_PITCH,
+    "Speed-ups should not change the pitch when preservesPitch=true")
+
+testPreservesPitch(true, 0.5, REFERENCE_PITCH,
+    "Slow-downs should not change the pitch when preservesPitch=true")
+
+testPreservesPitch(false, 2.0, REFERENCE_PITCH*2.0,
+    "Speed-ups should change the pitch when preservesPitch=false")
+
+testPreservesPitch(false, 0.5, REFERENCE_PITCH*0.5,
+    "Slow-downs should change the pitch when preservesPitch=false")
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/pointerevents.idl b/third_party/blink/web_tests/external/wpt/interfaces/pointerevents.idl
index 8a42b5a..e977ce7 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/pointerevents.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/pointerevents.idl
@@ -9,11 +9,11 @@
     double      height = 1;
     float       pressure = 0;
     float       tangentialPressure = 0;
-    long        tiltX = 0;
-    long        tiltY = 0;
+    long        tiltX;
+    long        tiltY;
     long        twist = 0;
-    double      altitudeAngle = 0;
-    double      azimuthAngle = 0;
+    double      altitudeAngle;
+    double      azimuthAngle;
     DOMString   pointerType = "";
     boolean     isPrimary = false;
     sequence<PointerEvent> coalescedEvents = [];
diff --git a/third_party/blink/web_tests/external/wpt/media/sine440.mp3 b/third_party/blink/web_tests/external/wpt/media/sine440.mp3
new file mode 100644
index 0000000..4ded8b3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/media/sine440.mp3
Binary files differ
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-no-child-bad-subdomain.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-no-child-bad-subdomain.sub.https.html
new file mode 100644
index 0000000..8c18711
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-no-child-bad-subdomain.sub.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is not isolated, child attempts to isolate but uses a bad header value, child is a subdomain of the parent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testSameAgentCluster,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+let frameIndex = 0;
+for (const badValue of ["", "?0", "true", "\"?1\"", "1", "?2", "(?1)"]) {
+  promise_test(async () => {
+    await insertIframe("{{hosts[][www]}}", badValue);
+  }, `"${badValue}": frame insertion`);
+
+  // Since the header values are bad there should be no isolation
+  testSameAgentCluster([self, frameIndex], `"${badValue}"`);
+  testOriginIsolationRestricted(frameIndex, false, `"${badValue}"`);
+  ++frameIndex;
+}
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-no-child-yes-port.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-no-child-yes-port.sub.https.html
new file mode 100644
index 0000000..10df411a96
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-no-child-yes-port.sub.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is not isolated, child is isolated, child is different-origin to the parent because of a port mismatch</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testDifferentAgentClusters
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+  await insertIframe("{{hosts[][]}}:{{ports[https][1]}}", "?1");
+});
+
+// Since they're different-origin, the child's isolation request is respected,
+// so the parent ends up in the site-keyed agent cluster and the child in the
+// origin-keyed one.
+
+testDifferentAgentClusters([self, 0]);
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-no-child-yes-same.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-no-child-yes-same.sub.https.html
new file mode 100644
index 0000000..034c8a3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-no-child-yes-same.sub.https.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is not isolated, child is isolated, child is same-origin to the parent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testSameAgentCluster,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+  await insertIframe("{{hosts[][]}}", "?1");
+});
+
+// Since they're same-origin, and the parent loaded without isolation, the
+// child's request for isolation gets ignored, and both end up site-keyed.
+testSameAgentCluster([self, 0]);
+testOriginIsolationRestricted(self, false, "parent");
+testOriginIsolationRestricted(0, false, "child");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-no-child-yes-subdomain.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-no-child-yes-subdomain.sub.https.html
new file mode 100644
index 0000000..3894f56
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-no-child-yes-subdomain.sub.https.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is not isolated, child is isolated, child is a subdomain of the parent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testDifferentAgentClusters,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+  await insertIframe("{{hosts[][www]}}", "?1");
+});
+
+// Since they're different-origin, the child's isolation request is respected,
+// so the parent ends up in the site-keyed agent cluster and the child in the
+// origin-keyed one.
+testDifferentAgentClusters([self, 0]);
+testOriginIsolationRestricted(self, false, "parent");
+testOriginIsolationRestricted(0, true, "child");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-no-child-yeswithparams-subdomain.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-no-child-yeswithparams-subdomain.sub.https.html
new file mode 100644
index 0000000..a47729b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-no-child-yeswithparams-subdomain.sub.https.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is not isolated, child is isolated using parameters on its structured header, child is a subdomain of the parent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testDifferentAgentClusters,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+  await insertIframe("{{hosts[][www]}}", "?1;param1;param2=value2");
+});
+
+// Since they're different-origin, the child's isolation request is respected,
+// so the parent ends up in the site-keyed agent cluster and the child in the
+// origin-keyed one.
+testDifferentAgentClusters([self, 0]);
+testOriginIsolationRestricted(self, false, "parent");
+testOriginIsolationRestricted(0, true, "child");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-no-port.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-no-port.sub.https.html
new file mode 100644
index 0000000..09389e33
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-no-port.sub.https.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is isolated, child is not isolated, child is is different-origin to the parent because of a port mismatch</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testDifferentAgentClusters
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+  await insertIframe("{{hosts[][]}}:{{ports[https][1]}}");
+});
+
+// Since they're different-origin, the parent's isolation request is respected,
+// as is the child's non-request. So the parent ends up in the origin-keyed
+// agent cluster and the child ends up in the site-keyed one.
+testDifferentAgentClusters([self, 0]);
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/getter-removed-iframe.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-no-port.sub.https.html.headers
similarity index 100%
copy from third_party/blink/web_tests/external/wpt/origin-isolation/getter-removed-iframe.sub.https.html.headers
copy to third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-no-port.sub.https.html.headers
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-no-same.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-no-same.sub.https.html
new file mode 100644
index 0000000..7070c1b8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-no-same.sub.https.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is isolated, child is not isolated, child is same-origin to the parent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testSameAgentCluster,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+  await insertIframe("{{hosts[][]}}");
+});
+
+// Since they're same-origin, and the parent loaded with isolation, the
+// child's non-request for isolation gets ignored, and both end up origin-keyed.
+testSameAgentCluster([self, 0]);
+
+testOriginIsolationRestricted(self, true, "parent");
+testOriginIsolationRestricted(0, true, "child");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-no-same.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-no-same.sub.https.html.headers
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-no-same.sub.https.html.headers
rename to third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-no-same.sub.https.html.headers
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-no-subdomain.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-no-subdomain.sub.https.html
new file mode 100644
index 0000000..32e5a17
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-no-subdomain.sub.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is isolated, child is not isolated, child is a subdomain of the parent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testDifferentAgentClusters,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+  await insertIframe("{{hosts[][www]}}");
+});
+
+// Since they're different-origin, the parent's isolation request is respected,
+// as is the child's non-request. So the parent ends up in the origin-keyed
+// agent cluster and the child ends up in the site-keyed one.
+testDifferentAgentClusters([self, 0]);
+
+testOriginIsolationRestricted(self, true, "parent");
+testOriginIsolationRestricted(0, false, "child");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-no-subdomain.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-no-subdomain.sub.https.html.headers
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-no-subdomain.sub.https.html.headers
rename to third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-no-subdomain.sub.https.html.headers
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-yes-port.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-yes-port.sub.https.html
new file mode 100644
index 0000000..96669c3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-yes-port.sub.https.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is isolated, child is not isolated, child is different-origin to the parent because of a port mismatch</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testDifferentAgentClusters
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+  await insertIframe("{{hosts[][]}}:{{ports[https][1]}}", "?1");
+});
+
+// Both request isolation, so the parent ends up in one origin-keyed agent
+// cluster (the default port's origin), and the child ends up in a different
+// origin-keyed agent cluster (the other port's origin).
+testDifferentAgentClusters([self, 0]);
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/getter-removed-iframe.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-yes-port.sub.https.html.headers
similarity index 100%
copy from third_party/blink/web_tests/external/wpt/origin-isolation/getter-removed-iframe.sub.https.html.headers
copy to third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-yes-port.sub.https.html.headers
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-yes-same.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-yes-same.sub.https.html
new file mode 100644
index 0000000..a83582f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-yes-same.sub.https.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is isolated, child is not isolated, child is same-origin to the parent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testSameAgentCluster,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+  await insertIframe("{{hosts[][]}}", "?1");
+});
+
+// Both request isolation, and they're same-origin, so they both end up in the
+// same origin-keyed agent cluster.
+testSameAgentCluster([self, 0]);
+
+testOriginIsolationRestricted(self, true, "parent");
+testOriginIsolationRestricted(0, true, "child");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-yes-same.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-yes-same.sub.https.html.headers
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-yes-same.sub.https.html.headers
rename to third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-yes-same.sub.https.html.headers
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-yes-subdomain.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-yes-subdomain.sub.https.html
new file mode 100644
index 0000000..7010e4c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-yes-subdomain.sub.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is isolated, child is not isolated, child is a subdomain of the parent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testDifferentAgentClusters,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+  await insertIframe("{{hosts[][www]}}", "?1");
+});
+
+// Both request isolation, so the parent ends up in one origin-keyed agent
+// cluster (the base domain's origin), and the child ends up in a different
+// origin-keyed agent cluster (the www subdomain's origin).
+testDifferentAgentClusters([self, 0]);
+
+testOriginIsolationRestricted(self, true, "parent");
+testOriginIsolationRestricted(0, true, "child");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-yes-subdomain.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-yes-subdomain.sub.https.html.headers
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-yes-subdomain.sub.https.html.headers
rename to third_party/blink/web_tests/external/wpt/origin-isolation/1-iframe/parent-yes-child-yes-subdomain.sub.https.html.headers
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-no-child1-no-subdomain-child2-yes-subdomain.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-no-child1-no-subdomain-child2-yes-subdomain.sub.https.html
new file mode 100644
index 0000000..a8437c5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-no-child1-no-subdomain-child2-yes-subdomain.sub.https.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is not isolated, subdomain child 1 is not isolated, same-subdomain child 2 is isolated</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testSameAgentCluster,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+  // Must be sequential, not parallel: the non-isolated frame must load first.
+  await insertIframe("{{hosts[][www]}}");
+  await insertIframe("{{hosts[][www]}}", "?1");
+});
+
+
+// Since they're different-origin, the parent's isolation non-request is
+// respected, as is child 1's non-request. child 2 requests isolation
+// but is ignored, since child 1 is in the same browsing context group.
+//
+// So, everyone ends up in the site-keyed agent cluster.
+testSameAgentCluster([self, 0], "Parent to child1");
+testSameAgentCluster([self, 1], "Parent to child2");
+testSameAgentCluster([0, 1], "child1 to child2");
+testSameAgentCluster([1, 0], "child2 to child1");
+
+testOriginIsolationRestricted(self, false, "parent");
+testOriginIsolationRestricted(0, false, "child1");
+testOriginIsolationRestricted(1, false, "child2");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-no-child1-no-subdomain-child2-yes-subdomainport.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-no-child1-no-subdomain-child2-yes-subdomainport.sub.https.html
new file mode 100644
index 0000000..4103373d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-no-child1-no-subdomain-child2-yes-subdomainport.sub.https.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is not isolated, subdomain child 1 is not isolated, different-port-same-subdomain child 2 is isolated</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testSameAgentCluster,
+  testDifferentAgentClusters,
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+  // Order of loading should not matter, but we make it sequential to ensure the
+  // tests are deterministic.
+  await insertIframe("{{hosts[][www]}}");
+  await insertIframe("{{hosts[][www]}}:{{ports[https][1]}}", "?1");
+});
+
+// Since everybody is different-origin, everyone's requests/non-requests get
+// respected.
+//
+// So, the parent and child 1 end up in the site-keyed agent cluster, and child
+// 2 ends up in its own origin-keyed agent cluster.
+testSameAgentCluster([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testDifferentAgentClusters([0, 1], "child1 to child2");
+testDifferentAgentClusters([1, 0], "child2 to child1");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-no-child1-no-subdomain1-child2-yes-subdomain2.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-no-child1-no-subdomain1-child2-yes-subdomain2.sub.https.html
new file mode 100644
index 0000000..06d58b3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-no-child1-no-subdomain1-child2-yes-subdomain2.sub.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is not isolated, subdomain child 1 is not isolated, different-subdomain child 2 is isolated</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testSameAgentCluster,
+  testDifferentAgentClusters,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+  // Order of loading should not matter, but we make it sequential to ensure the
+  // tests are deterministic.
+  await insertIframe("{{hosts[][www]}}");
+  await insertIframe("{{hosts[][www1]}}", "?1");
+});
+
+
+// Since everybody is different-origin, everyone's requests/non-requests get
+// respected.
+//
+// So, the parent and child 1 end up in the site-keyed agent cluster, and child
+// 2 ends up in its own origin-keyed agent cluster.
+testSameAgentCluster([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testDifferentAgentClusters([0, 1], "child1 to child2");
+testDifferentAgentClusters([1, 0], "child2 to child1");
+
+testOriginIsolationRestricted(self, false, "parent");
+testOriginIsolationRestricted(0, false, "child1");
+testOriginIsolationRestricted(1, true, "child2");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-no-child1-yes-subdomain-child2-no-port.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-no-child1-yes-subdomain-child2-no-port.sub.https.html
new file mode 100644
index 0000000..68c9fe1a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-no-child1-yes-subdomain-child2-no-port.sub.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is not isolated, subdomain child 1 is isolated, non-subdomain but different-port child 2 is not isolated</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testSameAgentCluster,
+  testDifferentAgentClusters,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+  // Order of loading should not matter, but we make it sequential to ensure the
+  // tests are deterministic.
+  await insertIframe("{{hosts[][www]}}", "?1");
+  await insertIframe("{{hosts[][]}}:{{ports[https][1]}}");
+});
+
+// Everyone is different-origin, so everyone's request/non-request is
+// respected.
+//
+// So, the parent and child2 end up in the site-keyed agent cluster, and child1
+// ends up in an origin-keyed agent cluster.
+testDifferentAgentClusters([self, 0], "Parent to child1");
+testSameAgentCluster([self, 1], "Parent to child2");
+testDifferentAgentClusters([0, 1], "child1 to child2");
+testDifferentAgentClusters([1, 0], "child2 to child1");
+
+testOriginIsolationRestricted(self, false, "parent");
+testOriginIsolationRestricted(0, true, "child1");
+testOriginIsolationRestricted(1, false, "child2");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-no-child1-yes-subdomain-child2-no-subdomain.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-no-child1-yes-subdomain-child2-no-subdomain.sub.https.html
new file mode 100644
index 0000000..4e95827
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-no-child1-yes-subdomain-child2-no-subdomain.sub.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is not isolated, subdomain child 1 is isolated, same-subdomain child 2 is not isolated</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testSameAgentCluster,
+  testDifferentAgentClusters,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+  // Must be sequential, not parallel: the isolated frame must load first.
+  await insertIframe("{{hosts[][www]}}", "?1");
+  await insertIframe("{{hosts[][www]}}");
+});
+
+
+// Since they're different-origin, the parent's isolation non-request is
+// respected, as is child 1's request. child 2's non-request is
+// ignored, since child 1 is in the same browsing context group.
+//
+// So, the parent ends up in the site-keyed agent cluster, and both children end
+// up in an origin-keyed agent cluster.
+testDifferentAgentClusters([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testSameAgentCluster([0, 1], "child1 to child2");
+testSameAgentCluster([1, 0], "child2 to child1");
+
+testOriginIsolationRestricted(self, false, "parent");
+testOriginIsolationRestricted(0, true, "child1");
+testOriginIsolationRestricted(1, true, "child2");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html
new file mode 100644
index 0000000..f261b8e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is isolated, subdomain child 1 is not isolated, same-subdomain child 2 is not isolated</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testSameAgentCluster,
+  testDifferentAgentClusters,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+  // Order of loading should not matter, but we make it sequential to ensure the
+  // tests are deterministic.
+  await insertIframe("{{hosts[][www]}}");
+  await insertIframe("{{hosts[][www]}}");
+});
+
+
+// Since everybody is different-origin, everyone's requests/non-requests get
+// respected.
+//
+// So, the parent ends up in its origin-keyed agent cluster, and child 1 and
+// child 2 both end up in the site-keyed agent cluster.
+testDifferentAgentClusters([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testSameAgentCluster([0, 1], "child1 to child2");
+testSameAgentCluster([1, 0], "child2 to child1");
+
+testOriginIsolationRestricted(self, true, "parent");
+testOriginIsolationRestricted(0, false, "child1");
+testOriginIsolationRestricted(1, false, "child2");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-no-subdomain.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html.headers
similarity index 100%
copy from third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-no-subdomain.sub.https.html.headers
copy to third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html.headers
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html
new file mode 100644
index 0000000..86c951d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is isolated, subdomain child 1 is not isolated, different-subdomain child 2 is not isolated</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testSameAgentCluster,
+  testDifferentAgentClusters,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+  // Order of loading should not matter, but we make it sequential to ensure the
+  // tests are deterministic.
+  await insertIframe("{{hosts[][www]}}");
+  await insertIframe("{{hosts[][www1]}}");
+});
+
+
+// Since everybody is different-origin, everyone's requests/non-requests get
+// respected.
+//
+// So, the parent ends up in its origin-keyed agent cluster, and child 1 and
+// child 2 both end up in the site-keyed agent cluster.
+testDifferentAgentClusters([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testSameAgentCluster([0, 1], "child1 to child2");
+testSameAgentCluster([1, 0], "child2 to child1");
+
+testOriginIsolationRestricted(self, true, "parent");
+testOriginIsolationRestricted(0, false, "child1");
+testOriginIsolationRestricted(1, false, "child2");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/getter-removed-iframe.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html.headers
similarity index 100%
copy from third_party/blink/web_tests/external/wpt/origin-isolation/getter-removed-iframe.sub.https.html.headers
copy to third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html.headers
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html
new file mode 100644
index 0000000..997fa085
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is isolated, subdomain child 1 is not isolated, same-subdomain child 2 is isolated</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testSameAgentCluster,
+  testDifferentAgentClusters,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+  // Must be sequential, not parallel: the non-isolaed frame must load first.
+  await insertIframe("{{hosts[][www]}}");
+  await insertIframe("{{hosts[][www]}}", "?1");
+});
+
+
+// Since they're different-origin, the parent's isolation request is respected,
+// as is child 1's non-request. child 2 requests isolation but is
+// ignored, since child 1 is in the same browsing context group.
+//
+// So, the parent ends up in the origin-keyed agent cluster, and both children
+// ends up in the site-keyed one.
+testDifferentAgentClusters([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testSameAgentCluster([0, 1], "child1 to child2");
+testSameAgentCluster([1, 0], "child2 to child1");
+
+testOriginIsolationRestricted(self, true, "parent");
+testOriginIsolationRestricted(0, false, "child1");
+testOriginIsolationRestricted(1, false, "child2");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-yes-subdomain.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html.headers
similarity index 100%
copy from third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-yes-subdomain.sub.https.html.headers
copy to third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html.headers
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html
new file mode 100644
index 0000000..7631bdd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is isolated, subdomain child 1 is not isolated, different-subdomain child 2 is isolated</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testDifferentAgentClusters,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+  // Order of loading should not matter, but we make it sequential to ensure the
+  // tests are deterministic.
+  await insertIframe("{{hosts[][www]}}");
+  await insertIframe("{{hosts[][www1]}}", "?1");
+});
+
+
+// Since everybody is different-origin, everyone's requests/non-requests get
+// respected.
+//
+// So, the parent ends up in its origin-keyed agent cluster, child 1 ends up in
+// the site-keyed agent cluster, and child 2 ends up in a different origin-keyed
+// agent cluster.
+testDifferentAgentClusters([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testDifferentAgentClusters([0, 1], "child1 to child2");
+testDifferentAgentClusters([1, 0], "child2 to child1");
+
+testOriginIsolationRestricted(self, true, "parent");
+testOriginIsolationRestricted(0, false, "child1");
+testOriginIsolationRestricted(1, true, "child2");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/getter-removed-iframe.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html.headers
similarity index 100%
copy from third_party/blink/web_tests/external/wpt/origin-isolation/getter-removed-iframe.sub.https.html.headers
copy to third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html.headers
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html
new file mode 100644
index 0000000..e578e35
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is isolated, subdomain child 1 is not isolated, different-port-same-subdomain child 2 is isolated</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testDifferentAgentClusters,
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+  // Order of loading should not matter, but we make it sequential to ensure the
+  // tests are deterministic.
+  await insertIframe("{{hosts[][www]}}");
+  await insertIframe("{{hosts[][www]}}:{{ports[https][1]}}", "?1");
+});
+
+
+// Since everybody is different-origin, everyone's requests/non-requests get
+// respected.
+//
+// So, the parent ends up in its origin-keyed agent cluster, child 1 ends up in
+// the site-keyed agent cluster, and child 2 ends up in a different origin-keyed
+// agent cluster.
+testDifferentAgentClusters([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testDifferentAgentClusters([0, 1], "child1 to child2");
+testDifferentAgentClusters([1, 0], "child2 to child1");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/getter-removed-iframe.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html.headers
similarity index 100%
copy from third_party/blink/web_tests/external/wpt/origin-isolation/getter-removed-iframe.sub.https.html.headers
copy to third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html.headers
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html
new file mode 100644
index 0000000..edcdcba
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is isolated, subdomain child 1 is isolated, non-subdomain but different-port child 2 is not isolated</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testDifferentAgentClusters,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+  // Order of loading should not matter, but we make it sequential to ensure the
+  // tests are deterministic.
+  await insertIframe("{{hosts[][www]}}", "?1");
+  await insertIframe("{{hosts[][]}}:{{ports[https][1]}}");
+});
+
+// Everyone is different-origin, so everyone's request/non-request is
+// respected.
+//
+// So, child2 ends up in the site-keyed agent cluster, and the parent and child1
+// end up in two separate origin-keyed agent clusters.
+testDifferentAgentClusters([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testDifferentAgentClusters([0, 1], "child1 to child2");
+testDifferentAgentClusters([1, 0], "child2 to child1");
+
+testOriginIsolationRestricted(self, true, "parent");
+testOriginIsolationRestricted(0, true, "child1");
+testOriginIsolationRestricted(1, false, "child2");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/getter-removed-iframe.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html.headers
similarity index 100%
copy from third_party/blink/web_tests/external/wpt/origin-isolation/getter-removed-iframe.sub.https.html.headers
copy to third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html.headers
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html
new file mode 100644
index 0000000..f850331
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is isolated, subdomain child 1 is isolated, same-subdomain child 2 is not isolated</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testSameAgentCluster,
+  testDifferentAgentClusters,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+  // Must be sequential, not parallel: the isolated frame must load first.
+  await insertIframe("{{hosts[][www]}}", "?1");
+  await insertIframe("{{hosts[][www]}}");
+});
+
+
+// Since they're different-origin, the parent's isolation request is respected,
+// as is child 1's request. child 2's non-request is ignored, since
+// child 1 is in the same browsing context group.
+//
+// So, the parent ends up in the origin-keyed agent cluster, and both children
+// ends up in a different origin-keyed agent cluster.
+testDifferentAgentClusters([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testSameAgentCluster([0, 1], "child1 to child2");
+testSameAgentCluster([1, 0], "child2 to child1");
+
+testOriginIsolationRestricted(self, true, "parent");
+testOriginIsolationRestricted(0, true, "child1");
+testOriginIsolationRestricted(1, true, "child2");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-no-subdomain.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html.headers
similarity index 100%
copy from third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-no-subdomain.sub.https.html.headers
copy to third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html.headers
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html
new file mode 100644
index 0000000..6ae6fac
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is isolated, subdomain child 1 is isolated, same-subdomain child 2 is not isolated</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testSameAgentCluster,
+  testDifferentAgentClusters,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+  // Order of loading should not matter, but we make it sequential to ensure the
+  // tests are deterministic.
+  await insertIframe("{{hosts[][www]}}", "?1");
+  await insertIframe("{{hosts[][www]}}", "?1");
+});
+
+
+// Since they're different-origin, the parent's isolation request is respected,
+// as is child 1's request. child 2's request is redundant, since
+// child 1 is in the same browsing context group.
+//
+// So, the parent ends up in the origin-keyed agent cluster, and both children
+// ends up in a different origin-keyed agent cluster.
+testDifferentAgentClusters([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testSameAgentCluster([0, 1], "child1 to child2");
+testSameAgentCluster([1, 0], "child2 to child1");
+
+testOriginIsolationRestricted(self, true, "parent");
+testOriginIsolationRestricted(0, true, "child1");
+testOriginIsolationRestricted(1, true, "child2");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-yes-subdomain.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html.headers
similarity index 100%
copy from third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-yes-subdomain.sub.https.html.headers
copy to third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html.headers
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html
new file mode 100644
index 0000000..53eaea1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is isolated, subdomain child 1 is isolated, different-subdomain child 2 is isolated</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testDifferentAgentClusters,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+  // Order of loading should not matter, but we make it sequential to ensure the
+  // tests are deterministic.
+  await insertIframe("{{hosts[][www]}}", "?1");
+  await insertIframe("{{hosts[][www1]}}", "?1");
+});
+
+
+// Since everybody is different-origin, everyone's requests get
+// respected.
+//
+// So, the parent ends up in its origin-keyed agent cluster, child 1 ends up in
+// a second origin-keyed agent cluster, and child 2 ends up in a third
+// origin-keyed agent cluster.
+testDifferentAgentClusters([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testDifferentAgentClusters([0, 1], "child1 to child2");
+testDifferentAgentClusters([1, 0], "child2 to child1");
+
+testOriginIsolationRestricted(self, true, "parent");
+testOriginIsolationRestricted(0, true, "child1");
+testOriginIsolationRestricted(1, true, "child2");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/getter-removed-iframe.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html.headers
similarity index 100%
copy from third_party/blink/web_tests/external/wpt/origin-isolation/getter-removed-iframe.sub.https.html.headers
copy to third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html.headers
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html
new file mode 100644
index 0000000..370f3dc
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is isolated, subdomain child 1 is isolated, different-port-same-subdomain child 2 is isolated</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  testDifferentAgentClusters,
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+  // Order of loading should not matter, but we make it sequential to ensure the
+  // tests are deterministic.
+  await insertIframe("{{hosts[][www]}}", "?1");
+  await insertIframe("{{hosts[][www]}}:{{ports[https][1]}}", "?1");
+});
+
+
+// Since everybody is different-origin, everyone's requests get
+// respected.
+//
+// So, the parent ends up in its origin-keyed agent cluster, child 1 ends up in
+// a second origin-keyed agent cluster, and child 2 ends up in a third
+// origin-keyed agent cluster.
+testDifferentAgentClusters([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testDifferentAgentClusters([0, 1], "child1 to child2");
+testDifferentAgentClusters([1, 0], "child2 to child1");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/getter-removed-iframe.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html.headers
similarity index 100%
copy from third_party/blink/web_tests/external/wpt/origin-isolation/getter-removed-iframe.sub.https.html.headers
copy to third_party/blink/web_tests/external/wpt/origin-isolation/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html.headers
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/README.md b/third_party/blink/web_tests/external/wpt/origin-isolation/README.md
index b028fcd9..8ff8de6 100644
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/README.md
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/README.md
@@ -1,3 +1,26 @@
 # Origin isolation tests
 
-These tests are for the proposal at [WICG/origin-isolation](https://github.com/WICG/origin-isolation). They should eventually move into a subdirectory of `html/` if/when that proposal merges into the HTML Standard.
+These tests are for the proposal at
+[WICG/origin-isolation](https://github.com/WICG/origin-isolation). They should
+eventually move into a subdirectory of `html/` if/when that proposal merges into
+the HTML Standard.
+
+## Test filenames
+
+The tests in `2-iframes` follow the file naming pattern
+
+```
+parent-[yes|no]-child1-[yes|no]-[designator]-child2-[yes|no]-[designator]
+```
+
+Here:
+
+* `yes` or `no` refers to whether the `Origin-Isolation` header is set or unset.
+* `designator` explains how the child differs from the parent: e.g. by being a
+  subdomain, or having a different port, or both. There's also `same` if it's
+  same-origin.
+
+Other directories have variations on this, e.g. `1-iframe/` does the same thing
+but for a single `child` instead of `child1` and `child2`, and `navigation/`
+uses `1` and `2` to represent the two different locations the single iframe will
+be navigated to.
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/getter-data-url.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/getter-data-url.https.html
deleted file mode 100644
index da2c87b..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/getter-data-url.https.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>window.originIsolationRestricted for a data: URL</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  waitForIframe,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-promise_setup(() => {
-  const iframe = document.createElement("iframe");
-
-  // This copies parts of resources/send-origin-isolation-header.py that allow
-  // us to reuse testOriginIsolationRestricted.
-  iframe.src = `data:text/html,<script>
-    window.onmessage = () => {
-      parent.postMessage(self.originIsolationRestricted, "*");
-    };
-    </` + `script>
-  `;
-
-  const waitPromise = waitForIframe(iframe);
-  document.body.append(iframe);
-  return waitPromise;
-});
-
-// The data: URL iframe has an opaque origin, so it definitely should return
-// false. It's pretty unlikely that it would return true anyway, since we can't
-// set the header on the iframe, but we should test it to make sure there isn't
-// some strange main page -> data: URL iframe inheritance going on.
-
-testOriginIsolationRestricted(0, false);
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/getter-removed-iframe.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/getter-removed-iframe.sub.https.html
deleted file mode 100644
index a137346..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/getter-removed-iframe.sub.https.html
+++ /dev/null
@@ -1,27 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>window.crossOriginIsolated for a removed frame</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import { navigateIframe } from "./resources/helpers.mjs";
-
-promise_test(async () => {
-  // We cannot use insertIframe because it sets both `document.domain`s. That
-  // shouldn't matter, but Chrome has a bug (https://crbug.com/1095145), so
-  // let's avoid making the test needlessly fail because of that bug.
-  const iframe = document.createElement("iframe");
-  const navigatePromise = navigateIframe(iframe, "{{hosts[][]}}", "?1");
-  document.body.append(iframe);
-  await navigatePromise;
-
-  const frameWindow = iframe.contentWindow;
-
-  assert_equals(frameWindow.originIsolationRestricted, true, "before");
-  iframe.remove();
-  assert_equals(frameWindow.originIsolationRestricted, true, "after");
-}, "Removing the iframe does not change originIsolationRestricted");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/getter-sandboxed-iframe.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/getter-sandboxed-iframe.sub.https.html
deleted file mode 100644
index 366fae6..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/getter-sandboxed-iframe.sub.https.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>window.originIsolationRestricted for a sandboxed frame</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  navigateIframe,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-// We do this manually instead of using insertIframe because we want to add a
-// sandbox="" attribute and we don't want to set both document.domains.
-promise_setup(() => {
-  const iframe = document.createElement("iframe");
-  iframe.sandbox = "allow-scripts";
-  const navigatePromise = navigateIframe(iframe, "{{hosts[][]}}", "?1");
-  document.body.append(iframe);
-  return navigatePromise;
-});
-
-// Because sandboxed iframes have an opaque origin, their agent cluster key is
-// always an origin, so there are no additional restrictions imposed by origin
-// isolation. Thus the getter returns false.
-
-testOriginIsolationRestricted(0, false);
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/getter-special-cases/data-url.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/getter-special-cases/data-url.https.html
new file mode 100644
index 0000000..559d3256
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/getter-special-cases/data-url.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>window.originIsolationRestricted for a data: URL</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  waitForIframe,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+promise_setup(() => {
+  const iframe = document.createElement("iframe");
+
+  // This copies parts of resources/send-origin-isolation-header.py that allow
+  // us to reuse testOriginIsolationRestricted.
+  iframe.src = `data:text/html,<script>
+    window.onmessage = () => {
+      parent.postMessage(self.originIsolationRestricted, "*");
+    };
+    </` + `script>
+  `;
+
+  const waitPromise = waitForIframe(iframe);
+  document.body.append(iframe);
+  return waitPromise;
+});
+
+// The data: URL iframe has an opaque origin, so it definitely should return
+// false. It's pretty unlikely that it would return true anyway, since we can't
+// set the header on the iframe, but we should test it to make sure there isn't
+// some strange main page -> data: URL iframe inheritance going on.
+
+testOriginIsolationRestricted(0, false);
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/getter-data-url.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/getter-special-cases/data-url.https.html.headers
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/origin-isolation/getter-data-url.https.html.headers
rename to third_party/blink/web_tests/external/wpt/origin-isolation/getter-special-cases/data-url.https.html.headers
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/getter-special-cases/removed-iframe.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/getter-special-cases/removed-iframe.sub.https.html
new file mode 100644
index 0000000..36651fb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/getter-special-cases/removed-iframe.sub.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>window.originIsolationRestricted for a removed frame</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import { navigateIframe } from "../resources/helpers.mjs";
+
+promise_test(async () => {
+  // We cannot use insertIframe because it sets both `document.domain`s. That
+  // shouldn't matter, but Chrome has a bug (https://crbug.com/1095145), so
+  // let's avoid making the test needlessly fail because of that bug.
+  const iframe = document.createElement("iframe");
+  const navigatePromise = navigateIframe(iframe, "{{hosts[][]}}", "?1");
+  document.body.append(iframe);
+  await navigatePromise;
+
+  const frameWindow = iframe.contentWindow;
+
+  assert_equals(frameWindow.originIsolationRestricted, true, "before");
+  iframe.remove();
+  assert_equals(frameWindow.originIsolationRestricted, true, "after");
+}, "Removing the iframe does not change originIsolationRestricted");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/getter-removed-iframe.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/getter-special-cases/removed-iframe.sub.https.html.headers
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/origin-isolation/getter-removed-iframe.sub.https.html.headers
rename to third_party/blink/web_tests/external/wpt/origin-isolation/getter-special-cases/removed-iframe.sub.https.html.headers
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/getter-special-cases/sandboxed-iframe.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/getter-special-cases/sandboxed-iframe.sub.https.html
new file mode 100644
index 0000000..655f1e1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/getter-special-cases/sandboxed-iframe.sub.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>window.originIsolationRestricted for a sandboxed frame</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  navigateIframe,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+// We do this manually instead of using insertIframe because we want to add a
+// sandbox="" attribute and we don't want to set both document.domains.
+promise_setup(() => {
+  const iframe = document.createElement("iframe");
+  iframe.sandbox = "allow-scripts";
+  const navigatePromise = navigateIframe(iframe, "{{hosts[][]}}", "?1");
+  document.body.append(iframe);
+  return navigatePromise;
+});
+
+// Because sandboxed iframes have an opaque origin, their agent cluster key is
+// always an origin, so there are no additional restrictions imposed by origin
+// isolation. Thus the getter returns false.
+
+testOriginIsolationRestricted(0, false);
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/getter-sandboxed-iframe.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/getter-special-cases/sandboxed-iframe.sub.https.html.headers
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/origin-isolation/getter-sandboxed-iframe.sub.https.html.headers
rename to third_party/blink/web_tests/external/wpt/origin-isolation/getter-special-cases/sandboxed-iframe.sub.https.html.headers
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-no-1-no-same-2-yes-port.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-no-1-no-same-2-yes-port.sub.https.html
new file mode 100644
index 0000000..ff9ea4d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-no-1-no-same-2-yes-port.sub.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is not isolated, navigate a frame from same-origin non-isolated to different-origin (different-port) isolated</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  navigateIframe,
+  setBothDocumentDomains,
+  testSameAgentCluster,
+  testDifferentAgentClusters,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+let frame1;
+promise_setup(async () => {
+  frame1 = await insertIframe("{{hosts[][]}}");
+});
+
+// Nobody requested isolation yet.
+
+testSameAgentCluster([self, 0], "Before: parent to child");
+testOriginIsolationRestricted(self, false, "before parent");
+testOriginIsolationRestricted(0, false, "before child");
+
+promise_test(async () => {
+  await navigateIframe(frame1, "{{hosts[][]}}:{{ports[https][1]}}", "?1");
+  await setBothDocumentDomains(frames[0]);
+}, "Navigation");
+
+// Since the new page is different-origin, it should be isolated.
+
+testDifferentAgentClusters([self, 0], "After: parent to child");
+testOriginIsolationRestricted(self, false, "after parent");
+testOriginIsolationRestricted(0, true, "after child");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-no-1-no-same-2-yes-subdomain.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-no-1-no-same-2-yes-subdomain.sub.https.html
new file mode 100644
index 0000000..f599cb9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-no-1-no-same-2-yes-subdomain.sub.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is not isolated, navigate a frame from same-origin non-isolated to different-origin (subdomain) isolated</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  navigateIframe,
+  setBothDocumentDomains,
+  testSameAgentCluster,
+  testDifferentAgentClusters,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+let frame1;
+promise_setup(async () => {
+  frame1 = await insertIframe("{{hosts[][]}}");
+});
+
+// Nobody requested isolation yet.
+
+testSameAgentCluster([self, 0], "Before: parent to child");
+testOriginIsolationRestricted(self, false, "before parent");
+testOriginIsolationRestricted(0, false, "before child");
+
+promise_test(async () => {
+  await navigateIframe(frame1, "{{hosts[][www]}}", "?1");
+  await setBothDocumentDomains(frames[0]);
+}, "Navigation");
+
+// Since the new page is different-origin, it should be isolated.
+
+testDifferentAgentClusters([self, 0], "After: parent to child");
+testOriginIsolationRestricted(self, false, "after parent");
+testOriginIsolationRestricted(0, true, "after child");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain.sub.https.html
new file mode 100644
index 0000000..8777b65
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain.sub.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is not isolated, navigate a frame from a subdomain non-isolated to the same subdomain isolated</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  navigateIframe,
+  setBothDocumentDomains,
+  testSameAgentCluster,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+let frame1;
+promise_setup(async () => {
+  frame1 = await insertIframe("{{hosts[][www]}}");
+});
+
+// Nobody requested isolation yet.
+
+testSameAgentCluster([self, 0], "Before: parent to child");
+testOriginIsolationRestricted(self, false, "before parent");
+testOriginIsolationRestricted(0, false, "before child");
+
+promise_test(async () => {
+  await navigateIframe(frame1, "{{hosts[][www]}}", "?1");
+  await setBothDocumentDomains(frames[0]);
+}, "Navigation");
+
+// Because this subdomain was previously non-isolated, the second load's
+// isolation request is ignored; instead we continue isolating.
+
+testSameAgentCluster([self, 0], "After: parent to child");
+testOriginIsolationRestricted(self, false, "after parent");
+testOriginIsolationRestricted(0, false, "after child");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain2.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain2.sub.https.html
new file mode 100644
index 0000000..22f35eb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain2.sub.https.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is not isolated, navigate a frame from a subdomain non-isolated to a second-subdomain isolated</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  navigateIframe,
+  setBothDocumentDomains,
+  testSameAgentCluster,
+  testDifferentAgentClusters,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+let frame1;
+promise_setup(async () => {
+  frame1 = await insertIframe("{{hosts[][www]}}");
+});
+
+// Nobody requested isolation yet.
+
+testSameAgentCluster([self, 0], "Before: parent to child");
+testOriginIsolationRestricted(self, false, "before parent");
+testOriginIsolationRestricted(0, false, "before child");
+
+promise_test(async () => {
+  await navigateIframe(frame1, "{{hosts[][www1]}}", "?1");
+  await setBothDocumentDomains(frames[0]);
+}, "Navigation");
+
+// Because we're going to a different subdomain (and thus different origin), the
+// isolation request is respected.
+
+testDifferentAgentClusters([self, 0], "After: parent to child");
+testOriginIsolationRestricted(self, false, "after parent");
+testOriginIsolationRestricted(0, true, "after child");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-no-1-subdomain-yes-2-subdomain2-no.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-no-1-subdomain-yes-2-subdomain2-no.sub.https.html
new file mode 100644
index 0000000..ac51e6e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-no-1-subdomain-yes-2-subdomain2-no.sub.https.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is not isolated, navigate a frame from a subdomain isolated to a second-subdomain non-isolated</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  navigateIframe,
+  setBothDocumentDomains,
+  testSameAgentCluster,
+  testDifferentAgentClusters,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+let frame1;
+promise_setup(async () => {
+  frame1 = await insertIframe("{{hosts[][www]}}", "?1");
+});
+
+// Since they are different-origin, the child's isolation request is respected.
+
+testDifferentAgentClusters([self, 0], "Before: parent to child");
+testOriginIsolationRestricted(self, false, "before parent");
+testOriginIsolationRestricted(0, true, "before child");
+
+promise_test(async () => {
+  await navigateIframe(frame1, "{{hosts[][www1]}}");
+  await setBothDocumentDomains(frames[0]);
+}, "Navigation");
+
+// Make sure that the different-subdomain page (which doesn't request isolation)
+// doesn't somehow get isolated just because its predecessor was.
+
+testSameAgentCluster([self, 0], "After: parent to child");
+testOriginIsolationRestricted(self, false, "after parent");
+testOriginIsolationRestricted(0, false, "after child");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-no-1-yes-subdomain-2-no-subdomain.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-no-1-yes-subdomain-2-no-subdomain.sub.https.html
new file mode 100644
index 0000000..42598342
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-no-1-yes-subdomain-2-no-subdomain.sub.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is not isolated, navigate a frame from a subdomain isolated to the same subdomain non-isolated</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  navigateIframe,
+  setBothDocumentDomains,
+  testDifferentAgentClusters,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+let frame1;
+promise_setup(async () => {
+  frame1 = await insertIframe("{{hosts[][www]}}", "?1");
+});
+
+// Since they are different-origin, the child's isolation request is respected.
+
+testDifferentAgentClusters([self, 0], "Before: parent to child");
+testOriginIsolationRestricted(self, false, "before parent");
+testOriginIsolationRestricted(0, true, "before child");
+
+promise_test(async () => {
+  await navigateIframe(frame1, "{{hosts[][www]}}");
+  await setBothDocumentDomains(frames[0]);
+}, "Navigation");
+
+// Because this subdomain was previously isolated, the second load's
+// non-isolation request is ignored; instead we continue isolating.
+
+testDifferentAgentClusters([self, 0], "After: parent to child");
+testOriginIsolationRestricted(self, false, "after parent");
+testOriginIsolationRestricted(0, true, "after child");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-yes-1-no-same-2-no-port.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-yes-1-no-same-2-no-port.sub.https.html
new file mode 100644
index 0000000..2d9c9ac9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-yes-1-no-same-2-no-port.sub.https.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is isolated, navigate a frame from same-origin non-isolated to different-origin (different-port) isolated</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  navigateIframe,
+  setBothDocumentDomains,
+  testSameAgentCluster,
+  testDifferentAgentClusters,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+let frame1;
+promise_setup(async () => {
+  frame1 = await insertIframe("{{hosts[][]}}");
+});
+
+// Since the parent is isolated, the same-origin child's non-request is ignored,
+// so it gets isolated too.
+
+testSameAgentCluster([self, 0], "Before: parent to child");
+testOriginIsolationRestricted(self, true, "before parent");
+testOriginIsolationRestricted(0, true, "before child");
+
+promise_test(async () => {
+  await navigateIframe(frame1, "{{hosts[][]}}:{{ports[https][1]}}");
+  await setBothDocumentDomains(frames[0]);
+}, "Navigation");
+
+// Since the new page is different-origin, its non-request should be respected.
+
+testDifferentAgentClusters([self, 0], "After: parent to child");
+testOriginIsolationRestricted(self, true, "after parent");
+testOriginIsolationRestricted(0, false, "after child");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/getter-removed-iframe.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-yes-1-no-same-2-no-port.sub.https.html.headers
similarity index 100%
copy from third_party/blink/web_tests/external/wpt/origin-isolation/getter-removed-iframe.sub.https.html.headers
copy to third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-yes-1-no-same-2-no-port.sub.https.html.headers
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-yes-1-no-same-2-no-subdomain.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-yes-1-no-same-2-no-subdomain.sub.https.html
new file mode 100644
index 0000000..abf0da2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-yes-1-no-same-2-no-subdomain.sub.https.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is isolated, navigate a frame from same-origin non-isolated to different-origin (subdomain) isolated</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+  insertIframe,
+  navigateIframe,
+  setBothDocumentDomains,
+  testSameAgentCluster,
+  testDifferentAgentClusters,
+  testOriginIsolationRestricted
+} from "../resources/helpers.mjs";
+
+let frame1;
+promise_setup(async () => {
+  frame1 = await insertIframe("{{hosts[][]}}");
+});
+
+// Since the parent is isolated, the same-origin child's non-request is ignored,
+// so it gets isolated too.
+
+testSameAgentCluster([self, 0], "Before: parent to child");
+testOriginIsolationRestricted(self, true, "before parent");
+testOriginIsolationRestricted(0, true, "before child");
+
+promise_test(async () => {
+  await navigateIframe(frame1, "{{hosts[][www]}}");
+  await setBothDocumentDomains(frames[0]);
+}, "Navigation");
+
+// Since the new page is different-origin, its non-request should be respected.
+
+testDifferentAgentClusters([self, 0], "After: parent to child");
+testOriginIsolationRestricted(self, true, "after parent");
+testOriginIsolationRestricted(0, false, "after child");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-no-subdomain.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-yes-1-no-same-2-no-subdomain.sub.https.html.headers
similarity index 100%
copy from third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-no-subdomain.sub.https.html.headers
copy to third_party/blink/web_tests/external/wpt/origin-isolation/iframe-navigation/parent-yes-1-no-same-2-no-subdomain.sub.https.html.headers
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-no-1-same-no-2-ports-yes.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-no-1-same-no-2-ports-yes.sub.https.html
deleted file mode 100644
index dc449e2..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-no-1-same-no-2-ports-yes.sub.https.html
+++ /dev/null
@@ -1,40 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is not isolated, navigate a frame from same-origin non-isolated to different-origin (different-port) isolated</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  navigateIframe,
-  setBothDocumentDomains,
-  testSameAgentCluster,
-  testDifferentAgentClusters,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-let frame1;
-promise_setup(async () => {
-  frame1 = await insertIframe("{{hosts[][]}}");
-});
-
-// Nobody requested isolation yet.
-
-testSameAgentCluster([self, 0], "Before: parent to child");
-testOriginIsolationRestricted(self, false, "before parent");
-testOriginIsolationRestricted(0, false, "before child");
-
-promise_test(async () => {
-  await navigateIframe(frame1, "{{hosts[][]}}:{{ports[https][1]}}", "?1");
-  await setBothDocumentDomains(frames[0]);
-}, "Navigation");
-
-// Since the new page is different-origin, it should be isolated.
-
-testDifferentAgentClusters([self, 0], "After: parent to child");
-testOriginIsolationRestricted(self, false, "after parent");
-testOriginIsolationRestricted(0, true, "after child");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-no-1-same-no-2-subdomain-yes.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-no-1-same-no-2-subdomain-yes.sub.https.html
deleted file mode 100644
index 6815aa9..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-no-1-same-no-2-subdomain-yes.sub.https.html
+++ /dev/null
@@ -1,40 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is not isolated, navigate a frame from same-origin non-isolated to different-origin (subdomain) isolated</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  navigateIframe,
-  setBothDocumentDomains,
-  testSameAgentCluster,
-  testDifferentAgentClusters,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-let frame1;
-promise_setup(async () => {
-  frame1 = await insertIframe("{{hosts[][]}}");
-});
-
-// Nobody requested isolation yet.
-
-testSameAgentCluster([self, 0], "Before: parent to child");
-testOriginIsolationRestricted(self, false, "before parent");
-testOriginIsolationRestricted(0, false, "before child");
-
-promise_test(async () => {
-  await navigateIframe(frame1, "{{hosts[][www]}}", "?1");
-  await setBothDocumentDomains(frames[0]);
-}, "Navigation");
-
-// Since the new page is different-origin, it should be isolated.
-
-testDifferentAgentClusters([self, 0], "After: parent to child");
-testOriginIsolationRestricted(self, false, "after parent");
-testOriginIsolationRestricted(0, true, "after child");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-no-1-subdomain-no-2-subdomain-yes.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-no-1-subdomain-no-2-subdomain-yes.sub.https.html
deleted file mode 100644
index 2887ea8..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-no-1-subdomain-no-2-subdomain-yes.sub.https.html
+++ /dev/null
@@ -1,40 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is not isolated, navigate a frame from a subdomain non-isolated to the same subdomain isolated</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  navigateIframe,
-  setBothDocumentDomains,
-  testSameAgentCluster,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-let frame1;
-promise_setup(async () => {
-  frame1 = await insertIframe("{{hosts[][www]}}");
-});
-
-// Nobody requested isolation yet.
-
-testSameAgentCluster([self, 0], "Before: parent to child");
-testOriginIsolationRestricted(self, false, "before parent");
-testOriginIsolationRestricted(0, false, "before child");
-
-promise_test(async () => {
-  await navigateIframe(frame1, "{{hosts[][www]}}", "?1");
-  await setBothDocumentDomains(frames[0]);
-}, "Navigation");
-
-// Because this subdomain was previously non-isolated, the second load's
-// isolation request is ignored; instead we continue isolating.
-
-testSameAgentCluster([self, 0], "After: parent to child");
-testOriginIsolationRestricted(self, false, "after parent");
-testOriginIsolationRestricted(0, false, "after child");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-no-1-subdomain-no-2-subdomain2-yes.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-no-1-subdomain-no-2-subdomain2-yes.sub.https.html
deleted file mode 100644
index cd01f96..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-no-1-subdomain-no-2-subdomain2-yes.sub.https.html
+++ /dev/null
@@ -1,41 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is not isolated, navigate a frame from a subdomain non-isolated to a second-subdomain isolated</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  navigateIframe,
-  setBothDocumentDomains,
-  testSameAgentCluster,
-  testDifferentAgentClusters,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-let frame1;
-promise_setup(async () => {
-  frame1 = await insertIframe("{{hosts[][www]}}");
-});
-
-// Nobody requested isolation yet.
-
-testSameAgentCluster([self, 0], "Before: parent to child");
-testOriginIsolationRestricted(self, false, "before parent");
-testOriginIsolationRestricted(0, false, "before child");
-
-promise_test(async () => {
-  await navigateIframe(frame1, "{{hosts[][www1]}}", "?1");
-  await setBothDocumentDomains(frames[0]);
-}, "Navigation");
-
-// Because we're going to a different subdomain (and thus different origin), the
-// isolation request is respected.
-
-testDifferentAgentClusters([self, 0], "After: parent to child");
-testOriginIsolationRestricted(self, false, "after parent");
-testOriginIsolationRestricted(0, true, "after child");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-no-1-subdomain-yes-2-subdomain-no.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-no-1-subdomain-yes-2-subdomain-no.sub.https.html
deleted file mode 100644
index 245a8331..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-no-1-subdomain-yes-2-subdomain-no.sub.https.html
+++ /dev/null
@@ -1,40 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is not isolated, navigate a frame from a subdomain isolated to the same subdomain non-isolated</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  navigateIframe,
-  setBothDocumentDomains,
-  testDifferentAgentClusters,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-let frame1;
-promise_setup(async () => {
-  frame1 = await insertIframe("{{hosts[][www]}}", "?1");
-});
-
-// Since they are different-origin, the child's isolation request is respected.
-
-testDifferentAgentClusters([self, 0], "Before: parent to child");
-testOriginIsolationRestricted(self, false, "before parent");
-testOriginIsolationRestricted(0, true, "before child");
-
-promise_test(async () => {
-  await navigateIframe(frame1, "{{hosts[][www]}}");
-  await setBothDocumentDomains(frames[0]);
-}, "Navigation");
-
-// Because this subdomain was previously isolated, the second load's
-// non-isolation request is ignored; instead we continue isolating.
-
-testDifferentAgentClusters([self, 0], "After: parent to child");
-testOriginIsolationRestricted(self, false, "after parent");
-testOriginIsolationRestricted(0, true, "after child");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-no-1-subdomain-yes-2-subdomain2-no.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-no-1-subdomain-yes-2-subdomain2-no.sub.https.html
deleted file mode 100644
index a4ee8e1..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-no-1-subdomain-yes-2-subdomain2-no.sub.https.html
+++ /dev/null
@@ -1,41 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is not isolated, navigate a frame from a subdomain isolated to a second-subdomain non-isolated</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  navigateIframe,
-  setBothDocumentDomains,
-  testSameAgentCluster,
-  testDifferentAgentClusters,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-let frame1;
-promise_setup(async () => {
-  frame1 = await insertIframe("{{hosts[][www]}}", "?1");
-});
-
-// Since they are different-origin, the child's isolation request is respected.
-
-testDifferentAgentClusters([self, 0], "Before: parent to child");
-testOriginIsolationRestricted(self, false, "before parent");
-testOriginIsolationRestricted(0, true, "before child");
-
-promise_test(async () => {
-  await navigateIframe(frame1, "{{hosts[][www1]}}");
-  await setBothDocumentDomains(frames[0]);
-}, "Navigation");
-
-// Make sure that the different-subdomain page (which doesn't request isolation)
-// doesn't somehow get isolated just because its predecessor was.
-
-testSameAgentCluster([self, 0], "After: parent to child");
-testOriginIsolationRestricted(self, false, "after parent");
-testOriginIsolationRestricted(0, false, "after child");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-yes-1-same-no-2-ports-no.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-yes-1-same-no-2-ports-no.sub.https.html
deleted file mode 100644
index 9b554c3..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-yes-1-same-no-2-ports-no.sub.https.html
+++ /dev/null
@@ -1,41 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is isolated, navigate a frame from same-origin non-isolated to different-origin (different-port) isolated</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  navigateIframe,
-  setBothDocumentDomains,
-  testSameAgentCluster,
-  testDifferentAgentClusters,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-let frame1;
-promise_setup(async () => {
-  frame1 = await insertIframe("{{hosts[][]}}");
-});
-
-// Since the parent is isolated, the same-origin child's non-request is ignored,
-// so it gets isolated too.
-
-testSameAgentCluster([self, 0], "Before: parent to child");
-testOriginIsolationRestricted(self, true, "before parent");
-testOriginIsolationRestricted(0, true, "before child");
-
-promise_test(async () => {
-  await navigateIframe(frame1, "{{hosts[][]}}:{{ports[https][1]}}");
-  await setBothDocumentDomains(frames[0]);
-}, "Navigation");
-
-// Since the new page is different-origin, its non-request should be respected.
-
-testDifferentAgentClusters([self, 0], "After: parent to child");
-testOriginIsolationRestricted(self, true, "after parent");
-testOriginIsolationRestricted(0, false, "after child");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-yes-1-same-no-2-ports-no.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-yes-1-same-no-2-ports-no.sub.https.html.headers
deleted file mode 100644
index ea3f6b33..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-yes-1-same-no-2-ports-no.sub.https.html.headers
+++ /dev/null
@@ -1 +0,0 @@
-Origin-Isolation: ?1
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-yes-1-same-no-2-subdomain-no.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-yes-1-same-no-2-subdomain-no.sub.https.html
deleted file mode 100644
index 07a6b01d..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-yes-1-same-no-2-subdomain-no.sub.https.html
+++ /dev/null
@@ -1,41 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is isolated, navigate a frame from same-origin non-isolated to different-origin (subdomain) isolated</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  navigateIframe,
-  setBothDocumentDomains,
-  testSameAgentCluster,
-  testDifferentAgentClusters,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-let frame1;
-promise_setup(async () => {
-  frame1 = await insertIframe("{{hosts[][]}}");
-});
-
-// Since the parent is isolated, the same-origin child's non-request is ignored,
-// so it gets isolated too.
-
-testSameAgentCluster([self, 0], "Before: parent to child");
-testOriginIsolationRestricted(self, true, "before parent");
-testOriginIsolationRestricted(0, true, "before child");
-
-promise_test(async () => {
-  await navigateIframe(frame1, "{{hosts[][www]}}");
-  await setBothDocumentDomains(frames[0]);
-}, "Navigation");
-
-// Since the new page is different-origin, its non-request should be respected.
-
-testDifferentAgentClusters([self, 0], "After: parent to child");
-testOriginIsolationRestricted(self, true, "after parent");
-testOriginIsolationRestricted(0, false, "after child");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-yes-1-same-no-2-subdomain-no.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-yes-1-same-no-2-subdomain-no.sub.https.html.headers
deleted file mode 100644
index ea3f6b33..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/navigation-parent-yes-1-same-no-2-subdomain-no.sub.https.html.headers
+++ /dev/null
@@ -1 +0,0 @@
-Origin-Isolation: ?1
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child-bad-subdomain.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child-bad-subdomain.sub.https.html
deleted file mode 100644
index cfa39aa..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child-bad-subdomain.sub.https.html
+++ /dev/null
@@ -1,27 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is not isolated, child attempts to isolate but uses a bad header value, child is a subdomain of the parent</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testSameAgentCluster,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-let frameIndex = 0;
-for (const badValue of ["", "?0", "true", "\"?1\"", "1", "?2", "(?1)"]) {
-  promise_test(async () => {
-    await insertIframe("{{hosts[][www]}}", badValue);
-  }, `"${badValue}": frame insertion`);
-
-  // Since the header values are bad there should be no isolation
-  testSameAgentCluster([self, frameIndex], `"${badValue}"`);
-  testOriginIsolationRestricted(frameIndex, false, `"${badValue}"`);
-  ++frameIndex;
-}
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child-yes-ports.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child-yes-ports.sub.https.html
deleted file mode 100644
index e203d0c..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child-yes-ports.sub.https.html
+++ /dev/null
@@ -1,24 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is not isolated, child is isolated, child is different-origin to the parent because of a port mismatch</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testDifferentAgentClusters
-} from "./resources/helpers.mjs";
-
-promise_setup(async () => {
-  await insertIframe("{{hosts[][]}}:{{ports[https][1]}}", "?1");
-});
-
-// Since they're different-origin, the child's isolation request is respected,
-// so the parent ends up in the site-keyed agent cluster and the child in the
-// origin-keyed one.
-
-testDifferentAgentClusters([self, 0]);
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child-yes-same.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child-yes-same.sub.https.html
deleted file mode 100644
index ece3b973..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child-yes-same.sub.https.html
+++ /dev/null
@@ -1,25 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is not isolated, child is isolated, child is same-origin to the parent</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testSameAgentCluster,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-promise_setup(async () => {
-  await insertIframe("{{hosts[][]}}", "?1");
-});
-
-// Since they're same-origin, and the parent loaded without isolation, the
-// child's request for isolation gets ignored, and both end up site-keyed.
-testSameAgentCluster([self, 0]);
-testOriginIsolationRestricted(self, false, "parent");
-testOriginIsolationRestricted(0, false, "child");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child-yes-subdomain.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child-yes-subdomain.sub.https.html
deleted file mode 100644
index ab060e2..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child-yes-subdomain.sub.https.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is not isolated, child is isolated, child is a subdomain of the parent</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testDifferentAgentClusters,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-promise_setup(async () => {
-  await insertIframe("{{hosts[][www]}}", "?1");
-});
-
-// Since they're different-origin, the child's isolation request is respected,
-// so the parent ends up in the site-keyed agent cluster and the child in the
-// origin-keyed one.
-testDifferentAgentClusters([self, 0]);
-testOriginIsolationRestricted(self, false, "parent");
-testOriginIsolationRestricted(0, true, "child");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child-yes-with-params-subdomain.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child-yes-with-params-subdomain.sub.https.html
deleted file mode 100644
index e1459b9..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child-yes-with-params-subdomain.sub.https.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is not isolated, child is isolated using parameters on its structured header, child is a subdomain of the parent</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testDifferentAgentClusters,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-promise_setup(async () => {
-  await insertIframe("{{hosts[][www]}}", "?1;param1;param2=value2");
-});
-
-// Since they're different-origin, the child's isolation request is respected,
-// so the parent ends up in the site-keyed agent cluster and the child in the
-// origin-keyed one.
-testDifferentAgentClusters([self, 0]);
-testOriginIsolationRestricted(self, false, "parent");
-testOriginIsolationRestricted(0, true, "child");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child1-no-child2-yes-children-different.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child1-no-child2-yes-children-different.sub.https.html
deleted file mode 100644
index 17ee8a6..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child1-no-child2-yes-children-different.sub.https.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is not isolated, subdomain child 1 is not isolated, different-subdomain child 2 is isolated</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testSameAgentCluster,
-  testDifferentAgentClusters,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-promise_setup(async () => {
-  // Order of loading should not matter, but we make it sequential to ensure the
-  // tests are deterministic.
-  await insertIframe("{{hosts[][www]}}");
-  await insertIframe("{{hosts[][www1]}}", "?1");
-});
-
-
-// Since everybody is different-origin, everyone's requests/non-requests get
-// respected.
-//
-// So, the parent and child 1 end up in the site-keyed agent cluster, and child
-// 2 ends up in its own origin-keyed agent cluster.
-testSameAgentCluster([self, 0], "Parent to child1");
-testDifferentAgentClusters([self, 1], "Parent to child2");
-testDifferentAgentClusters([0, 1], "child1 to child2");
-testDifferentAgentClusters([1, 0], "child2 to child1");
-
-testOriginIsolationRestricted(self, false, "parent");
-testOriginIsolationRestricted(0, false, "child1");
-testOriginIsolationRestricted(1, true, "child2");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child1-no-child2-yes-children-ports.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child1-no-child2-yes-children-ports.sub.https.html
deleted file mode 100644
index f5de8877..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child1-no-child2-yes-children-ports.sub.https.html
+++ /dev/null
@@ -1,32 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is not isolated, subdomain child 1 is not isolated, different-port-same-subdomain child 2 is isolated</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testSameAgentCluster,
-  testDifferentAgentClusters,
-} from "./resources/helpers.mjs";
-
-promise_setup(async () => {
-  // Order of loading should not matter, but we make it sequential to ensure the
-  // tests are deterministic.
-  await insertIframe("{{hosts[][www]}}");
-  await insertIframe("{{hosts[][www]}}:{{ports[https][1]}}", "?1");
-});
-
-// Since everybody is different-origin, everyone's requests/non-requests get
-// respected.
-//
-// So, the parent and child 1 end up in the site-keyed agent cluster, and child
-// 2 ends up in its own origin-keyed agent cluster.
-testSameAgentCluster([self, 0], "Parent to child1");
-testDifferentAgentClusters([self, 1], "Parent to child2");
-testDifferentAgentClusters([0, 1], "child1 to child2");
-testDifferentAgentClusters([1, 0], "child2 to child1");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child1-no-child2-yes-children-same.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child1-no-child2-yes-children-same.sub.https.html
deleted file mode 100644
index 17b82bc09..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child1-no-child2-yes-children-same.sub.https.html
+++ /dev/null
@@ -1,36 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is not isolated, subdomain child 1 is not isolated, same-subdomain child 2 is isolated</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testSameAgentCluster,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-promise_setup(async () => {
-  // Must be sequential, not parallel: the non-isolated frame must load first.
-  await insertIframe("{{hosts[][www]}}");
-  await insertIframe("{{hosts[][www]}}", "?1");
-});
-
-
-// Since they're different-origin, the parent's isolation non-request is
-// respected, as is child 1's non-request. child 2 requests isolation
-// but is ignored, since child 1 is in the same browsing context group.
-//
-// So, everyone ends up in the site-keyed agent cluster.
-testSameAgentCluster([self, 0], "Parent to child1");
-testSameAgentCluster([self, 1], "Parent to child2");
-testSameAgentCluster([0, 1], "child1 to child2");
-testSameAgentCluster([1, 0], "child2 to child1");
-
-testOriginIsolationRestricted(self, false, "parent");
-testOriginIsolationRestricted(0, false, "child1");
-testOriginIsolationRestricted(1, false, "child2");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child1-yes-child2-no-children-same.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child1-yes-child2-no-children-same.sub.https.html
deleted file mode 100644
index f6955c5..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child1-yes-child2-no-children-same.sub.https.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is not isolated, subdomain child 1 is isolated, same-subdomain child 2 is not isolated</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testSameAgentCluster,
-  testDifferentAgentClusters,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-promise_setup(async () => {
-  // Must be sequential, not parallel: the isolated frame must load first.
-  await insertIframe("{{hosts[][www]}}", "?1");
-  await insertIframe("{{hosts[][www]}}");
-});
-
-
-// Since they're different-origin, the parent's isolation non-request is
-// respected, as is child 1's request. child 2's non-request is
-// ignored, since child 1 is in the same browsing context group.
-//
-// So, the parent ends up in the site-keyed agent cluster, and both children end
-// up in an origin-keyed agent cluster.
-testDifferentAgentClusters([self, 0], "Parent to child1");
-testDifferentAgentClusters([self, 1], "Parent to child2");
-testSameAgentCluster([0, 1], "child1 to child2");
-testSameAgentCluster([1, 0], "child2 to child1");
-
-testOriginIsolationRestricted(self, false, "parent");
-testOriginIsolationRestricted(0, true, "child1");
-testOriginIsolationRestricted(1, true, "child2");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child1-yes-child2-no-not-subdomain.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child1-yes-child2-no-not-subdomain.sub.https.html
deleted file mode 100644
index 7b7b2a2f..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-no-child1-yes-child2-no-not-subdomain.sub.https.html
+++ /dev/null
@@ -1,37 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is not isolated, subdomain child 1 is isolated, non-subdomain but different-port child 2 is not isolated</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testSameAgentCluster,
-  testDifferentAgentClusters,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-promise_setup(async () => {
-  // Order of loading should not matter, but we make it sequential to ensure the
-  // tests are deterministic.
-  await insertIframe("{{hosts[][www]}}", "?1");
-  await insertIframe("{{hosts[][]}}:{{ports[https][1]}}");
-});
-
-// Everyone is different-origin, so everyone's request/non-request is
-// respected.
-//
-// So, the parent and child2 end up in the site-keyed agent cluster, and child1
-// ends up in an origin-keyed agent cluster.
-testDifferentAgentClusters([self, 0], "Parent to child1");
-testSameAgentCluster([self, 1], "Parent to child2");
-testDifferentAgentClusters([0, 1], "child1 to child2");
-testDifferentAgentClusters([1, 0], "child2 to child1");
-
-testOriginIsolationRestricted(self, false, "parent");
-testOriginIsolationRestricted(0, true, "child1");
-testOriginIsolationRestricted(1, false, "child2");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-no-ports.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-no-ports.sub.https.html
deleted file mode 100644
index a33dcd25..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-no-ports.sub.https.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is isolated, child is not isolated, child is is different-origin to the parent because of a port mismatch</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testDifferentAgentClusters
-} from "./resources/helpers.mjs";
-
-promise_setup(async () => {
-  await insertIframe("{{hosts[][]}}:{{ports[https][1]}}");
-});
-
-// Since they're different-origin, the parent's isolation request is respected,
-// as is the child's non-request. So the parent ends up in the origin-keyed
-// agent cluster and the child ends up in the site-keyed one.
-testDifferentAgentClusters([self, 0]);
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-no-ports.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-no-ports.sub.https.html.headers
deleted file mode 100644
index ea3f6b33..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-no-ports.sub.https.html.headers
+++ /dev/null
@@ -1 +0,0 @@
-Origin-Isolation: ?1
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-no-same.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-no-same.sub.https.html
deleted file mode 100644
index 7edebe9b..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-no-same.sub.https.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is isolated, child is not isolated, child is same-origin to the parent</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testSameAgentCluster,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-promise_setup(async () => {
-  await insertIframe("{{hosts[][]}}");
-});
-
-// Since they're same-origin, and the parent loaded with isolation, the
-// child's non-request for isolation gets ignored, and both end up origin-keyed.
-testSameAgentCluster([self, 0]);
-
-testOriginIsolationRestricted(self, true, "parent");
-testOriginIsolationRestricted(0, true, "child");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-no-subdomain.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-no-subdomain.sub.https.html
deleted file mode 100644
index f0cd3c48..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-no-subdomain.sub.https.html
+++ /dev/null
@@ -1,27 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is isolated, child is not isolated, child is a subdomain of the parent</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testDifferentAgentClusters,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-promise_setup(async () => {
-  await insertIframe("{{hosts[][www]}}");
-});
-
-// Since they're different-origin, the parent's isolation request is respected,
-// as is the child's non-request. So the parent ends up in the origin-keyed
-// agent cluster and the child ends up in the site-keyed one.
-testDifferentAgentClusters([self, 0]);
-
-testOriginIsolationRestricted(self, true, "parent");
-testOriginIsolationRestricted(0, false, "child");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-yes-ports.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-yes-ports.sub.https.html
deleted file mode 100644
index d20bd34..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-yes-ports.sub.https.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is isolated, child is not isolated, child is different-origin to the parent because of a port mismatch</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testDifferentAgentClusters
-} from "./resources/helpers.mjs";
-
-promise_setup(async () => {
-  await insertIframe("{{hosts[][]}}:{{ports[https][1]}}", "?1");
-});
-
-// Both request isolation, so the parent ends up in one origin-keyed agent
-// cluster (the default port's origin), and the child ends up in a different
-// origin-keyed agent cluster (the other port's origin).
-testDifferentAgentClusters([self, 0]);
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-yes-ports.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-yes-ports.sub.https.html.headers
deleted file mode 100644
index ea3f6b33..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-yes-ports.sub.https.html.headers
+++ /dev/null
@@ -1 +0,0 @@
-Origin-Isolation: ?1
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-yes-same.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-yes-same.sub.https.html
deleted file mode 100644
index c491781..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-yes-same.sub.https.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is isolated, child is not isolated, child is same-origin to the parent</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testSameAgentCluster,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-promise_setup(async () => {
-  await insertIframe("{{hosts[][]}}", "?1");
-});
-
-// Both request isolation, and they're same-origin, so they both end up in the
-// same origin-keyed agent cluster.
-testSameAgentCluster([self, 0]);
-
-testOriginIsolationRestricted(self, true, "parent");
-testOriginIsolationRestricted(0, true, "child");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-yes-subdomain.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-yes-subdomain.sub.https.html
deleted file mode 100644
index fc5b1984..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child-yes-subdomain.sub.https.html
+++ /dev/null
@@ -1,27 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is isolated, child is not isolated, child is a subdomain of the parent</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testDifferentAgentClusters,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-promise_setup(async () => {
-  await insertIframe("{{hosts[][www]}}", "?1");
-});
-
-// Both request isolation, so the parent ends up in one origin-keyed agent
-// cluster (the base domain's origin), and the child ends up in a different
-// origin-keyed agent cluster (the www subdomain's origin).
-testDifferentAgentClusters([self, 0]);
-
-testOriginIsolationRestricted(self, true, "parent");
-testOriginIsolationRestricted(0, true, "child");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-no-children-different.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-no-children-different.sub.https.html
deleted file mode 100644
index 1635bd6..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-no-children-different.sub.https.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is isolated, subdomain child 1 is not isolated, different-subdomain child 2 is not isolated</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testSameAgentCluster,
-  testDifferentAgentClusters,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-promise_setup(async () => {
-  // Order of loading should not matter, but we make it sequential to ensure the
-  // tests are deterministic.
-  await insertIframe("{{hosts[][www]}}");
-  await insertIframe("{{hosts[][www1]}}");
-});
-
-
-// Since everybody is different-origin, everyone's requests/non-requests get
-// respected.
-//
-// So, the parent ends up in its origin-keyed agent cluster, and child 1 and
-// child 2 both end up in the site-keyed agent cluster.
-testDifferentAgentClusters([self, 0], "Parent to child1");
-testDifferentAgentClusters([self, 1], "Parent to child2");
-testSameAgentCluster([0, 1], "child1 to child2");
-testSameAgentCluster([1, 0], "child2 to child1");
-
-testOriginIsolationRestricted(self, true, "parent");
-testOriginIsolationRestricted(0, false, "child1");
-testOriginIsolationRestricted(1, false, "child2");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-no-children-different.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-no-children-different.sub.https.html.headers
deleted file mode 100644
index ea3f6b33..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-no-children-different.sub.https.html.headers
+++ /dev/null
@@ -1 +0,0 @@
-Origin-Isolation: ?1
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-no-children-same.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-no-children-same.sub.https.html
deleted file mode 100644
index 0f2f2902..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-no-children-same.sub.https.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is isolated, subdomain child 1 is not isolated, same-subdomain child 2 is not isolated</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testSameAgentCluster,
-  testDifferentAgentClusters,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-promise_setup(async () => {
-  // Order of loading should not matter, but we make it sequential to ensure the
-  // tests are deterministic.
-  await insertIframe("{{hosts[][www]}}");
-  await insertIframe("{{hosts[][www]}}");
-});
-
-
-// Since everybody is different-origin, everyone's requests/non-requests get
-// respected.
-//
-// So, the parent ends up in its origin-keyed agent cluster, and child 1 and
-// child 2 both end up in the site-keyed agent cluster.
-testDifferentAgentClusters([self, 0], "Parent to child1");
-testDifferentAgentClusters([self, 1], "Parent to child2");
-testSameAgentCluster([0, 1], "child1 to child2");
-testSameAgentCluster([1, 0], "child2 to child1");
-
-testOriginIsolationRestricted(self, true, "parent");
-testOriginIsolationRestricted(0, false, "child1");
-testOriginIsolationRestricted(1, false, "child2");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-no-children-same.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-no-children-same.sub.https.html.headers
deleted file mode 100644
index ea3f6b33..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-no-children-same.sub.https.html.headers
+++ /dev/null
@@ -1 +0,0 @@
-Origin-Isolation: ?1
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-yes-children-different.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-yes-children-different.sub.https.html
deleted file mode 100644
index c830e15..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-yes-children-different.sub.https.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is isolated, subdomain child 1 is not isolated, different-subdomain child 2 is isolated</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testDifferentAgentClusters,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-promise_setup(async () => {
-  // Order of loading should not matter, but we make it sequential to ensure the
-  // tests are deterministic.
-  await insertIframe("{{hosts[][www]}}");
-  await insertIframe("{{hosts[][www1]}}", "?1");
-});
-
-
-// Since everybody is different-origin, everyone's requests/non-requests get
-// respected.
-//
-// So, the parent ends up in its origin-keyed agent cluster, child 1 ends up in
-// the site-keyed agent cluster, and child 2 ends up in a different origin-keyed
-// agent cluster.
-testDifferentAgentClusters([self, 0], "Parent to child1");
-testDifferentAgentClusters([self, 1], "Parent to child2");
-testDifferentAgentClusters([0, 1], "child1 to child2");
-testDifferentAgentClusters([1, 0], "child2 to child1");
-
-testOriginIsolationRestricted(self, true, "parent");
-testOriginIsolationRestricted(0, false, "child1");
-testOriginIsolationRestricted(1, true, "child2");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-yes-children-different.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-yes-children-different.sub.https.html.headers
deleted file mode 100644
index ea3f6b33..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-yes-children-different.sub.https.html.headers
+++ /dev/null
@@ -1 +0,0 @@
-Origin-Isolation: ?1
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-yes-children-ports.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-yes-children-ports.sub.https.html
deleted file mode 100644
index 2301624..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-yes-children-ports.sub.https.html
+++ /dev/null
@@ -1,33 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is isolated, subdomain child 1 is not isolated, different-port-same-subdomain child 2 is isolated</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testDifferentAgentClusters,
-} from "./resources/helpers.mjs";
-
-promise_setup(async () => {
-  // Order of loading should not matter, but we make it sequential to ensure the
-  // tests are deterministic.
-  await insertIframe("{{hosts[][www]}}");
-  await insertIframe("{{hosts[][www]}}:{{ports[https][1]}}", "?1");
-});
-
-
-// Since everybody is different-origin, everyone's requests/non-requests get
-// respected.
-//
-// So, the parent ends up in its origin-keyed agent cluster, child 1 ends up in
-// the site-keyed agent cluster, and child 2 ends up in a different origin-keyed
-// agent cluster.
-testDifferentAgentClusters([self, 0], "Parent to child1");
-testDifferentAgentClusters([self, 1], "Parent to child2");
-testDifferentAgentClusters([0, 1], "child1 to child2");
-testDifferentAgentClusters([1, 0], "child2 to child1");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-yes-children-ports.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-yes-children-ports.sub.https.html.headers
deleted file mode 100644
index ea3f6b33..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-yes-children-ports.sub.https.html.headers
+++ /dev/null
@@ -1 +0,0 @@
-Origin-Isolation: ?1
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-yes-children-same.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-yes-children-same.sub.https.html
deleted file mode 100644
index dc157a9..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-yes-children-same.sub.https.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is isolated, subdomain child 1 is not isolated, same-subdomain child 2 is isolated</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testSameAgentCluster,
-  testDifferentAgentClusters,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-promise_setup(async () => {
-  // Must be sequential, not parallel: the non-isolaed frame must load first.
-  await insertIframe("{{hosts[][www]}}");
-  await insertIframe("{{hosts[][www]}}", "?1");
-});
-
-
-// Since they're different-origin, the parent's isolation request is respected,
-// as is child 1's non-request. child 2 requests isolation but is
-// ignored, since child 1 is in the same browsing context group.
-//
-// So, the parent ends up in the origin-keyed agent cluster, and both children
-// ends up in the site-keyed one.
-testDifferentAgentClusters([self, 0], "Parent to child1");
-testDifferentAgentClusters([self, 1], "Parent to child2");
-testSameAgentCluster([0, 1], "child1 to child2");
-testSameAgentCluster([1, 0], "child2 to child1");
-
-testOriginIsolationRestricted(self, true, "parent");
-testOriginIsolationRestricted(0, false, "child1");
-testOriginIsolationRestricted(1, false, "child2");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-yes-children-same.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-yes-children-same.sub.https.html.headers
deleted file mode 100644
index ea3f6b33..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-no-child2-yes-children-same.sub.https.html.headers
+++ /dev/null
@@ -1 +0,0 @@
-Origin-Isolation: ?1
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-no-children-same.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-no-children-same.sub.https.html
deleted file mode 100644
index 033fdd2..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-no-children-same.sub.https.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is isolated, subdomain child 1 is isolated, same-subdomain child 2 is not isolated</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testSameAgentCluster,
-  testDifferentAgentClusters,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-promise_setup(async () => {
-  // Must be sequential, not parallel: the isolated frame must load first.
-  await insertIframe("{{hosts[][www]}}", "?1");
-  await insertIframe("{{hosts[][www]}}");
-});
-
-
-// Since they're different-origin, the parent's isolation request is respected,
-// as is child 1's request. child 2's non-request is ignored, since
-// child 1 is in the same browsing context group.
-//
-// So, the parent ends up in the origin-keyed agent cluster, and both children
-// ends up in a different origin-keyed agent cluster.
-testDifferentAgentClusters([self, 0], "Parent to child1");
-testDifferentAgentClusters([self, 1], "Parent to child2");
-testSameAgentCluster([0, 1], "child1 to child2");
-testSameAgentCluster([1, 0], "child2 to child1");
-
-testOriginIsolationRestricted(self, true, "parent");
-testOriginIsolationRestricted(0, true, "child1");
-testOriginIsolationRestricted(1, true, "child2");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-no-children-same.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-no-children-same.sub.https.html.headers
deleted file mode 100644
index ea3f6b33..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-no-children-same.sub.https.html.headers
+++ /dev/null
@@ -1 +0,0 @@
-Origin-Isolation: ?1
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-no-not-subdomain.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-no-not-subdomain.sub.https.html
deleted file mode 100644
index 784e100f..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-no-not-subdomain.sub.https.html
+++ /dev/null
@@ -1,36 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is isolated, subdomain child 1 is isolated, non-subdomain but different-port child 2 is not isolated</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testDifferentAgentClusters,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-promise_setup(async () => {
-  // Order of loading should not matter, but we make it sequential to ensure the
-  // tests are deterministic.
-  await insertIframe("{{hosts[][www]}}", "?1");
-  await insertIframe("{{hosts[][]}}:{{ports[https][1]}}");
-});
-
-// Everyone is different-origin, so everyone's request/non-request is
-// respected.
-//
-// So, child2 ends up in the site-keyed agent cluster, and the parent and child1
-// end up in two separate origin-keyed agent clusters.
-testDifferentAgentClusters([self, 0], "Parent to child1");
-testDifferentAgentClusters([self, 1], "Parent to child2");
-testDifferentAgentClusters([0, 1], "child1 to child2");
-testDifferentAgentClusters([1, 0], "child2 to child1");
-
-testOriginIsolationRestricted(self, true, "parent");
-testOriginIsolationRestricted(0, true, "child1");
-testOriginIsolationRestricted(1, false, "child2");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-no-not-subdomain.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-no-not-subdomain.sub.https.html.headers
deleted file mode 100644
index ea3f6b33..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-no-not-subdomain.sub.https.html.headers
+++ /dev/null
@@ -1 +0,0 @@
-Origin-Isolation: ?1
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-yes-children-different.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-yes-children-different.sub.https.html
deleted file mode 100644
index 2c1f134..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-yes-children-different.sub.https.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is isolated, subdomain child 1 is isolated, different-subdomain child 2 is isolated</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testDifferentAgentClusters,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-promise_setup(async () => {
-  // Order of loading should not matter, but we make it sequential to ensure the
-  // tests are deterministic.
-  await insertIframe("{{hosts[][www]}}", "?1");
-  await insertIframe("{{hosts[][www1]}}", "?1");
-});
-
-
-// Since everybody is different-origin, everyone's requests get
-// respected.
-//
-// So, the parent ends up in its origin-keyed agent cluster, child 1 ends up in
-// a second origin-keyed agent cluster, and child 2 ends up in a third
-// origin-keyed agent cluster.
-testDifferentAgentClusters([self, 0], "Parent to child1");
-testDifferentAgentClusters([self, 1], "Parent to child2");
-testDifferentAgentClusters([0, 1], "child1 to child2");
-testDifferentAgentClusters([1, 0], "child2 to child1");
-
-testOriginIsolationRestricted(self, true, "parent");
-testOriginIsolationRestricted(0, true, "child1");
-testOriginIsolationRestricted(1, true, "child2");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-yes-children-different.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-yes-children-different.sub.https.html.headers
deleted file mode 100644
index ea3f6b33..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-yes-children-different.sub.https.html.headers
+++ /dev/null
@@ -1 +0,0 @@
-Origin-Isolation: ?1
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-yes-children-ports.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-yes-children-ports.sub.https.html
deleted file mode 100644
index bf873166..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-yes-children-ports.sub.https.html
+++ /dev/null
@@ -1,33 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is isolated, subdomain child 1 is isolated, different-port-same-subdomain child 2 is isolated</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testDifferentAgentClusters,
-} from "./resources/helpers.mjs";
-
-promise_setup(async () => {
-  // Order of loading should not matter, but we make it sequential to ensure the
-  // tests are deterministic.
-  await insertIframe("{{hosts[][www]}}", "?1");
-  await insertIframe("{{hosts[][www]}}:{{ports[https][1]}}", "?1");
-});
-
-
-// Since everybody is different-origin, everyone's requests get
-// respected.
-//
-// So, the parent ends up in its origin-keyed agent cluster, child 1 ends up in
-// a second origin-keyed agent cluster, and child 2 ends up in a third
-// origin-keyed agent cluster.
-testDifferentAgentClusters([self, 0], "Parent to child1");
-testDifferentAgentClusters([self, 1], "Parent to child2");
-testDifferentAgentClusters([0, 1], "child1 to child2");
-testDifferentAgentClusters([1, 0], "child2 to child1");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-yes-children-ports.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-yes-children-ports.sub.https.html.headers
deleted file mode 100644
index ea3f6b33..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-yes-children-ports.sub.https.html.headers
+++ /dev/null
@@ -1 +0,0 @@
-Origin-Isolation: ?1
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-yes-children-same.sub.https.html b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-yes-children-same.sub.https.html
deleted file mode 100644
index 2de08307..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-yes-children-same.sub.https.html
+++ /dev/null
@@ -1,39 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Parent is isolated, subdomain child 1 is isolated, same-subdomain child 2 is not isolated</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script type="module">
-import {
-  insertIframe,
-  testSameAgentCluster,
-  testDifferentAgentClusters,
-  testOriginIsolationRestricted
-} from "./resources/helpers.mjs";
-
-promise_setup(async () => {
-  // Order of loading should not matter, but we make it sequential to ensure the
-  // tests are deterministic.
-  await insertIframe("{{hosts[][www]}}", "?1");
-  await insertIframe("{{hosts[][www]}}", "?1");
-});
-
-
-// Since they're different-origin, the parent's isolation request is respected,
-// as is child 1's request. child 2's request is redundant, since
-// child 1 is in the same browsing context group.
-//
-// So, the parent ends up in the origin-keyed agent cluster, and both children
-// ends up in a different origin-keyed agent cluster.
-testDifferentAgentClusters([self, 0], "Parent to child1");
-testDifferentAgentClusters([self, 1], "Parent to child2");
-testSameAgentCluster([0, 1], "child1 to child2");
-testSameAgentCluster([1, 0], "child2 to child1");
-
-testOriginIsolationRestricted(self, true, "parent");
-testOriginIsolationRestricted(0, true, "child1");
-testOriginIsolationRestricted(1, true, "child2");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-yes-children-same.sub.https.html.headers b/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-yes-children-same.sub.https.html.headers
deleted file mode 100644
index ea3f6b33..0000000
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/parent-yes-child1-yes-child2-yes-children-same.sub.https.html.headers
+++ /dev/null
@@ -1 +0,0 @@
-Origin-Isolation: ?1
diff --git a/third_party/blink/web_tests/external/wpt/origin-isolation/resources/README.md b/third_party/blink/web_tests/external/wpt/origin-isolation/resources/README.md
index dd5e192b..9292fe3 100644
--- a/third_party/blink/web_tests/external/wpt/origin-isolation/resources/README.md
+++ b/third_party/blink/web_tests/external/wpt/origin-isolation/resources/README.md
@@ -1,6 +1,6 @@
 Why are there `.headers` files here for the `.mjs` scripts?
 
-Because `../getter-sandboxed-iframe.sub.https.html` is testing an opaque origin,
-which is cross-origin with these scripts. Since `<script type="module">`
-respects the same-origin policy, we need CORS headers to allow them to be
-accessed.
+Because `../getter-special-cases/sandboxed-iframe.sub.https.html` is testing an
+opaque origin, which is cross-origin with these scripts. Since
+`<script type="module">` respects the same-origin policy, we need CORS headers
+to allow them to be accessed.
diff --git a/third_party/blink/web_tests/external/wpt/tools/ci/tc/tasks/test.yml b/third_party/blink/web_tests/external/wpt/tools/ci/tc/tasks/test.yml
index 9396ce02..657abf1 100644
--- a/third_party/blink/web_tests/external/wpt/tools/ci/tc/tasks/test.yml
+++ b/third_party/blink/web_tests/external/wpt/tools/ci/tc/tasks/test.yml
@@ -139,8 +139,6 @@
         - vars:
             suite: reftest
         - vars:
-            suite: print-reftest
-        - vars:
             suite: wdspec
         - vars:
             suite: crashtest
@@ -214,6 +212,63 @@
                   of ${chunks.total}), run in the ${vars.channel} release of
                   ${vars.browser}.
 
+  # print-reftest are currently only supported by Chrome and Firefox.
+  - $map:
+      for:
+        - vars:
+            suite: print-reftest
+      do:
+        $map:
+          for:
+            - vars:
+                browser: firefox
+                channel: nightly
+              use:
+                - trigger-master
+                - trigger-push
+            - vars:
+                browser: firefox
+                channel: beta
+              use:
+                - trigger-weekly
+                - trigger-push
+            - vars:
+                browser: firefox
+                channel: stable
+              use:
+                - trigger-daily
+                - trigger-push
+            - vars:
+                browser: chrome
+                channel: dev
+              use:
+                - trigger-master
+                - trigger-push
+            - vars:
+                browser: chrome
+                channel: beta
+              use:
+                - trigger-weekly
+                - trigger-push
+            - vars:
+                browser: chrome
+                channel: stable
+              use:
+                - trigger-daily
+                - trigger-push
+          do:
+            - ${vars.browser}-${vars.channel}-${vars.suite}:
+                use:
+                  - wpt-base
+                  - run-options
+                  - wpt-run
+                  - browser-${vars.browser}
+                  - wpt-${vars.suite}
+                description: >-
+                  A subset of WPT's "${vars.suite}" tests (chunk number ${chunks.id}
+                  of ${chunks.total}), run in the ${vars.channel} release of
+                  ${vars.browser}.
+
   - $map:
       for:
         - vars:
diff --git a/third_party/blink/web_tests/external/wpt/tools/ci/tc/tests/test_valid.py b/third_party/blink/web_tests/external/wpt/tools/ci/tc/tests/test_valid.py
index 6ac76f4..204465a 100644
--- a/third_party/blink/web_tests/external/wpt/tools/ci/tc/tests/test_valid.py
+++ b/third_party/blink/web_tests/external/wpt/tools/ci/tc/tests/test_valid.py
@@ -123,14 +123,14 @@
       'wpt-chrome-dev-reftest-3',
       'wpt-chrome-dev-reftest-4',
       'wpt-chrome-dev-reftest-5',
-      'wpt-firefox-nightly-print-reftest-1',
-      'wpt-chrome-dev-print-reftest-1',
       'wpt-firefox-nightly-wdspec-1',
       'wpt-firefox-nightly-wdspec-2',
       'wpt-chrome-dev-wdspec-1',
       'wpt-chrome-dev-wdspec-2',
       'wpt-firefox-nightly-crashtest-1',
       'wpt-chrome-dev-crashtest-1',
+      'wpt-firefox-nightly-print-reftest-1',
+      'wpt-chrome-dev-print-reftest-1',
       'lint']),
     ("pr_event.json", True, {".taskcluster.yml",".travis.yml","tools/ci/start.sh"},
      ['lint',
@@ -243,10 +243,6 @@
       'wpt-servo-nightly-reftest-3',
       'wpt-servo-nightly-reftest-4',
       'wpt-servo-nightly-reftest-5',
-      'wpt-firefox-stable-print-reftest-1',
-      'wpt-chrome-stable-print-reftest-1',
-      'wpt-webkitgtk_minibrowser-nightly-print-reftest-1',
-      'wpt-servo-nightly-print-reftest-1',
       'wpt-firefox-stable-wdspec-1',
       'wpt-firefox-stable-wdspec-2',
       'wpt-chrome-stable-wdspec-1',
@@ -258,7 +254,9 @@
       'wpt-firefox-stable-crashtest-1',
       'wpt-chrome-stable-crashtest-1',
       'wpt-webkitgtk_minibrowser-nightly-crashtest-1',
-      'wpt-servo-nightly-crashtest-1'])
+      'wpt-servo-nightly-crashtest-1',
+      'wpt-firefox-stable-print-reftest-1',
+      'wpt-chrome-stable-print-reftest-1',])
 ])
 def test_schedule_tasks(event_path, is_pr, files_changed, expected):
     with mock.patch("tools.ci.tc.decision.get_fetch_rev", return_value=(None, None, None)):
diff --git a/third_party/blink/web_tests/external/wpt/webxr/ar-module/idlharness.https.window-expected.txt b/third_party/blink/web_tests/external/wpt/webxr/ar-module/idlharness.https.window-expected.txt
deleted file mode 100644
index 12c4446..0000000
--- a/third_party/blink/web_tests/external/wpt/webxr/ar-module/idlharness.https.window-expected.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-This is a testharness.js-based test.
-PASS idl_test setup
-PASS idl_test validation
-PASS Partial interface XRSession: original interface defined
-PASS Partial interface XRSession: member names are unique
-PASS Partial interface XRSession[2]: original interface defined
-PASS Partial interface XRSession[2]: member names are unique
-PASS Partial interface XRView: original interface defined
-PASS Partial interface XRView: member names are unique
-PASS XRSession interface: attribute environmentBlendMode
-PASS XRSession interface: attribute interactionMode
-PASS XRSession interface: xrSession must inherit property "environmentBlendMode" with the proper type
-PASS XRSession interface: xrSession must inherit property "interactionMode" with the proper type
-FAIL XRView interface: attribute isFirstPersonObserver assert_true: The prototype object must have a property "isFirstPersonObserver" expected true got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/fast/events/remove-child-onscroll-expected.txt b/third_party/blink/web_tests/fast/events/remove-child-onscroll-expected.txt
deleted file mode 100644
index 95497955..0000000
--- a/third_party/blink/web_tests/fast/events/remove-child-onscroll-expected.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-This test verifies that children can be removed by their parent element's onscroll event handler. The test succeeds if this is the only text remaining after the two scroll events are dispatched. The test fails if the inner div remains in the output or if WebKit crashes.
-
-
diff --git a/third_party/blink/web_tests/fast/events/remove-child-onscroll.html b/third_party/blink/web_tests/fast/events/remove-child-onscroll.html
index 4fd981b..af25113 100644
--- a/third_party/blink/web_tests/fast/events/remove-child-onscroll.html
+++ b/third_party/blink/web_tests/fast/events/remove-child-onscroll.html
@@ -1,39 +1,37 @@
-<html>
-    <head>
-        <script>
-            if (window.testRunner) {
-                testRunner.dumpAsText();
-                testRunner.waitUntilDone();
-            }
+<!DOCTYPE HTML>
+<script src='../../resources/testharness.js'></script>
+<script src='../../resources/testharnessreport.js'></script>
+<script src='../../resources/gesture-util.js'></script>
 
-            function dispatchScrollEvents()
-            {
-                if (window.eventSender && window.testRunner) {
-                    testRunner.waitUntilDone();
-                    var scrollCount = 0;
-                    document.getElementById('dv').addEventListener(
-                        'scroll',
-                        function(event) {
-                            this.removeChild(this.firstChild);
-                            scrollCount++;
-                            if (scrollCount == 1)
-                                eventSender.mouseScrollBy(-1, -1);
-                            else
-                                testRunner.notifyDone();
-                        },
-                        false);
+<div id="dv" style="overflow: auto; width: 200px; height: 200px;
+     whitespace: nowrap;">
+    <div style="width:300px; height:300px">
+        Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
+        eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
+        minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
+        ex ea commodo consequat. Duis aute irure dolor in reprehenderit in
+        voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
+        sint occaecat cupidatat non proident, sunt in culpa qui officia
+        deserunt mollit anim id est laborum
+    </div>
+</div>
 
-                      eventSender.mouseMoveTo(100, 100);
-                      eventSender.mouseScrollBy(0, -1);
-                }
-            }
-        </script>
-    </head>
-    <body onload="setTimeout('dispatchScrollEvents();', 1);">
-        This test verifies that children can be removed by their parent element's onscroll event handler.  The test succeeds if this is the only text remaining after the two scroll events are dispatched.  The test fails if the inner div remains in the output or if WebKit crashes.<br><br>
-        <div id="dv" style="overflow: auto; width: 200px; height: 200px; whitespace: nowrap;">
-        <div style="width:300px; height:300px">Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum
-        </div>
-        </div>
-    </body>
-</html>
+
+<script>
+promise_test(async () => {
+    var scrollCount = 0;
+    var target = document.getElementById('dv');
+    var target_center = elementCenter(target);
+    target.addEventListener('scroll', function(event) {
+        if(this.firstElementChild)
+            this.removeChild(this.firstElementChild);
+        scrollCount++;
+    });
+
+    await wheelTick(0, 1, target_center);
+    await wheelTick(0, 1, target_center);
+    assert_greater_than(scrollCount, 1);
+    assert_equals(target.children.length, 0);
+}, "No crash when a child is removed while its parent is scrolling.");
+
+</script>
diff --git a/third_party/blink/web_tests/fast/forms/select/menulist-dynamic-style-change-expected.html b/third_party/blink/web_tests/fast/forms/select/menulist-dynamic-style-change-expected.html
new file mode 100644
index 0000000..9baa8c2
--- /dev/null
+++ b/third_party/blink/web_tests/fast/forms/select/menulist-dynamic-style-change-expected.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<title>crbug.com/1103773</title>
+<style>
+select {
+  padding-left: 1rem;
+  padding-right: 1rem;
+  padding-top: 1.5rem;
+  padding-bottom: 0.5rem;
+}
+</style>
+<body>
+<select><option>abcd</select>
+</body>
diff --git a/third_party/blink/web_tests/fast/forms/select/menulist-dynamic-style-change.html b/third_party/blink/web_tests/fast/forms/select/menulist-dynamic-style-change.html
new file mode 100644
index 0000000..d286b44
--- /dev/null
+++ b/third_party/blink/web_tests/fast/forms/select/menulist-dynamic-style-change.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>crbug.com/1103773</title>
+<style>
+select {
+  padding: 1rem;
+}
+
+.update {
+  padding-top: 1.5rem;
+  padding-bottom: 0.5rem;
+}
+</style>
+<body>
+<select><option>abcd</select>
+<script>
+const select = document.querySelector('select');
+select.offsetTop;
+if (window.testRunner)
+  testRunner.waitUntilDone();
+requestAnimationFrame(() => {
+  requestAnimationFrame(() => {
+    select.classList.add('update');
+    testRunner.notifyDone();
+  });
+});
+</script>
+</body>
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/001-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/001-expected.png
new file mode 100644
index 0000000..b23170f
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/001-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/002-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/002-expected.png
new file mode 100644
index 0000000..5402b53
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/002-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/003-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/003-expected.png
new file mode 100644
index 0000000..06ec0b4
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/003-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/004-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/004-expected.png
new file mode 100644
index 0000000..5402b53
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/004-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/005-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/005-expected.png
new file mode 100644
index 0000000..b23170f
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/005-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/006-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/006-expected.png
new file mode 100644
index 0000000..f66f23a
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/006-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/010-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/010-expected.png
new file mode 100644
index 0000000..ed7ac711
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/010-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/011-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/011-expected.png
new file mode 100644
index 0000000..ed7ac711
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/011-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/012-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/012-expected.png
new file mode 100644
index 0000000..7657582
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/012-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/015-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/015-expected.png
new file mode 100644
index 0000000..754674f4
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/015-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/016-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/016-expected.png
new file mode 100644
index 0000000..4799ea48
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/016-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/017-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/017-expected.png
new file mode 100644
index 0000000..89fce59
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/017-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/018-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/018-expected.png
new file mode 100644
index 0000000..b2bbd01
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/018-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/019-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/019-expected.png
new file mode 100644
index 0000000..cb388bb
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/019-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/020-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/020-expected.png
new file mode 100644
index 0000000..83420849
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/020-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/021-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/021-expected.png
new file mode 100644
index 0000000..ae88020
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/021-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/022-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/022-expected.png
new file mode 100644
index 0000000..ae88020
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/022-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/025-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/025-expected.png
new file mode 100644
index 0000000..ae88020
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/block/margin-collapse/block-inside-inline/025-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/001-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/001-expected.png
new file mode 100644
index 0000000..bd2b440
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/001-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/002-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/002-expected.png
new file mode 100644
index 0000000..768c128
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/002-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/25277-2-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/25277-2-expected.png
new file mode 100644
index 0000000..93d153e
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/25277-2-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/25277-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/25277-expected.png
new file mode 100644
index 0000000..6251c8e
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/25277-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/absolute-positioned-block-in-centred-block-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/absolute-positioned-block-in-centred-block-expected.png
new file mode 100644
index 0000000..ea3dc0aa26
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/absolute-positioned-block-in-centred-block-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/absolute-positioned-inline-in-centred-block-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/absolute-positioned-inline-in-centred-block-expected.png
new file mode 100644
index 0000000..7e94ad74
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/absolute-positioned-inline-in-centred-block-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/br-text-decoration-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/br-text-decoration-expected.png
new file mode 100644
index 0000000..14a12ee
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/br-text-decoration-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/continuation-outlines-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/continuation-outlines-expected.png
new file mode 100644
index 0000000..7577aca
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/continuation-outlines-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/continuation-outlines-with-layers-2-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/continuation-outlines-with-layers-2-expected.png
new file mode 100644
index 0000000..1d02d1a
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/continuation-outlines-with-layers-2-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/continuation-outlines-with-layers-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/continuation-outlines-with-layers-expected.png
new file mode 100644
index 0000000..a587f09
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/continuation-outlines-with-layers-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/drawStyledEmptyInlines-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/drawStyledEmptyInlines-expected.png
new file mode 100644
index 0000000..f7e7a3d
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/drawStyledEmptyInlines-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/drawStyledEmptyInlinesWithWS-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/drawStyledEmptyInlinesWithWS-expected.png
new file mode 100644
index 0000000..27b7cc1
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/drawStyledEmptyInlinesWithWS-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/emptyInlinesWithinLists-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/emptyInlinesWithinLists-expected.png
new file mode 100644
index 0000000..798d732
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/emptyInlinesWithinLists-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-borders-with-bidi-override-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-borders-with-bidi-override-expected.png
new file mode 100644
index 0000000..6f56621
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-borders-with-bidi-override-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-box-background-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-box-background-expected.png
new file mode 100644
index 0000000..02eadd9
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-box-background-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-box-background-long-image-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-box-background-long-image-expected.png
new file mode 100644
index 0000000..415e810
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-box-background-long-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-box-background-repeat-x-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-box-background-repeat-x-expected.png
new file mode 100644
index 0000000..3240cb4
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-box-background-repeat-x-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-box-background-repeat-y-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-box-background-repeat-y-expected.png
new file mode 100644
index 0000000..5efbb5a5
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-box-background-repeat-y-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-continuation-borders-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-continuation-borders-expected.png
new file mode 100644
index 0000000..1ebce73
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-continuation-borders-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-focus-ring-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-focus-ring-expected.png
new file mode 100644
index 0000000..f75b0e8
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-padding-disables-text-quirk-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-padding-disables-text-quirk-expected.png
new file mode 100644
index 0000000..e19ced3
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-padding-disables-text-quirk-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-text-quirk-bpm-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-text-quirk-bpm-expected.png
new file mode 100644
index 0000000..136c25c
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-text-quirk-bpm-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-wrap-with-parent-padding-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-wrap-with-parent-padding-expected.png
new file mode 100644
index 0000000..8efde12
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/inline-wrap-with-parent-padding-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/justify-emphasis-inline-box-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/justify-emphasis-inline-box-expected.png
new file mode 100644
index 0000000..5d11f2e
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/justify-emphasis-inline-box-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/left-right-center-inline-alignment-in-ltr-and-rtl-blocks-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/left-right-center-inline-alignment-in-ltr-and-rtl-blocks-expected.png
new file mode 100644
index 0000000..74c722e
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/left-right-center-inline-alignment-in-ltr-and-rtl-blocks-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/long-wrapped-line-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/long-wrapped-line-expected.png
new file mode 100644
index 0000000..941b4d7
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/long-wrapped-line-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/nested-top-alignment-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/nested-top-alignment-expected.png
new file mode 100644
index 0000000..361e6a3
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/nested-top-alignment-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/outline-offset-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/outline-offset-expected.png
new file mode 100644
index 0000000..7711094
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/outline-offset-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/percentage-margins-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/percentage-margins-expected.png
new file mode 100644
index 0000000..07be8ce
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/percentage-margins-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/positioned-object-between-replaced-elements-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/positioned-object-between-replaced-elements-expected.png
new file mode 100644
index 0000000..1925353
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/positioned-object-between-replaced-elements-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/positionedLifetime-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/positionedLifetime-expected.png
new file mode 100644
index 0000000..a30cb3e
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/positionedLifetime-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/styledEmptyInlinesWithBRs-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/styledEmptyInlinesWithBRs-expected.png
new file mode 100644
index 0000000..5493a85
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/styledEmptyInlinesWithBRs-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/vertical-align-text-bottom-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/vertical-align-text-bottom-expected.png
new file mode 100644
index 0000000..a0da8bd
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/vertical-align-text-bottom-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/vertical-align-with-fallback-fonts-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/vertical-align-with-fallback-fonts-expected.png
new file mode 100644
index 0000000..22a449cf
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/inline/vertical-align-with-fallback-fonts-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/001-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/001-expected.png
new file mode 100644
index 0000000..107a78a
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/001-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/002-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/002-expected.png
new file mode 100644
index 0000000..7ff1a73
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/002-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/003-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/003-expected.png
new file mode 100644
index 0000000..7ff1a73
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/003-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/004-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/004-expected.png
new file mode 100644
index 0000000..21426e9
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/004-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/005-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/005-expected.png
new file mode 100644
index 0000000..0205512
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/005-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/006-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/006-expected.png
new file mode 100644
index 0000000..b2bc105
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/006-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/007-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/007-expected.png
new file mode 100644
index 0000000..414943d
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/007-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/008-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/008-expected.png
new file mode 100644
index 0000000..18ae570
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/008-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/absolute-image-sizing-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/absolute-image-sizing-expected.png
new file mode 100644
index 0000000..0b5e9f3d
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/absolute-image-sizing-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/absolute-position-percentage-height-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/absolute-position-percentage-height-expected.png
new file mode 100644
index 0000000..473be57
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/absolute-position-percentage-height-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/absolute-position-percentage-width-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/absolute-position-percentage-width-expected.png
new file mode 100644
index 0000000..2870657
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/absolute-position-percentage-width-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/absolute-position-with-auto-height-and-top-and-bottom-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/absolute-position-with-auto-height-and-top-and-bottom-expected.png
new file mode 100644
index 0000000..4867fcd
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/absolute-position-with-auto-height-and-top-and-bottom-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/absolute-position-with-auto-width-and-left-and-right-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/absolute-position-with-auto-width-and-left-and-right-expected.png
new file mode 100644
index 0000000..7b3d9a0
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/absolute-position-with-auto-width-and-left-and-right-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/border-radius-clip-content-edge-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/border-radius-clip-content-edge-expected.png
new file mode 100644
index 0000000..400aa5a
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/border-radius-clip-content-edge-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/border-radius-clip-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/border-radius-clip-expected.png
new file mode 100644
index 0000000..7786a90
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/border-radius-clip-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/embed-display-none-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/embed-display-none-expected.png
new file mode 100644
index 0000000..aba6b50
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/embed-display-none-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/image-onload-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/image-onload-expected.png
new file mode 100644
index 0000000..b21a66e
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/image-onload-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/image-resize-width-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/image-resize-width-expected.png
new file mode 100644
index 0000000..e8008106
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/image-resize-width-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/image-sizing-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/image-sizing-expected.png
new file mode 100644
index 0000000..634628d9
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/image-sizing-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/image-solid-color-with-alpha-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/image-solid-color-with-alpha-expected.png
new file mode 100644
index 0000000..34040f8
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/image-solid-color-with-alpha-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/image-tag-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/image-tag-expected.png
new file mode 100644
index 0000000..f76c11bd
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/image-tag-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/max-width-percent-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/max-width-percent-expected.png
new file mode 100644
index 0000000..9b03e00
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/max-width-percent-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/maxheight-percent-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/maxheight-percent-expected.png
new file mode 100644
index 0000000..72731657
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/maxheight-percent-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/maxheight-pxs-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/maxheight-pxs-expected.png
new file mode 100644
index 0000000..22e4d9f
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/maxheight-pxs-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/maxwidth-percent-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/maxwidth-percent-expected.png
new file mode 100644
index 0000000..9570c0e
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/maxwidth-percent-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/maxwidth-pxs-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/maxwidth-pxs-expected.png
new file mode 100644
index 0000000..22e4d9f
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/maxwidth-pxs-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/minheight-percent-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/minheight-percent-expected.png
new file mode 100644
index 0000000..72731657
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/minheight-percent-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/minheight-pxs-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/minheight-pxs-expected.png
new file mode 100644
index 0000000..22e4d9f
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/minheight-pxs-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/minwidth-percent-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/minwidth-percent-expected.png
new file mode 100644
index 0000000..9570c0e
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/minwidth-percent-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/minwidth-pxs-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/minwidth-pxs-expected.png
new file mode 100644
index 0000000..22e4d9f
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/minwidth-pxs-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/object-align-hspace-vspace-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/object-align-hspace-vspace-expected.png
new file mode 100644
index 0000000..788d512
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/object-align-hspace-vspace-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/object-display-none-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/object-display-none-expected.png
new file mode 100644
index 0000000..d89a49f
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/object-display-none-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/outline-replaced-elements-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/outline-replaced-elements-expected.png
new file mode 100644
index 0000000..b3973c7
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/outline-replaced-elements-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/percent-height-in-anonymous-block-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/percent-height-in-anonymous-block-expected.png
new file mode 100644
index 0000000..626a5c7
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/percent-height-in-anonymous-block-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/percent-height-in-anonymous-block-in-table-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/percent-height-in-anonymous-block-in-table-expected.png
new file mode 100644
index 0000000..3c112bf
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/percent-height-in-anonymous-block-in-table-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/percent-height-in-anonymous-block-widget-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/percent-height-in-anonymous-block-widget-expected.png
new file mode 100644
index 0000000..404a3e8c
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/percent-height-in-anonymous-block-widget-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/replaced-breaking-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/replaced-breaking-expected.png
new file mode 100644
index 0000000..96f9a9a
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/replaced-breaking-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/replaced-breaking-mixture-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/replaced-breaking-mixture-expected.png
new file mode 100644
index 0000000..efc337b
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/replaced-breaking-mixture-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/selection-rect-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/selection-rect-expected.png
new file mode 100644
index 0000000..1615fc7
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/selection-rect-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/selection-rect-in-table-cell-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/selection-rect-in-table-cell-expected.png
new file mode 100644
index 0000000..35d1d23
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/selection-rect-in-table-cell-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/selection-rect-transform-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/selection-rect-transform-expected.png
new file mode 100644
index 0000000..1d6482b
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/selection-rect-transform-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/table-percent-height-positioned-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/table-percent-height-positioned-expected.png
new file mode 100644
index 0000000..ebd4f12
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/table-percent-height-positioned-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/three-selects-break-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/three-selects-break-expected.png
new file mode 100644
index 0000000..d509a5c
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/three-selects-break-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/vertical-lr/absolute-position-percentage-width-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/vertical-lr/absolute-position-percentage-width-expected.png
new file mode 100644
index 0000000..c9fb157
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/vertical-lr/absolute-position-percentage-width-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/vertical-lr/absolute-position-with-auto-height-and-top-and-bottom-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/vertical-lr/absolute-position-with-auto-height-and-top-and-bottom-expected.png
new file mode 100644
index 0000000..84d80045
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/vertical-lr/absolute-position-with-auto-height-and-top-and-bottom-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/vertical-lr/absolute-position-with-auto-width-and-left-and-right-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/vertical-lr/absolute-position-with-auto-width-and-left-and-right-expected.png
new file mode 100644
index 0000000..beaba21
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/vertical-lr/absolute-position-with-auto-width-and-left-and-right-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/vertical-rl/absolute-position-percentage-width-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/vertical-rl/absolute-position-percentage-width-expected.png
new file mode 100644
index 0000000..cda8c37d
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/vertical-rl/absolute-position-percentage-width-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/vertical-rl/absolute-position-with-auto-height-and-top-and-bottom-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/vertical-rl/absolute-position-with-auto-height-and-top-and-bottom-expected.png
new file mode 100644
index 0000000..204a00c
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/vertical-rl/absolute-position-with-auto-height-and-top-and-bottom-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/vertical-rl/absolute-position-with-auto-width-and-left-and-right-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/vertical-rl/absolute-position-with-auto-width-and-left-and-right-expected.png
new file mode 100644
index 0000000..41192245
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/vertical-rl/absolute-position-with-auto-width-and-left-and-right-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/width100percent-button-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/width100percent-button-expected.png
new file mode 100644
index 0000000..ba624e8
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/width100percent-button-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/width100percent-checkbox-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/width100percent-checkbox-expected.png
new file mode 100644
index 0000000..8c19284
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/width100percent-checkbox-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/width100percent-image-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/width100percent-image-expected.png
new file mode 100644
index 0000000..78464ee
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/width100percent-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/width100percent-menulist-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/width100percent-menulist-expected.png
new file mode 100644
index 0000000..e86d8400
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/width100percent-menulist-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/width100percent-radio-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/width100percent-radio-expected.png
new file mode 100644
index 0000000..0e5ee00
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/width100percent-radio-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/width100percent-searchfield-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/width100percent-searchfield-expected.png
new file mode 100644
index 0000000..0534429c7
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/width100percent-searchfield-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/width100percent-textarea-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/width100percent-textarea-expected.png
new file mode 100644
index 0000000..8aee8fd
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/width100percent-textarea-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/width100percent-textfield-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/width100percent-textfield-expected.png
new file mode 100644
index 0000000..0534429c7
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/replaced/width100percent-textfield-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/base-shorter-than-text-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/base-shorter-than-text-expected.png
new file mode 100644
index 0000000..d31fa51
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/base-shorter-than-text-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/float-overhang-from-ruby-text-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/float-overhang-from-ruby-text-expected.png
new file mode 100644
index 0000000..d5231beb
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/float-overhang-from-ruby-text-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/floating-ruby-text-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/floating-ruby-text-expected.png
new file mode 100644
index 0000000..0ad21d1d
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/floating-ruby-text-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/nested-ruby-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/nested-ruby-expected.png
new file mode 100644
index 0000000..08a2a17
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/nested-ruby-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/overhang-horizontal-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/overhang-horizontal-expected.png
new file mode 100644
index 0000000..50b2009e
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/overhang-horizontal-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/overhang-horizontal-no-overlap1-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/overhang-horizontal-no-overlap1-expected.png
new file mode 100644
index 0000000..1b2f9fa5
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/overhang-horizontal-no-overlap1-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/overhang-horizontal-no-overlap2-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/overhang-horizontal-no-overlap2-expected.png
new file mode 100644
index 0000000..2a59e3c
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/overhang-horizontal-no-overlap2-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/overhang-vertical-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/overhang-vertical-expected.png
new file mode 100644
index 0000000..4745620
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/overhang-vertical-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/overhang-vertical-no-overlap1-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/overhang-vertical-no-overlap1-expected.png
new file mode 100644
index 0000000..e6eac563
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/overhang-vertical-no-overlap1-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/overhang-vertical-no-overlap2-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/overhang-vertical-no-overlap2-expected.png
new file mode 100644
index 0000000..917b1f6
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/overhang-vertical-no-overlap2-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/position-after-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/position-after-expected.png
new file mode 100644
index 0000000..b6208ca
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/position-after-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/positioned-ruby-text-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/positioned-ruby-text-expected.png
new file mode 100644
index 0000000..0ad21d1d
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/positioned-ruby-text-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-base-merge-block-children-crash-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-base-merge-block-children-crash-expected.png
new file mode 100644
index 0000000..fd80c03a6
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-base-merge-block-children-crash-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-block-style-not-updated-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-block-style-not-updated-expected.png
new file mode 100644
index 0000000..64270d41
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-block-style-not-updated-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-empty-rt-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-empty-rt-expected.png
new file mode 100644
index 0000000..819ed48
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-empty-rt-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-inline-style-not-updated-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-inline-style-not-updated-expected.png
new file mode 100644
index 0000000..64270d41
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-inline-style-not-updated-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-inline-table-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-inline-table-expected.png
new file mode 100644
index 0000000..8051b0a
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-inline-table-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-length-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-length-expected.png
new file mode 100644
index 0000000..150348fc
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-length-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-run-break-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-run-break-expected.png
new file mode 100644
index 0000000..0392a149
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-run-break-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-runs-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-runs-expected.png
new file mode 100644
index 0000000..12b3d14
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-runs-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-runs-spans-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-runs-spans-expected.png
new file mode 100644
index 0000000..fb36e7f
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-runs-spans-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-simple-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-simple-expected.png
new file mode 100644
index 0000000..1519d73
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-simple-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-simple-rp-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-simple-rp-expected.png
new file mode 100644
index 0000000..0ed8c524
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-simple-rp-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-text-before-after-content-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-text-before-after-content-expected.png
new file mode 100644
index 0000000..1ec98f0
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-text-before-after-content-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-text-before-child-split-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-text-before-child-split-expected.png
new file mode 100644
index 0000000..0ad21d1d
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-text-before-child-split-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-trailing-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-trailing-expected.png
new file mode 100644
index 0000000..6dfbe73
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/ruby-trailing-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/select-ruby-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/select-ruby-expected.png
new file mode 100644
index 0000000..537f43b4
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/ruby/select-ruby-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/sub-pixel/selection/selection-rect-in-sub-pixel-table-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/sub-pixel/selection/selection-rect-in-sub-pixel-table-expected.png
new file mode 100644
index 0000000..66f846dd
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/sub-pixel/selection/selection-rect-in-sub-pixel-table-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/sub-pixel/sub-pixel-iframe-copy-on-scroll-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/sub-pixel/sub-pixel-iframe-copy-on-scroll-expected.png
new file mode 100644
index 0000000..cc147fe
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/sub-pixel/sub-pixel-iframe-copy-on-scroll-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png
new file mode 100644
index 0000000..29113fa
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/paint/invalidation/scroll/overflow-scroll-in-overflow-scroll-scrolled-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/paint/invalidation/scroll/overflow-scroll-in-overflow-scroll-scrolled-expected.png
deleted file mode 100644
index 735bbbdd..0000000
--- a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/paint/invalidation/scroll/overflow-scroll-in-overflow-scroll-scrolled-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/paint/invalidation/table/table-overflow-scroll-in-overflow-scroll-scrolled-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/paint/invalidation/table/table-overflow-scroll-in-overflow-scroll-scrolled-expected.png
deleted file mode 100644
index 6143130d..0000000
--- a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/paint/invalidation/table/table-overflow-scroll-in-overflow-scroll-scrolled-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-animate-rotate-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-animate-rotate-expected.png
new file mode 100644
index 0000000..e2101bd
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-animate-rotate-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-background-image-cover-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-background-image-cover-expected.png
new file mode 100644
index 0000000..40a4cc9
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-background-image-cover-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-expected.png
new file mode 100644
index 0000000..2dbae54
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-png-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-png-expected.png
new file mode 100644
index 0000000..2dbae54
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-png-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png
new file mode 100644
index 0000000..cd10794
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
new file mode 100644
index 0000000..e40632d
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-border-fade-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-border-fade-expected.png
new file mode 100644
index 0000000..ffa0219
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-border-fade-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-border-image-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-border-image-expected.png
new file mode 100644
index 0000000..5c58623
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-border-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-border-image-source-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-border-image-source-expected.png
new file mode 100644
index 0000000..da1f1b1
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-border-image-source-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-clip-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-clip-expected.png
new file mode 100644
index 0000000..aa0a724
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-clip-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-iframe-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-iframe-expected.png
new file mode 100644
index 0000000..c15e28e
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-iframe-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png
new file mode 100644
index 0000000..41592b2
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-image-canvas-pattern-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-image-canvas-pattern-expected.png
new file mode 100644
index 0000000..bcf57521
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-image-canvas-pattern-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-image-canvas-svg-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-image-canvas-svg-expected.png
new file mode 100644
index 0000000..dc58a742
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-image-canvas-svg-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-image-filter-all-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-image-filter-all-expected.png
new file mode 100644
index 0000000..cc18eac
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-image-filter-all-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-image-object-fit-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-image-object-fit-expected.png
new file mode 100644
index 0000000..2d81298
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-image-object-fit-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-image-pseudo-content-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-image-pseudo-content-expected.png
new file mode 100644
index 0000000..b15c88f
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-image-pseudo-content-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-image-shape-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-image-shape-expected.png
new file mode 100644
index 0000000..49a4b04
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-image-shape-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-image-svg-resource-url-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-image-svg-resource-url-expected.png
new file mode 100644
index 0000000..78d9294
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-image-svg-resource-url-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png
new file mode 100644
index 0000000..af0096f
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-munsell-adobe-to-srgb-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-munsell-adobe-to-srgb-expected.png
new file mode 100644
index 0000000..4873b80
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-munsell-adobe-to-srgb-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-munsell-adobe-to-srgb-webgl-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-munsell-adobe-to-srgb-webgl-expected.png
new file mode 100644
index 0000000..56f36ca
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-munsell-adobe-to-srgb-webgl-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-munsell-srgb-to-srgb-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-munsell-srgb-to-srgb-expected.png
new file mode 100644
index 0000000..4e1a3e17
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-munsell-srgb-to-srgb-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-object-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-object-expected.png
new file mode 100644
index 0000000..427fb2c
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-object-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-svg-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-svg-expected.png
new file mode 100644
index 0000000..9edf17c6
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-svg-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-svg-fill-text-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-svg-fill-text-expected.png
new file mode 100644
index 0000000..3856479
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/color-profile-svg-fill-text-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/cross-fade-background-size-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/cross-fade-background-size-expected.png
new file mode 100644
index 0000000..c6ca5b6
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/cross-fade-background-size-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/cross-fade-invalidation-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/cross-fade-invalidation-expected.png
new file mode 100644
index 0000000..c1673ab2
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/cross-fade-invalidation-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/cross-fade-tiled-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/cross-fade-tiled-expected.png
new file mode 100644
index 0000000..8e3c124
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/cross-fade-tiled-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/drag-image-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/drag-image-expected.png
new file mode 100644
index 0000000..366d125
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/drag-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/favicon-as-image-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/favicon-as-image-expected.png
new file mode 100644
index 0000000..655e945
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/favicon-as-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/gray-scale-jpeg-with-color-profile-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/gray-scale-jpeg-with-color-profile-expected.png
new file mode 100644
index 0000000..ef776f73
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/gray-scale-jpeg-with-color-profile-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/icon-decoding-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/icon-decoding-expected.png
new file mode 100644
index 0000000..1c6bfae1
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/icon-decoding-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/image-css3-content-data-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/image-css3-content-data-expected.png
new file mode 100644
index 0000000..fd853d45
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/image-css3-content-data-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/image-in-map-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/image-in-map-expected.png
new file mode 100644
index 0000000..42067d4
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/image-in-map-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/image-map-anchor-children-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/image-map-anchor-children-expected.png
new file mode 100644
index 0000000..084f8cdc
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/image-map-anchor-children-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-circle-focus-ring-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-circle-focus-ring-expected.png
new file mode 100644
index 0000000..25ca16f
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-circle-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-focus-ring-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-focus-ring-expected.png
new file mode 100644
index 0000000..80cabef
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-focus-ring-in-positioned-container-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-focus-ring-in-positioned-container-expected.png
new file mode 100644
index 0000000..66d5a59
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-focus-ring-in-positioned-container-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-focus-ring-outline-color-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-focus-ring-outline-color-expected.png
new file mode 100644
index 0000000..75ac18b
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-focus-ring-outline-color-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-focus-ring-outline-color-explicitly-inherited-from-map-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-focus-ring-outline-color-explicitly-inherited-from-map-expected.png
new file mode 100644
index 0000000..e7148ab4
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-focus-ring-outline-color-explicitly-inherited-from-map-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-focus-ring-with-paint-root-offset-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-focus-ring-with-paint-root-offset-expected.png
new file mode 100644
index 0000000..f6fd8ef3
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-focus-ring-with-paint-root-offset-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-focus-ring-with-scale-transform-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-focus-ring-with-scale-transform-expected.png
new file mode 100644
index 0000000..cfe799d8
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-focus-ring-with-scale-transform-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-focus-ring-zero-outline-width-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-focus-ring-zero-outline-width-expected.png
new file mode 100644
index 0000000..dd39d2c
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-focus-ring-zero-outline-width-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-focus-ring-zoom-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-focus-ring-zoom-expected.png
new file mode 100644
index 0000000..085c740f
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-focus-ring-zoom-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-overflowing-circle-focus-ring-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-overflowing-circle-focus-ring-expected.png
new file mode 100644
index 0000000..561c611d
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-overflowing-circle-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
new file mode 100644
index 0000000..5d4ee0f
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png
new file mode 100644
index 0000000..5ff259b1
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/jpeg-with-color-profile-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/jpeg-with-color-profile-expected.png
new file mode 100644
index 0000000..13cd5b0
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/jpeg-with-color-profile-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/jpeg-yuv-progressive-image-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/jpeg-yuv-progressive-image-expected.png
new file mode 100644
index 0000000..e18c682
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/jpeg-yuv-progressive-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/missing-image-border-zoom-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/missing-image-border-zoom-expected.png
new file mode 100644
index 0000000..bd20fae
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/missing-image-border-zoom-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/motion-jpeg-single-frame-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/motion-jpeg-single-frame-expected.png
new file mode 100644
index 0000000..00198ce
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/motion-jpeg-single-frame-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/optimize-contrast-canvas-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/optimize-contrast-canvas-expected.png
new file mode 100644
index 0000000..ef378d0
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/optimize-contrast-canvas-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/optimize-contrast-image-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/optimize-contrast-image-expected.png
new file mode 100644
index 0000000..ef378d0
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/optimize-contrast-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/paletted-png-with-color-profile-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/paletted-png-with-color-profile-expected.png
new file mode 100644
index 0000000..537d8db
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/paletted-png-with-color-profile-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/pixel-crack-image-background-webkit-transform-scale-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/pixel-crack-image-background-webkit-transform-scale-expected.png
new file mode 100644
index 0000000..e31ab2a9
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/pixel-crack-image-background-webkit-transform-scale-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/png-suite/test-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/png-suite/test-expected.png
new file mode 100644
index 0000000..f68a612
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/png-suite/test-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/png-with-color-profile-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/png-with-color-profile-expected.png
new file mode 100644
index 0000000..13cd5b0
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/png-with-color-profile-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/rendering-broken-0px-images-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/rendering-broken-0px-images-expected.png
new file mode 100644
index 0000000..5f81e02
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/rendering-broken-0px-images-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/rendering-broken-0px-images-quirk-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/rendering-broken-0px-images-quirk-expected.png
new file mode 100644
index 0000000..8b3476f
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/rendering-broken-0px-images-quirk-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/rendering-broken-10px-images-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/rendering-broken-10px-images-expected.png
new file mode 100644
index 0000000..40c1c20
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/rendering-broken-10px-images-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/rendering-broken-16px-images-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/rendering-broken-16px-images-expected.png
new file mode 100644
index 0000000..71fbd74
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/rendering-broken-16px-images-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/rendering-broken-1px-images-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/rendering-broken-1px-images-expected.png
new file mode 100644
index 0000000..68d5f69
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/rendering-broken-1px-images-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/rendering-broken-block-flow-images-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/rendering-broken-block-flow-images-expected.png
new file mode 100644
index 0000000..741a323
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/rendering-broken-block-flow-images-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/rendering-broken-images-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/rendering-broken-images-expected.png
new file mode 100644
index 0000000..facad199
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/rendering-broken-images-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/rgb-png-with-cmyk-color-profile-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/rgb-png-with-cmyk-color-profile-expected.png
new file mode 100644
index 0000000..6506e65
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/rgb-png-with-cmyk-color-profile-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/sprite-no-bleed-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/sprite-no-bleed-expected.png
new file mode 100644
index 0000000..6369f97
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/sprite-no-bleed-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/webp-color-profile-lossless-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/webp-color-profile-lossless-expected.png
new file mode 100644
index 0000000..1cbd441
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/webp-color-profile-lossless-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/webp-color-profile-lossy-alpha-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/webp-color-profile-lossy-alpha-expected.png
new file mode 100644
index 0000000..925982bb
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/webp-color-profile-lossy-alpha-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png
new file mode 100644
index 0000000..e6f64f40
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/yuv-decode-eligible/color-profile-filter-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/yuv-decode-eligible/color-profile-filter-expected.png
new file mode 100644
index 0000000..2717fb8
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/yuv-decode-eligible/color-profile-filter-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/yuv-decode-eligible/color-profile-image-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/yuv-decode-eligible/color-profile-image-expected.png
new file mode 100644
index 0000000..0232c59
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/yuv-decode-eligible/color-profile-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/yuv-decode-eligible/color-profile-image-profile-match-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/yuv-decode-eligible/color-profile-image-profile-match-expected.png
new file mode 100644
index 0000000..62d4751
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/yuv-decode-eligible/color-profile-image-profile-match-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/yuv-decode-eligible/webp-color-profile-lossy-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/yuv-decode-eligible/webp-color-profile-lossy-expected.png
new file mode 100644
index 0000000..4e598b9
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/yuv-decode-eligible/webp-color-profile-lossy-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/yuv-decode-eligible/webp-no-color-profile-lossy-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/yuv-decode-eligible/webp-no-color-profile-lossy-expected.png
new file mode 100644
index 0000000..b7c3f35
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/gpu-rasterization/images/yuv-decode-eligible/webp-no-color-profile-lossy-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/off-main-thread-css-paint/http/tests/csspaint/border-color-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/off-main-thread-css-paint/http/tests/csspaint/border-color-expected.png
new file mode 100644
index 0000000..b978b2a
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/off-main-thread-css-paint/http/tests/csspaint/border-color-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/accelerated-scrolling-with-clip-path-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/accelerated-scrolling-with-clip-path-expected.png
new file mode 100644
index 0000000..2d1aaba
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/accelerated-scrolling-with-clip-path-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/accelerated-scrolling-with-clip-path-text-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/accelerated-scrolling-with-clip-path-text-expected.png
new file mode 100644
index 0000000..e644bb9
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/accelerated-scrolling-with-clip-path-text-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/ancestor-overflow-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/ancestor-overflow-expected.png
new file mode 100644
index 0000000..058ec98
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/ancestor-overflow-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/border-radius-above-composited-subframe-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/border-radius-above-composited-subframe-expected.png
new file mode 100644
index 0000000..93f5e22
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/border-radius-above-composited-subframe-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/border-radius-composited-subframe-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/border-radius-composited-subframe-expected.png
new file mode 100644
index 0000000..0b96948
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/border-radius-composited-subframe-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/border-radius-on-grandparent-composited-grandchild-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/border-radius-on-grandparent-composited-grandchild-expected.png
new file mode 100644
index 0000000..0fe5e7e76
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/border-radius-on-grandparent-composited-grandchild-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/border-radius-on-parent-composited-grandchild-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/border-radius-on-parent-composited-grandchild-expected.png
new file mode 100644
index 0000000..c0068f2
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/border-radius-on-parent-composited-grandchild-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/border-radius-on-two-ancestors-composited-grandchild-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/border-radius-on-two-ancestors-composited-grandchild-expected.png
new file mode 100644
index 0000000..c44f46c
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/border-radius-on-two-ancestors-composited-grandchild-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/border-radius-outside-bounds-of-compositing-ancestor-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/border-radius-outside-bounds-of-compositing-ancestor-expected.png
new file mode 100644
index 0000000..7948e09
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/border-radius-outside-bounds-of-compositing-ancestor-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/border-radius-styles-with-composited-child-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/border-radius-styles-with-composited-child-expected.png
new file mode 100644
index 0000000..02a228f
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/border-radius-styles-with-composited-child-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/clip-content-under-overflow-controls-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/clip-content-under-overflow-controls-expected.png
new file mode 100644
index 0000000..dde7bbf8
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/clip-content-under-overflow-controls-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/clip-parent-reset-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/clip-parent-reset-expected.png
new file mode 100644
index 0000000..53cbfe8
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/clip-parent-reset-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/fixed-position-ancestor-clip-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/fixed-position-ancestor-clip-expected.png
new file mode 100644
index 0000000..b0057ff
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/fixed-position-ancestor-clip-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/grandchild-composited-with-border-radius-ancestor-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/grandchild-composited-with-border-radius-ancestor-expected.png
new file mode 100644
index 0000000..1833e11
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/grandchild-composited-with-border-radius-ancestor-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/grandchild-with-border-radius-ancestor-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/grandchild-with-border-radius-ancestor-expected.png
new file mode 100644
index 0000000..1833e11
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/grandchild-with-border-radius-ancestor-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/mask-with-filter-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/mask-with-filter-expected.png
new file mode 100644
index 0000000..906dd4b
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/mask-with-filter-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/mask-with-small-content-rect-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/mask-with-small-content-rect-expected.png
new file mode 100644
index 0000000..81e54f80
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/mask-with-small-content-rect-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/nested-border-radius-clipping-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/nested-border-radius-clipping-expected.png
new file mode 100644
index 0000000..a0299c1
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/nested-border-radius-clipping-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/nested-render-surfaces-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/nested-render-surfaces-expected.png
new file mode 100644
index 0000000..c659c80
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/nested-render-surfaces-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/nested-render-surfaces-with-intervening-clip-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/nested-render-surfaces-with-intervening-clip-expected.png
new file mode 100644
index 0000000..2778568
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/nested-render-surfaces-with-intervening-clip-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/nested-render-surfaces-with-rotation-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
new file mode 100644
index 0000000..bea19da
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/nested-scrolling-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/nested-scrolling-expected.png
new file mode 100644
index 0000000..3544c45
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/nested-scrolling-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/overflow-compositing-descendant-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/overflow-compositing-descendant-expected.png
new file mode 100644
index 0000000..d285fe26
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/overflow-compositing-descendant-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/overflow-positioning-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/overflow-positioning-expected.png
new file mode 100644
index 0000000..3ec02c6
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/overflow-positioning-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/overflow-scroll-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/overflow-scroll-expected.png
new file mode 100644
index 0000000..38d5831
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/overflow-scroll-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/parent-overflow-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/parent-overflow-expected.png
new file mode 100644
index 0000000..9e6c45b
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/parent-overflow-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/relpos-under-abspos-border-radius-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/relpos-under-abspos-border-radius-expected.png
new file mode 100644
index 0000000..ed480437
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/relpos-under-abspos-border-radius-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/remove-overflow-crash2-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/remove-overflow-crash2-expected.png
new file mode 100644
index 0000000..ad37897d3
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/remove-overflow-crash2-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/rotate-clip-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/rotate-clip-expected.png
new file mode 100644
index 0000000..43d39b0
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/rotate-clip-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/rotate-then-clip-effect-interleave-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/rotate-then-clip-effect-interleave-expected.png
new file mode 100644
index 0000000..99946db
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/rotate-then-clip-effect-interleave-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/rotate-then-clip-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/rotate-then-clip-expected.png
new file mode 100644
index 0000000..00798e9
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/rotate-then-clip-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/rotate-then-clip-z-order-interleave-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/rotate-then-clip-z-order-interleave-expected.png
new file mode 100644
index 0000000..6413b6e
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/rotate-then-clip-z-order-interleave-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/scaled-mask-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/scaled-mask-expected.png
new file mode 100644
index 0000000..71bbf14
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/scaled-mask-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/scroll-ancestor-update-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/scroll-ancestor-update-expected.png
new file mode 100644
index 0000000..ed395e5
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/scroll-ancestor-update-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/scrollbar-painting-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/scrollbar-painting-expected.png
new file mode 100644
index 0000000..aac3fe6d
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/scrollbar-painting-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/siblings-composited-with-border-radius-ancestor-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/siblings-composited-with-border-radius-ancestor-expected.png
new file mode 100644
index 0000000..1833e11
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/siblings-composited-with-border-radius-ancestor-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/siblings-with-border-radius-ancestor-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/siblings-with-border-radius-ancestor-expected.png
new file mode 100644
index 0000000..bea96ba
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/siblings-with-border-radius-ancestor-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/theme-affects-visual-overflow-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/theme-affects-visual-overflow-expected.png
new file mode 100644
index 0000000..222cc9f
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/theme-affects-visual-overflow-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/tiled-mask-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/tiled-mask-expected.png
new file mode 100644
index 0000000..afac061
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/tiled-mask-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/update-widget-positions-on-nested-frames-and-scrollers-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/update-widget-positions-on-nested-frames-and-scrollers-expected.png
new file mode 100644
index 0000000..281de3f
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/prefer_compositing_to_lcd_text/compositing/overflow/update-widget-positions-on-nested-frames-and-scrollers-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/text-antialias/sub-pixel/text-scaling-pixel-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/text-antialias/sub-pixel/text-scaling-pixel-expected.png
new file mode 100644
index 0000000..311ca20
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/text-antialias/sub-pixel/text-scaling-pixel-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/threaded/compositing/visibility/visibility-simple-video-layer-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/threaded/compositing/visibility/visibility-simple-video-layer-expected.png
new file mode 100644
index 0000000..2a6e0ff
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/threaded/compositing/visibility/visibility-simple-video-layer-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/threaded/compositing/webgl/webgl-background-color-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/threaded/compositing/webgl/webgl-background-color-expected.png
new file mode 100644
index 0000000..292672c6
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/threaded/compositing/webgl/webgl-background-color-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/threaded/compositing/webgl/webgl-reflection-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/threaded/compositing/webgl/webgl-reflection-expected.png
new file mode 100644
index 0000000..3b97e94e
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/threaded/compositing/webgl/webgl-reflection-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/threaded/compositing/webgl/webgl-repaint-expected.png b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/threaded/compositing/webgl/webgl-repaint-expected.png
new file mode 100644
index 0000000..82c662b
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/virtual/threaded/compositing/webgl/webgl-repaint-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/resources/blocking-main-thread.html b/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/resources/blocking-main-thread.html
new file mode 100644
index 0000000..ed57ed4
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/resources/blocking-main-thread.html
@@ -0,0 +1,25 @@
+<!--
+  Copyright 2020 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.
+-->
+<html>
+<script>
+  const sab = new SharedArrayBuffer(1024);
+  const sharedArray = new Int32Array(sab);
+  const worker = new Worker('blocking-main-thread.js');
+  worker.postMessage(sab);
+
+  function checkWorkerReady() {
+    // Wait for the worker to start execution.
+    if (sharedArray[0] === 1) {
+      // Enter blocking loop, and wait for an update before exiting.
+      while (sharedArray[2] != 1) Atomics.notify(sharedArray, 1, 1);
+    } else {
+      setTimeout(checkWorkerReady, 0);
+    }
+  }
+  // Let worker start execution.
+  setTimeout(checkWorkerReady, 0);
+</script>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/resources/blocking-main-thread.js b/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/resources/blocking-main-thread.js
new file mode 100644
index 0000000..eaa9179
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/resources/blocking-main-thread.js
@@ -0,0 +1,14 @@
+// Copyright 2020 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.
+
+self.addEventListener('message', (m) => {
+  const sharedArray = new Int32Array(m.data);
+  var i = 0;
+  // Notify main thread that execution started.
+  Atomics.store(sharedArray, i++, 1);
+  // Wait for the main thread to enter the blocking loop.
+  Atomics.wait(sharedArray, i++, 1);
+  // Make the update for the main thread to exit the loop.
+  Atomics.store(sharedArray, i, 1);
+});
\ No newline at end of file
diff --git a/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/set-breakpoint-while-blocking-main-thread-expected.txt b/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/set-breakpoint-while-blocking-main-thread-expected.txt
new file mode 100644
index 0000000..a5d11b34
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/set-breakpoint-while-blocking-main-thread-expected.txt
@@ -0,0 +1,14 @@
+Tests setting breakpoint when main thread blocks.
+
+
+Running: testSetBreakpoint
+Breakpoint sidebar pane 
+blocking-main-thread.js:13checked  Atomics.store(sharedArray, i, 1);
+Reloading page.
+Page reloaded.
+Script execution paused.
+Breakpoint sidebar pane 
+blocking-main-thread.html:11checked  worker.postMessage(sab);
+blocking-main-thread.js:13checked breakpoint hit  Atomics.store(sharedArray, i, 1);
+Script execution resumed.
+
diff --git a/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/set-breakpoint-while-blocking-main-thread.js b/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/set-breakpoint-while-blocking-main-thread.js
new file mode 100644
index 0000000..9518e73
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/devtools/sources/debugger-breakpoints/set-breakpoint-while-blocking-main-thread.js
@@ -0,0 +1,40 @@
+// Copyright 2020 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.
+
+(async function() {
+  TestRunner.addResult(`Tests setting breakpoint when main thread blocks.\n`);
+  await TestRunner.loadModule('sources_test_runner');
+  await TestRunner.showPanel('sources');
+  await TestRunner.navigatePromise('resources/blocking-main-thread.html');
+
+  SourcesTestRunner.runDebuggerTestSuite([
+    async function testSetBreakpoint(next) {
+
+      // The debugger plugin needs to be retrieved before pausing, otherwise we
+      // cannot set a breakpoint on the main thread during pause.
+      var mainThreadSource = await SourcesTestRunner.showScriptSourcePromise(
+          'blocking-main-thread.html');
+      const plugin = SourcesTestRunner.debuggerPlugin(mainThreadSource);
+
+      SourcesTestRunner.showScriptSource(
+          'blocking-main-thread.js', didShowWorkerSource);
+
+      async function didShowWorkerSource(sourceFrame) {
+        await SourcesTestRunner.createNewBreakpoint(sourceFrame, 12, '', true);
+        await SourcesTestRunner.waitBreakpointSidebarPane();
+        SourcesTestRunner.dumpBreakpointSidebarPane();
+        SourcesTestRunner.waitUntilPaused(paused);
+        TestRunner.addResult('Reloading page.');
+        TestRunner.reloadPage();
+      }
+
+      async function paused() {
+        plugin._createNewBreakpoint(10, '', true);
+        await SourcesTestRunner.waitBreakpointSidebarPane();
+        SourcesTestRunner.dumpBreakpointSidebarPane();
+        next();
+      }
+    }
+  ]);
+})();
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/page/navigated-heavy-ad-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/page/navigated-heavy-ad-expected.txt
new file mode 100644
index 0000000..c15266b
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/page/navigated-heavy-ad-expected.txt
@@ -0,0 +1,6 @@
+Tests that the ad frame type is reported on navigation
+
+{
+    adFrameType : root
+}
+
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/page/navigated-heavy-ad.js b/third_party/blink/web_tests/http/tests/inspector-protocol/page/navigated-heavy-ad.js
new file mode 100644
index 0000000..1a87b9a
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/page/navigated-heavy-ad.js
@@ -0,0 +1,27 @@
+(async function (testRunner) {
+  const { page, session, dp } = await testRunner.startBlank(
+    `Tests that the ad frame type is reported on navigation\n`);
+  await dp.Page.enable();
+  session.evaluate(`
+    if (window.testRunner) {
+      // Inject a subresource filter to mark 'ad-iframe-writer.js' as a would be disallowed resource.
+      testRunner.setDisallowedSubresourcePathSuffixes(["ad-iframe-writer.js"], false /* block_subresources */);
+      testRunner.setHighlightAds();
+    }
+
+    // Script must be loaded after disallowed paths are set to be marked as an ad.
+    let ad_script = document.createElement("script");
+    ad_script.async = false;
+    ad_script.src = "../resources/ad-iframe-writer.js";
+    ad_script.onload = function () {
+      ad_frame = createAdFrame();
+      ad_frame.width = 100;
+      ad_frame.height = 200;
+    };
+    document.body.appendChild(ad_script);
+  `);
+  const { params } = await dp.Page.onceFrameNavigated();
+  testRunner.log({ adFrameType: params.frame.adFrameType });
+
+  testRunner.completeTest();
+})
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/resources/ad-iframe-writer.js b/third_party/blink/web_tests/http/tests/inspector-protocol/resources/ad-iframe-writer.js
new file mode 100644
index 0000000..5b321dd2
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/resources/ad-iframe-writer.js
@@ -0,0 +1,7 @@
+// Creates and iframe and appends it to the body element. Make sure the caller
+// has a body element!
+function createAdFrame() {
+  let ad_frame = document.createElement('iframe');
+  document.body.appendChild(ad_frame);
+  return ad_frame;
+}
diff --git a/third_party/blink/web_tests/http/tests/lazyload/iframe-domparser.html b/third_party/blink/web_tests/http/tests/lazyload/iframe-domparser.html
new file mode 100644
index 0000000..446a2eac
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/lazyload/iframe-domparser.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+  test(() => {
+    const parser = new DOMParser();
+    const parsed_document =
+      parser.parseFromString('<iframe loading=lazy ' +
+                             'src=https://domfarolino.com></iframe>',
+                             'text/html');
+  }, 'Creating a loading=lazy iframe from DOMParser does not crash the renderer');
+</script>
diff --git a/third_party/blink/web_tests/inspector-protocol/css/css-fonts-updated-event-on-startup-expected.txt b/third_party/blink/web_tests/inspector-protocol/css/css-fonts-updated-event-on-startup-expected.txt
new file mode 100644
index 0000000..fab7dcd
--- /dev/null
+++ b/third_party/blink/web_tests/inspector-protocol/css/css-fonts-updated-event-on-startup-expected.txt
@@ -0,0 +1,13 @@
+Verifies that CSS.fontsUpdated events are sent after CSS domain is enabled
+{
+    fontFamily : Noto Mono
+    fontStretch : normal
+    fontStyle : normal
+    fontVariant : normal
+    fontWeight : normal
+    platformFontFamily : ಠ_ಠNoto Monoಠ_ಠ
+    src : <string>
+    unicodeRange : U+0-10FFFE
+}
+SUCCESS: CSS.FontsUpdated events received.
+
diff --git a/third_party/blink/web_tests/inspector-protocol/css/css-fonts-updated-event-on-startup.js b/third_party/blink/web_tests/inspector-protocol/css/css-fonts-updated-event-on-startup.js
new file mode 100644
index 0000000..bafc2b0
--- /dev/null
+++ b/third_party/blink/web_tests/inspector-protocol/css/css-fonts-updated-event-on-startup.js
@@ -0,0 +1,17 @@
+(async function(testRunner) {
+  const {session, dp} = await testRunner.startHTML(
+    `<link rel="stylesheet" href="${testRunner.url('./resources/noto-mono.css')}">
+    some text`,
+    'Verifies that CSS.fontsUpdated events are sent after CSS domain is enabled'
+  );
+  const eventPromise = dp.CSS.onceFontsUpdated(
+    event => typeof event.params.font !== 'undefined' &&
+    event.params.font.fontFamily === 'Noto Mono');
+  await dp.DOM.enable();
+  await dp.CSS.enable();
+  const event = await eventPromise;
+  const font = event.params.font;
+  testRunner.log(font, null, ['src'])
+  testRunner.log('SUCCESS: CSS.FontsUpdated events received.');
+  testRunner.completeTest();
+});
diff --git a/third_party/blink/web_tests/paint/invalidation/scroll/overflow-scroll-in-overflow-scroll-scrolled-expected.html b/third_party/blink/web_tests/paint/invalidation/scroll/overflow-scroll-in-overflow-scroll-scrolled-expected.html
new file mode 100644
index 0000000..afba406
--- /dev/null
+++ b/third_party/blink/web_tests/paint/invalidation/scroll/overflow-scroll-in-overflow-scroll-scrolled-expected.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script>
+onload = () => {
+    outerDiv.scrollTop = 300;
+    innerDiv.scrollTop = 400;
+}
+</script>
+<div style="height: 300px; overflow-y: scroll;" id="outerDiv">
+    <div style="height: 300px;"></div>
+    <div style="height: 400px; overflow-y: scroll;" id="innerDiv">
+        <div style="height: 800px; width: 300px; background: green"></div>
+    </div>
+</div>
diff --git a/third_party/blink/web_tests/paint/invalidation/scroll/overflow-scroll-in-overflow-scroll-scrolled.html b/third_party/blink/web_tests/paint/invalidation/scroll/overflow-scroll-in-overflow-scroll-scrolled.html
index 2ea5371..46b6b6b 100644
--- a/third_party/blink/web_tests/paint/invalidation/scroll/overflow-scroll-in-overflow-scroll-scrolled.html
+++ b/third_party/blink/web_tests/paint/invalidation/scroll/overflow-scroll-in-overflow-scroll-scrolled.html
@@ -1,18 +1,12 @@
 <!DOCTYPE html>
-<html>
-<head>
-    <script src="../resources/text-based-repaint.js" type="text/javascript"></script>
-    <script>
-    function repaintTest() {
-        // Now scroll once in the #innerDiv to the green area.
-        if (window.eventSender)
-            eventSender.mouseScrollBy(0, -10);
-    }
-    window.addEventListener("load", runRepaintAndPixelTest, false);
-    </script>
-</head>
-<body>
-<!-- Bug 71550 - REGRESSION (r93614): Content remains despite parent element being scrolled off page using javascript. -->
+<script src="../resources/text-based-repaint.js" type="text/javascript"></script>
+<script>
+function repaintTest() {
+    // Now scroll the innerDiv once to the green area.
+    innerDiv.scrollTop = 400;
+}
+window.addEventListener("load", runRepaintAndPixelTest, false);
+</script>
 <!-- For the test to pass you should not see any RED or PURPLE, only green -->
 <div style="height: 300px; overflow-y: scroll;" id="outerDiv">
     <div style="height: 300px; background: purple;"></div>
@@ -22,16 +16,6 @@
     </div>
 </div>
 <script>
-if (window.eventSender) {
-    if (window.internals)
-        internals.settings.setScrollAnimatorEnabled(false);
-
-    // Scroll the #outerDiv until we reach the #innerDiv.
-    eventSender.mouseMoveTo(50, 50);
-    eventSender.mouseScrollBy(0, -8);
-} else {
-    document.write("This test is better run under DumpRenderTree. To manually test it, continuously scroll down on the top-most element. There should be no repaint issue.");
-}
+    // Scroll the outerDiv until we reach the innerDiv.
+    outerDiv.scrollTop = 300;
 </script>
-</body>
-</html>
diff --git a/third_party/blink/web_tests/paint/invalidation/table/table-overflow-scroll-in-overflow-scroll-scrolled-expected.html b/third_party/blink/web_tests/paint/invalidation/table/table-overflow-scroll-in-overflow-scroll-scrolled-expected.html
new file mode 100644
index 0000000..bb6e47b
--- /dev/null
+++ b/third_party/blink/web_tests/paint/invalidation/table/table-overflow-scroll-in-overflow-scroll-scrolled-expected.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<script>
+onload = () => {
+    outerDiv.scrollTop = 300;
+    innerDiv.scrollTop = 404;
+}
+</script>
+<div style="height: 300px; overflow-y: scroll;" id="outerDiv">
+    <div style="height: 300px;"></div>
+    <div style="height: 400px; overflow-y: scroll;" id="innerDiv">
+        <table>
+            <tr><td style="height: 800px; width: 300px; background: green"></td></tr>
+        </table>
+    </div>
+</div>
diff --git a/third_party/blink/web_tests/paint/invalidation/table/table-overflow-scroll-in-overflow-scroll-scrolled.html b/third_party/blink/web_tests/paint/invalidation/table/table-overflow-scroll-in-overflow-scroll-scrolled.html
index 75f0e43d..1d47edf 100644
--- a/third_party/blink/web_tests/paint/invalidation/table/table-overflow-scroll-in-overflow-scroll-scrolled.html
+++ b/third_party/blink/web_tests/paint/invalidation/table/table-overflow-scroll-in-overflow-scroll-scrolled.html
@@ -1,18 +1,12 @@
 <!DOCTYPE html>
-<html>
-<head>
-    <script src="../resources/text-based-repaint.js" type="text/javascript"></script>
-    <script>
-    function repaintTest() {
-        // Now scroll once in the #innerDiv to the green area.
-        if (window.eventSender)
-            eventSender.mouseScrollBy(0, -10);
-    }
-    window.addEventListener("load", runRepaintAndPixelTest, false);
-    </script>
-</head>
-<body>
-<!-- Bug 71550 - REGRESSION (r93614): Content remains despite parent element being scrolled off page using javascript. -->
+<script src="../resources/text-based-repaint.js"></script>
+<script>
+function repaintTest() {
+    // Now scroll the innerDiv once to the green area.
+    innerDiv.scrollTop = 406;
+}
+window.addEventListener("load", runRepaintAndPixelTest, false);
+</script>
 <!-- For the test to pass you should not see any RED or PURPLE, only green -->
 <div style="height: 300px; overflow-y: scroll;" id="outerDiv">
     <div style="height: 300px; background: purple;"></div>
@@ -24,16 +18,6 @@
     </div>
 </div>
 <script>
-if (window.eventSender) {
-    if (window.internals)
-        internals.settings.setScrollAnimatorEnabled(false);
-
-    // Scroll the #outerDiv until we reach the #innerDiv.
-    eventSender.mouseMoveTo(50, 50);
-    eventSender.mouseScrollBy(0, -8);
-} else {
-    document.write("This test is better run under DumpRenderTree. To manually test it, continuously scroll down on the top-most element. There should be no repaint issue.");
-}
+// Scroll the outerDiv until we reach the innerDiv.
+outerDiv.scrollTop = 300;
 </script>
-</body>
-</html>
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-scroll-in-overflow-scroll-scrolled-expected.png b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-scroll-in-overflow-scroll-scrolled-expected.png
deleted file mode 100644
index a16ab6fd..0000000
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-scroll-in-overflow-scroll-scrolled-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/table/table-overflow-scroll-in-overflow-scroll-scrolled-expected.png b/third_party/blink/web_tests/platform/mac/paint/invalidation/table/table-overflow-scroll-in-overflow-scroll-scrolled-expected.png
deleted file mode 100644
index 5e391e1..0000000
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/table/table-overflow-scroll-in-overflow-scroll-scrolled-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/scroll/overflow-scroll-in-overflow-scroll-scrolled-expected.png b/third_party/blink/web_tests/platform/win/paint/invalidation/scroll/overflow-scroll-in-overflow-scroll-scrolled-expected.png
deleted file mode 100644
index decc654..0000000
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/scroll/overflow-scroll-in-overflow-scroll-scrolled-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/table/table-overflow-scroll-in-overflow-scroll-scrolled-expected.png b/third_party/blink/web_tests/platform/win/paint/invalidation/table/table-overflow-scroll-in-overflow-scroll-scrolled-expected.png
deleted file mode 100644
index 355ebbc..0000000
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/table/table-overflow-scroll-in-overflow-scroll-scrolled-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/fast/forms/month/month-picker-appearance-zoom150-expected.png b/third_party/blink/web_tests/platform/win7/fast/forms/month/month-picker-appearance-zoom150-expected.png
index c937ace72..666d858 100644
--- a/third_party/blink/web_tests/platform/win7/fast/forms/month/month-picker-appearance-zoom150-expected.png
+++ b/third_party/blink/web_tests/platform/win7/fast/forms/month/month-picker-appearance-zoom150-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/fast/forms/select/listbox-appearance-basic-expected.png b/third_party/blink/web_tests/platform/win7/fast/forms/select/listbox-appearance-basic-expected.png
deleted file mode 100644
index e207a379..0000000
--- a/third_party/blink/web_tests/platform/win7/fast/forms/select/listbox-appearance-basic-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/fast/forms/select/select-disabled-appearance-expected.png b/third_party/blink/web_tests/platform/win7/fast/forms/select/select-disabled-appearance-expected.png
deleted file mode 100644
index 81e4fc4..0000000
--- a/third_party/blink/web_tests/platform/win7/fast/forms/select/select-disabled-appearance-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/layout-ng-grid/external/wpt/css/css-grid/grid-definition/grid-auto-explicit-rows-001-expected.txt b/third_party/blink/web_tests/virtual/layout-ng-grid/external/wpt/css/css-grid/grid-definition/grid-auto-explicit-rows-001-expected.txt
new file mode 100644
index 0000000..78d85608
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/layout-ng-grid/external/wpt/css/css-grid/grid-definition/grid-auto-explicit-rows-001-expected.txt
@@ -0,0 +1,11 @@
+This is a testharness.js-based test.
+FAIL .grid 1 assert_equals: 
+<div class="grid">
+ <div id="c1" data-expected-width="23" data-expected-height="11"></div>
+ <div id="c2" data-expected-width="52" data-expected-height="24"></div>
+ <div id="c3" data-expected-width="83" data-expected-height="41"></div>
+ <div id="c4" data-expected-width="120" data-expected-height="60"></div>
+</div>
+width expected 23 but got 784
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/no-auto-wpt-origin-isolation/external/wpt/origin-isolation/getter-data-url.https-expected.txt b/third_party/blink/web_tests/virtual/no-auto-wpt-origin-isolation/external/wpt/origin-isolation/getter-special-cases/data-url.https-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/virtual/no-auto-wpt-origin-isolation/external/wpt/origin-isolation/getter-data-url.https-expected.txt
rename to third_party/blink/web_tests/virtual/no-auto-wpt-origin-isolation/external/wpt/origin-isolation/getter-special-cases/data-url.https-expected.txt
diff --git a/third_party/blink/web_tests/virtual/no-auto-wpt-origin-isolation/external/wpt/origin-isolation/getter-sandboxed-iframe.sub.https-expected.txt b/third_party/blink/web_tests/virtual/no-auto-wpt-origin-isolation/external/wpt/origin-isolation/getter-special-cases/sandboxed-iframe.sub.https-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/virtual/no-auto-wpt-origin-isolation/external/wpt/origin-isolation/getter-sandboxed-iframe.sub.https-expected.txt
rename to third_party/blink/web_tests/virtual/no-auto-wpt-origin-isolation/external/wpt/origin-isolation/getter-special-cases/sandboxed-iframe.sub.https-expected.txt
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
index eda2433..c89dcd3 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
@@ -9248,6 +9248,7 @@
 interface XRView
     attribute @@toStringTag
     getter eye
+    getter isFirstPersonObserver
     getter projectionMatrix
     getter transform
     method constructor
diff --git a/third_party/blink/web_tests/webcodecs/videoframe-imagebitmap.html b/third_party/blink/web_tests/webcodecs/videoframe-imagebitmap.html
index 2241788..e60c380 100644
--- a/third_party/blink/web_tests/webcodecs/videoframe-imagebitmap.html
+++ b/third_party/blink/web_tests/webcodecs/videoframe-imagebitmap.html
@@ -3,49 +3,84 @@
 <script src="../resources/testharnessreport.js"></script>
 <script>
   'use strict';
-  function testCanvas_0f0(ctx, width, height, assert_compares) {
+  // Reference values generated by:
+  // https://fiddle.skia.org/c/f100d4d5f085a9e09896aabcbc463868
+
+  const kSRGBPixel = [50, 100, 150, 255];
+  const kP3Pixel = [62, 99, 146, 255];
+  const kRec2020Pixel = [87, 106, 151, 255];
+
+  const kCanvasOptionsP3Uint8 = { colorSpace: 'p3', pixelFormat: 'uint8' };
+  const kCanvasOptionsRec2020Uint8 =
+    { colorSpace: 'rec2020', pixelFormat: 'uint8' };
+
+  function testCanvas(ctx, width, height, expected_pixel, assert_compares) {
+    // The dup getImageData is to workaournd crbug.com/1100233
+    let imageData = ctx.getImageData(0, 0, width, height);
     let colorData = ctx.getImageData(0, 0, width, height).data;
-    for (let i = 0; i < width * height; i += 4) {
-      assert_compares(colorData[0], 0);
-      assert_compares(colorData[1], 255);
-      assert_compares(colorData[2], 0);
-      assert_compares(colorData[3], 255);
+    const kMaxPixelToCheck = 128 * 96;
+    let step = width * height / kMaxPixelToCheck;
+    step = Math.round(step);
+    step = (step < 1) ? 1 : step;
+    for (let i = 0; i < 4 * width * height; i += (4 * step)) {
+      assert_compares(colorData[i], expected_pixel[0]);
+      assert_compares(colorData[i + 1], expected_pixel[1]);
+      assert_compares(colorData[i + 2], expected_pixel[2]);
+      assert_compares(colorData[i + 3], expected_pixel[3]);
     }
   }
 
-  function testImageBitmapToAndFromVideoFrame(width, height) {
+  function testImageBitmapToAndFromVideoFrame(width, height, expectedPixel,
+    canvasOptions, imageBitmapOptions) {
     let canvas = document.createElement('canvas');
     canvas.width = width;
     canvas.height = height;
-    let ctx = canvas.getContext('2d');
-    ctx.fillStyle = '#0f0';
+    let ctx = canvas.getContext('2d', canvasOptions);
+    ctx.fillStyle = 'rgb(50, 100, 150)';
     ctx.fillRect(0, 0, width, height);
-    testCanvas_0f0(ctx, width, height, assert_equals);
+    testCanvas(ctx, width, height, expectedPixel, assert_equals);
 
-    return createImageBitmap(canvas)
-    .then((fromImageBitmap) => {
-      let videoFrame = new VideoFrame({ timestamp: 0 }, fromImageBitmap);
-      return videoFrame.createImageBitmap();
-    })
-    .then((toImageBitmap) => {
+    return createImageBitmap(canvas, imageBitmapOptions)
+      .then((fromImageBitmap) => {
+        let videoFrame = new VideoFrame({ timestamp: 0 }, fromImageBitmap);
+        return videoFrame.createImageBitmap(imageBitmapOptions);
+      })
+      .then((toImageBitmap) => {
         let myCanvas = document.createElement('canvas');
         myCanvas.width = width;
         myCanvas.height = height;
-        let myCtx = myCanvas.getContext('2d');
+        let myCtx = myCanvas.getContext('2d', canvasOptions);
         myCtx.drawImage(toImageBitmap, 0, 0);
         let tolerance = 2;
-        testCanvas_0f0(myCtx, width, height, (actual, expected) => {
+        testCanvas(myCtx, width, height, expectedPixel, (actual, expected) => {
           assert_approx_equals(actual, expected, tolerance);
         });
-    });
+      });
   }
 
   promise_test(() => {
-    return testImageBitmapToAndFromVideoFrame(320, 240);
-  }, 'ImageBitmap<->VideoFrame with canvas size 320x240.');
+    return testImageBitmapToAndFromVideoFrame(48, 36, kSRGBPixel);
+  }, 'ImageBitmap<->VideoFrame with canvas(48x36 srgb uint8).');
+  promise_test(() => {
+    return testImageBitmapToAndFromVideoFrame(480, 360, kSRGBPixel);
+  }, 'ImageBitmap<->VideoFrame with canvas(480x360 srgb uint8).');
 
   promise_test(() => {
-    return testImageBitmapToAndFromVideoFrame(640, 480);
-  }, 'ImageBitmap<->VideoFrame with canvas size 640x480.');
+    return testImageBitmapToAndFromVideoFrame(48, 36, kP3Pixel,
+      kCanvasOptionsP3Uint8, { colorSpaceConversion: "none" });
+  }, 'ImageBitmap<->VideoFrame with canvas(48x36 p3 uint8).');
+  promise_test(() => {
+    return testImageBitmapToAndFromVideoFrame(480, 360, kP3Pixel,
+      kCanvasOptionsP3Uint8, { colorSpaceConversion: "none" });
+  }, 'ImageBitmap<->VideoFrame with canvas(480x360 p3 uint8).');
+
+  promise_test(() => {
+    return testImageBitmapToAndFromVideoFrame(48, 36, kRec2020Pixel,
+      kCanvasOptionsRec2020Uint8, { colorSpaceConversion: "none" });
+  }, 'ImageBitmap<->VideoFrame with canvas(48x36 rec2020 uint8).');
+  promise_test(() => {
+    return testImageBitmapToAndFromVideoFrame(480, 360, kRec2020Pixel,
+      kCanvasOptionsRec2020Uint8, { colorSpaceConversion: "none" });
+  }, 'ImageBitmap<->VideoFrame with canvas(480x360 rec2020 uint8).');
 
 </script>
diff --git a/third_party/blink/web_tests/webexposed/README.txt b/third_party/blink/web_tests/webexposed/README.txt
index 34a7b80..e9a9c42 100644
--- a/third_party/blink/web_tests/webexposed/README.txt
+++ b/third_party/blink/web_tests/webexposed/README.txt
@@ -1,6 +1,58 @@
-DumpRenderTree runs with a default set of runtime flags enabled.
-Thus the results in this directory serve to document Blink's
-complete API as visible during testing.
+The results in this directory serve to document Blink's complete API
+as "visible" (exposed to scripts) during testing. These tests are run
+with a default set of runtime flags enabled, including experimental
+features and test flags.
 
-virtual/stable/webexposed/ runs the same tests without the experimental and test
-flags enabled. It is testing what is exposed to the stable channel.
+web_tests/virtual/stable/webexposed/ runs the same tests without the
+experimental and test flags enabled. It is testing what is exposed to
+the stable channel. Changes there may require approval from blink API
+OWNERS.
+
+## Global Interface Listing Tests
+
+Additions to Blink IDL files with new interfaces, methods and
+attributes should result in changes to the expectations here. Such
+changes should normally be made behind experimental flags (e.g.
+[RuntimeEnabled=flag] in the IDL), and should not change the
+virtual/stable expectations. To learn more about runtime flags, see:
+third_party/blink/renderer/platform/RuntimeEnabledFeatures.md
+
+To learn more about Blink IDL files, start with:
+third_party/blink/renderer/bindings/README.md
+
+NOTE: Changes to the inheritance of an existing interface (e.g.
+changing an interface to derive from EventTarget) can't be guarded by
+runtime flags, and will introduce virtual/stable changes.
+
+The tests enumerate global interfaces, methods, getters/setters (how
+most IDL attributes are exposed) and attributes (other IDL
+attributes). They do not assert types or parameters. Individual
+features typically have idlharness.js-based tests that live in
+web_tests/external/wpt which more thoroughly exercise each API. See:
+https://web-platform-tests.org/writing-tests/idlharness.html
+
+
+The test files live in web_tests/webexposed:
+
+* global-interface-listing.html - Window context
+* global-interface-listing-dedicated-worker.html - Dedicated Worker context
+* global-interface-listing-shared-worker.html - Shared Worker context
+
+But also in web_tests/http/tests/serviceworker/webexposed:
+
+* global-interface-listing-service-worker.html - Service Worker context
+
+And also in web_tests/http/tests/worklet/webexposed:
+
+* global-interface-listing-paint-worklet.html - Worklet contexts
+
+Also in web_tests/web_exposed:
+
+* global-interface-listing-platform-specific.html - Tests the
+  interfaces listed in the platformSpecificInterfaces array in
+  web_tests/resources/global-interface-listing.js. The results appear
+  in:
+  * platform/win/virtual/stable/webexposed/global-interface-listing-platform-specific-expected.txt
+  * platform/mac/virtual/stable/webexposed/global-interface-listing-platform-specific-expected.txt
+  * platform/linux/virtual/stable/webexposed/global-interface-listing-platform-specific-expected.txt
+  * platform/win7/virtual/stable/webexposed/global-interface-listing-platform-specific-expected.txt
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 287a1ca..313de99 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
@@ -11501,6 +11501,7 @@
 interface XRView
     attribute @@toStringTag
     getter eye
+    getter isFirstPersonObserver
     getter projectionMatrix
     getter transform
     method constructor
diff --git a/third_party/blink/web_tests/webgpu/roll_webgpu_cts.sh b/third_party/blink/web_tests/webgpu/roll_webgpu_cts.sh
index 5f2f91d..9b3ddf86 100755
--- a/third_party/blink/web_tests/webgpu/roll_webgpu_cts.sh
+++ b/third_party/blink/web_tests/webgpu/roll_webgpu_cts.sh
@@ -10,7 +10,7 @@
 # It does the following for each branch:
 #   - Updates Chromium's DEPS to the latest origin/{master,glsl-dependent}.
 #   - Runs gclient sync.
-#   - Builds the CTS (requires a local installation of node/npm + yarn).
+#   - Builds the CTS (requires a local installation of node/npm).
 #   - Copies the built out-wpt/ directory into
 #     {external/wpt,wpt_internal}/webgpu/.
 #   - Adds {external/wpt,wpt_internal}/webgpu/ to the git index
@@ -50,7 +50,7 @@
 
   pushd third_party/webgpu-cts/src > /dev/null
 
-    yarn install --frozen-lockfile
+    npm install --frozen-lockfile
     npx grunt wpt  # build third_party/webgpu-cts/src/out-wpt/
 
   popd > /dev/null
diff --git a/third_party/closure_compiler/externs/networking_private.js b/third_party/closure_compiler/externs/networking_private.js
index 864b43c..349618e 100644
--- a/third_party/closure_compiler/externs/networking_private.js
+++ b/third_party/closure_compiler/externs/networking_private.js
@@ -1184,7 +1184,7 @@
 chrome.networkingPrivate.verifyAndEncryptData = function(properties, data, callback) {};
 
 /**
- * Enables TDLS for WiFi traffic with a specified peer if available.
+ * Deprecated. Enables TDLS for WiFi traffic with a specified peer if available.
  * @param {string} ip_or_mac_address The IP or MAC address of the peer with
  *     which to     enable a TDLS connection. |enabled| If true, enable TDLS,
  *     otherwise disable TDLS.
@@ -1194,17 +1194,17 @@
  *     that the request failed     (e.g. MAC address lookup failed). 'Timeout'
  *     indicates that the lookup     timed out. Otherwise a valid status is
  *     returned (see     $(ref:getWifiTDLSStatus)).
- * @deprecated Use networking.castPrivate API.
+ * @deprecated True
  */
 chrome.networkingPrivate.setWifiTDLSEnabledState = function(ip_or_mac_address, enabled, callback) {};
 
 /**
- * Returns the current TDLS status for the specified peer.
+ * Deprecated. Returns the current TDLS status for the specified peer.
  * @param {string} ip_or_mac_address The IP or MAC address of the peer.
  * @param {function(string): void} callback A callback function that receives a
  *     string with the current     TDLS status which can be 'Connected',
  *     'Disabled', 'Disconnected',     'Nonexistent', or 'Unknown'.
- * @deprecated Use networking.castPrivate API.
+ * @deprecated True
  */
 chrome.networkingPrivate.getWifiTDLSStatus = function(ip_or_mac_address, callback) {};
 
diff --git a/third_party/freetype/README.chromium b/third_party/freetype/README.chromium
index 85d3563..50a7e4a 100644
--- a/third_party/freetype/README.chromium
+++ b/third_party/freetype/README.chromium
@@ -1,7 +1,7 @@
 Name: FreeType
 URL: http://www.freetype.org/
-Version: VER-2-10-2-37-gc922ffa5d
-Revision: c922ffa5d2fe359d5e0d788f3a0850a59da4ae20
+Version: VER-2-10-2-39-g5fe7c044c
+Revision: 5fe7c044c25bba9dfae315ef56bacfc83976ddd0
 CPEPrefix: cpe:/a:freetype:freetype:2.10.1
 License: Custom license "inspired by the BSD, Artistic, and IJG (Independent
          JPEG Group) licenses"
diff --git a/third_party/nearby/BUILD.gn b/third_party/nearby/BUILD.gn
index 4b552a5..4b30d8c 100644
--- a/third_party/nearby/BUILD.gn
+++ b/third_party/nearby/BUILD.gn
@@ -312,7 +312,7 @@
     "src/cpp/core_v2/internal/wifi_lan_service_info.cc",
   ]
   public = [
-    "src/cpp/core/internal/mediums/message_lite.h",
+    "src/cpp/core/internal/message_lite.h",
     "src/cpp/core_v2/internal/base_endpoint_channel.h",
     "src/cpp/core_v2/internal/base_pcp_handler.h",
     "src/cpp/core_v2/internal/ble_advertisement.h",
diff --git a/third_party/wayland-protocols/unstable/remote-shell/remote-shell-unstable-v1.xml b/third_party/wayland-protocols/unstable/remote-shell/remote-shell-unstable-v1.xml
index c52d288f..f8007bd 100644
--- a/third_party/wayland-protocols/unstable/remote-shell/remote-shell-unstable-v1.xml
+++ b/third_party/wayland-protocols/unstable/remote-shell/remote-shell-unstable-v1.xml
@@ -38,7 +38,7 @@
     reset.
   </description>
 
-  <interface name="zcr_remote_shell_v1" version="28">
+  <interface name="zcr_remote_shell_v1" version="29">
     <description summary="remote_shell">
       The global interface that allows clients to turn a wl_surface into a
       "real window" which is remotely managed but can be stacked, activated
@@ -245,6 +245,35 @@
       <arg name="id" type="new_id" interface="zcr_toast_surface_v1"/>
       <arg name="surface" type="object" interface="wl_surface"/>
     </request>
+
+    <!-- Version 29 additions -->
+
+    <event name="layout_mode" since="29">
+      <description summary="sends the layout_mode">
+	Sends the layout_mode used by the server.
+      </description>
+      <arg name="layout_mode" type="int" summary="layout_mode enum"/>
+    </event>
+
+    <request name="get_remote_output" since="29">
+      <description summary="extend output interface for remote shell">
+	Instantiate an interface extension for the given wl_output to
+	provide remote shell functionality.
+      </description>
+      <arg name="id" type="new_id" interface="zcr_remote_output_v1" summary="the new remote output interface id"/>
+      <arg name="output" type="object" interface="wl_output" summary="the output"/>
+    </request>
+
+    <request name="set_use_default_device_scale_cancellation" since="29">
+      <description summary="set use default device scale cancellation">
+	Request the compositor to use the default_device_scale_factor to undo any
+	scaling applied to the client's buffers. When this is disabled, the
+	compositor will use the device_scale_factor of the display of the buffer to
+	cancel any buffer scaling.
+      </description>
+      <arg name="use_default_device_scale_factor" type="int" summary="0 if false"/>
+    </request>
+
   </interface>
 
   <interface name="zcr_remote_surface_v1" version="27">
@@ -1193,4 +1222,69 @@
     </request>
   </interface>
 
+  <interface name="zcr_remote_output_v1" version="29">
+    <description summary="remote shell interface to a wl_output">
+	An additional interface to a wl_output object, which allows the
+	client to access additional functionality for output.
+    </description>
+
+    <request name="destroy" type="destructor" since="29">
+      <description summary="destroy remote_output">
+	Destroy this remote_output object.
+      </description>
+    </request>
+
+    <event name="display_id" since="29">
+      <description summary="the identifier for the display">
+	Sends the display identifier used by the server for the display.
+      </description>
+      <arg name="display_id_hi" type="uint"/>
+      <arg name="display_id_lo" type="uint"/>
+    </event>
+
+    <event name="port" since="29">
+      <description summary="the port of the display">
+	Sends the port to which the display is connected for the server.
+      </description>
+      <arg name="port" type="uint"/>
+    </event>
+
+    <event name="identification_data" since="29">
+      <description summary="the identification data for the display">
+	Sends the identification data for the display, typically in the EDID format.
+      </description>
+      <arg name="identification_data" type="array"/>
+    </event>
+
+    <event name="insets" since="29">
+      <description summary="insets for the display in pixels">
+	Sends inset information about a particular display in the display's native coordinates.
+      </description>
+      <arg name="inset_left" type="int"/>
+      <arg name="inset_top" type="int"/>
+      <arg name="inset_right" type="int"/>
+      <arg name="inset_bottom" type="int"/>
+    </event>
+
+    <event name="stable_insets" since="29">
+      <description summary="stable insets for a display in pixels">
+	Sends stable inset information about a particular display in the display's native
+	coordinates.
+      </description>
+      <arg name="stable_inset_left" type="int"/>
+      <arg name="stable_inset_top" type="int"/>
+      <arg name="stable_inset_right" type="int"/>
+      <arg name="stable_inset_bottom" type="int"/>
+    </event>
+
+    <event name="systemui_visibility" since="29">
+      <description summary="systemui_visibility_state for a display">
+	Sends information about whether the systemui is visible. The "systemui_visibility"
+	value is of enum type "systemui_visibility_state".
+      </description>
+      <arg name="systemui_visibility" type="int" summary="systemui_visibility_state enum"/>
+    </event>
+
+ </interface>
+
 </protocol>
diff --git a/tools/android/checkxmlstyle/checkxmlstyle.py b/tools/android/checkxmlstyle/checkxmlstyle.py
index 6f18145..5db642a 100644
--- a/tools/android/checkxmlstyle/checkxmlstyle.py
+++ b/tools/android/checkxmlstyle/checkxmlstyle.py
@@ -33,10 +33,9 @@
   return _CommonChecks(input_api, output_api)
 
 
-def IncludedFiles(input_api):
+def IncludedFiles(input_api, allow_list=helpers.INCLUDED_PATHS):
   # Filter out XML files outside included paths and files that were deleted.
-  files = lambda f: input_api.FilterSourceFile(
-      f, white_list=helpers.INCLUDED_PATHS)
+  files = lambda f: input_api.FilterSourceFile(f, allow_list)
   return input_api.AffectedFiles(include_deletes=False, file_filter=files)
 
 
@@ -52,6 +51,7 @@
   result.extend(_CheckTextAppearance(input_api, output_api))
   result.extend(_CheckLineSpacingAttribute(input_api, output_api))
   result.extend(_CheckButtonCompatWidgetUsage(input_api, output_api))
+  result.extend(_CheckStringResourcePunctuations(input_api, output_api))
   # Add more checks here
   return result
 
@@ -468,6 +468,63 @@
 
   return []
 
+
+### String resource check ###
+def _CheckStringResourcePunctuations(input_api, output_api):
+  """Check whether inappropriate punctuations are used"""
+  warnings = []
+  result = []
+  # Removing placeholders for parsing purpose:
+  # placeholders will be parsed as children of the parent node.
+  ph = re.compile(r'<ph>.*</ph>')
+  quote_re = re.compile(u'[\u0022\u0027\u0060\u00B4]')
+  for f in IncludedFiles(input_api, helpers.INCLUDED_GRD_PATHS):
+    contents = input_api.ReadFile(f)
+
+    contents = re.sub(ph, '', contents)
+    tree = ET.fromstring(contents)
+
+    # some grds don't contain release and messages tags
+    if tree.find('release') is None:
+      continue
+    if tree.find('release').find('messages') is None:
+      continue
+
+    messages = tree.find('release').find('messages')
+
+    quotes = set()
+    for child in messages:
+      lines = child.text.split('\n')
+      quotes.update(l for l in lines if quote_re.search(l))
+
+    # Only report the lines in the changed contents of the current workspace
+    for line_number, line in f.ChangedContents():
+      lineWithoutPh = re.sub(ph, '', line)
+      if lineWithoutPh in quotes:
+        warnings.append('  %s:%d\n    \t%s' %
+                        (f.LocalPath(), line_number, line))
+
+  if warnings:
+    result += [
+        output_api.PresubmitPromptWarning(
+            u'''
+  Android String Resources Check failed:
+    Your new string is using one or more of generic quotes(\u0022 \\u0022, \u0027 \\u0027,
+    \u0060 \\u0060, \u00B4 \\u00B4), which is not encouraged. Instead, quotations marks
+    (\u201C \\u201C, \u201D \\u201D, \u2018 \\u2018, \u2019 \\u2019) are usually preferred.
+
+    Use prime (\u2032 \\u2032) only in abbreviations for feet, arcminutes, and minutes.
+    Use Double-prime (\u2033 \\u2033) only in abbreviations for inches, arcminutes, and minutes.
+
+    Please reach out to the UX designer/writer in your team to double check
+    which punctuation should be correctly used. Ignore this warning if UX has confirmed.
+
+    Reach out to writing-strings@chromium.org if you have any question about writing strings.
+  '''.encode('utf-8'), warnings)
+    ]
+  return result
+
+
 ### helpers ###
 def _colorXml2Dict(content):
   dct = dict()
diff --git a/tools/android/checkxmlstyle/checkxmlstyle_test.py b/tools/android/checkxmlstyle/checkxmlstyle_test.py
index 509d13f9..4a72438 100755
--- a/tools/android/checkxmlstyle/checkxmlstyle_test.py
+++ b/tools/android/checkxmlstyle/checkxmlstyle_test.py
@@ -434,5 +434,47 @@
                      result[0].items[1].splitlines()[0])
 
 
+class StringResourcesTest(unittest.TestCase):
+  def testInfavoredQuotations(self):
+    xmlChanges = (u'''<grit><release><messages>
+      <message name="IDS_TEST_0">
+          <ph><ex>Hi</ex></ph>, it\u0027s a good idea
+      </message>
+      <message name="IDS_TEST_1">
+          <ph><ex>Yes</ex></ph>, it\u2019s a good idea
+      </message>
+      <message name="IDS_TEST_2">
+        Go to \u0022Settings\u0022 and
+        \u0022Menus\u0022
+      </message>
+      <message name="IDS_TEST_3">
+        Go to \u201CSettings\u201D
+        \u0022Menus\u0023
+      </message>
+      <message name="IDS_TEST_4">
+        Go to \u201CSettings\u201D
+        \u201CMenus\u201D
+      </message>
+          </messages></release></grit>'''.encode('utf-8')).splitlines()
+
+    mock_input_api = MockInputApi()
+    mock_input_api.files = [
+        MockFile('ui/android/string/chrome_android_string.grd', xmlChanges)
+    ]
+    result = checkxmlstyle._CheckStringResourcePunctuations(
+        mock_input_api, MockOutputApi())
+
+    self.assertEqual(1, len(result))
+    self.assertEqual(4, len(result[0].items))
+    self.assertEqual('  ui/android/string/chrome_android_string.grd:3',
+                     result[0].items[0].splitlines()[0])
+    self.assertEqual('  ui/android/string/chrome_android_string.grd:9',
+                     result[0].items[1].splitlines()[0])
+    self.assertEqual('  ui/android/string/chrome_android_string.grd:10',
+                     result[0].items[2].splitlines()[0])
+    self.assertEqual('  ui/android/string/chrome_android_string.grd:14',
+                     result[0].items[3].splitlines()[0])
+
+
 if __name__ == '__main__':
   unittest.main()
diff --git a/tools/android/checkxmlstyle/helpers.py b/tools/android/checkxmlstyle/helpers.py
index c0c54dd2..9617fd2 100644
--- a/tools/android/checkxmlstyle/helpers.py
+++ b/tools/android/checkxmlstyle/helpers.py
@@ -23,6 +23,9 @@
 INCLUDED_PATHS = [
     r'^(chrome|ui|components|content)[\\/](.*[\\/])?java[\\/]res.+\.xml$'
 ]
+INCLUDED_GRD_PATHS = [
+    r'^(chrome|ui|components|content)[\\/](.*[\\/])?android[\\/](.*)\.grd$'
+]
 # TODO(lazzzis): check color references in java source files
 COLOR_REFERENCE_PATTERN = re.compile('''
     @color/   # starts with '@color'
diff --git a/tools/android/dependency_analysis/OWNERS b/tools/android/dependency_analysis/OWNERS
index f7f92ce..8d9cb5cc 100644
--- a/tools/android/dependency_analysis/OWNERS
+++ b/tools/android/dependency_analysis/OWNERS
@@ -1,3 +1,6 @@
-yjlong@google.com
 hnakashima@chromium.org
+mheikal@chromium.org
 
+# TEAM: clank-modularization@chromium.org
+# COMPONENT: Infra>CodeAnalysis
+# OS: Android
diff --git a/tools/android/dependency_analysis/js/OWNERS b/tools/android/dependency_analysis/js/OWNERS
new file mode 100644
index 0000000..c4fad82
--- /dev/null
+++ b/tools/android/dependency_analysis/js/OWNERS
@@ -0,0 +1,6 @@
+hnakashima@chromium.org
+huangs@chromium.org
+
+# TEAM: clank-modularization@chromium.org
+# COMPONENT: Infra>CodeAnalysis
+# OS: Android
diff --git a/tools/git/move_source_file.py b/tools/git/move_source_file.py
index 5b58b0ca..90ade78a 100755
--- a/tools/git/move_source_file.py
+++ b/tools/git/move_source_file.py
@@ -34,7 +34,7 @@
 import sort_sources
 
 
-HANDLED_EXTENSIONS = ['.cc', '.mm', '.h', '.hh', '.cpp']
+HANDLED_EXTENSIONS = ['.cc', '.mm', '.h', '.hh', '.cpp', '.mojom']
 
 
 def IsHandledFile(path):
@@ -87,6 +87,35 @@
     raise Exception('Fatal: Failed to run git mv command.')
 
 
+def UpdateIncludes(from_path, to_path, in_blink):
+  """Updates any includes of |from_path| to |to_path|. Paths supplied to this
+  function have been mapped to forward slashes.
+  """
+  from_include_path = from_path
+  to_include_path = to_path
+  if in_blink:
+    from_include_path = UpdateIncludePathForBlink(from_include_path)
+    to_include_path = UpdateIncludePathForBlink(to_include_path)
+
+  # This handles three types of include/imports:
+  # . C++ includes.
+  # . Object-C imports
+  # . Imports in mojom files.
+  files_with_changed_includes = mffr.MultiFileFindReplace(
+      r'(#?(include|import)\s*["<])%s([>"]);?' % re.escape(from_include_path),
+      r'\1%s\3' % to_include_path,
+      ['*.cc', '*.h', '*.m', '*.mm', '*.cpp', '*.mojom'])
+
+  # Reorder headers in files that changed.
+  for changed_file in files_with_changed_includes:
+
+    def AlwaysConfirm(a, b):
+      return True
+
+    sort_headers.FixFileWithConfirmFunction(changed_file, AlwaysConfirm, True,
+                                            in_blink)
+
+
 def UpdatePostMove(from_path, to_path, in_blink):
   """Given a file that has moved from |from_path| to |to_path|,
   updates the moved file's include guard to match the new path and
@@ -96,27 +125,15 @@
   # Include paths always use forward slashes.
   from_path = from_path.replace('\\', '/')
   to_path = to_path.replace('\\', '/')
+  extension = os.path.splitext(from_path)[1]
 
-  if os.path.splitext(from_path)[1] in ['.h', '.hh']:
-    UpdateIncludeGuard(from_path, to_path)
-
-    from_include_path = from_path
-    to_include_path = to_path
-    if in_blink:
-      from_include_path = UpdateIncludePathForBlink(from_include_path)
-      to_include_path = UpdateIncludePathForBlink(to_include_path)
-
-    # Update include/import references.
-    files_with_changed_includes = mffr.MultiFileFindReplace(
-        r'(#(include|import)\s*["<])%s([>"])' % re.escape(from_include_path),
-        r'\1%s\3' % to_include_path,
-        ['*.cc', '*.h', '*.m', '*.mm', '*.cpp'])
-
-    # Reorder headers in files that changed.
-    for changed_file in files_with_changed_includes:
-      def AlwaysConfirm(a, b): return True
-      sort_headers.FixFileWithConfirmFunction(changed_file, AlwaysConfirm, True,
-                                              in_blink)
+  if extension in ['.h', '.hh', '.mojom']:
+    UpdateIncludes(from_path, to_path, in_blink)
+    if extension == '.mojom':
+      # For mojom files, update includes of the generated header.
+      UpdateIncludes(from_path + '.h', to_path + '.h', in_blink)
+    else:
+      UpdateIncludeGuard(from_path, to_path)
 
   # Update comments; only supports // comments, which are primarily
   # used in our code.
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 92706c3..30af6d8 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -261,6 +261,7 @@
       'android-code-coverage-native': 'gpu_tests_android_release_bot_minimal_symbols_arm64_fastbuild_native_coverage',
       'android-mojo-webview-rel': 'android_release_bot_minimal_symbols_arm64',
       'chromeos-amd64-generic-lacros-rel': 'chromeos_amd64-generic_lacros_rel',
+      'fuchsia-fyi-arm64-dbg': 'debug_bot_fuchsia_arm64',
       'fuchsia-fyi-arm64-rel': 'release_bot_fuchsia_arm64',
       'fuchsia-fyi-x64-dbg': 'debug_bot_fuchsia',
       'fuchsia-fyi-x64-rel': 'release_bot_fuchsia',
@@ -845,6 +846,7 @@
       'fuchsia_arm64': 'release_trybot_fuchsia_arm64',
       'fuchsia-arm64-cast': 'release_trybot_fuchsia_arm64_cast',
       'fuchsia-compile-x64-dbg': 'debug_bot_fuchsia_compile_only',
+      'fuchsia-fyi-arm64-dbg': 'debug_bot_fuchsia_arm64',
       'fuchsia-fyi-arm64-rel': 'release_trybot_fuchsia_arm64',
       'fuchsia-fyi-x64-dbg': 'debug_bot_fuchsia',
       'fuchsia-fyi-x64-rel': 'release_trybot_fuchsia',
@@ -1718,6 +1720,10 @@
       'debug_bot', 'fuchsia',
     ],
 
+    'debug_bot_fuchsia_arm64': [
+      'debug_bot', 'fuchsia', 'arm64',
+    ],
+
     'debug_bot_fuchsia_compile_only': [
       'debug_bot', 'fuchsia', 'compile_only',
     ],
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 1976fa19a8..fa76838 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -18338,6 +18338,51 @@
   <description>Please enter the description of this user action.</description>
 </action>
 
+<action name="PaintPreview.Player.Flung">
+  <owner>ckitagawa@chromium.org</owner>
+  <owner>fredmello@chromium.org</owner>
+  <owner>mahmoudi@chromium.org</owner>
+  <description>
+    Records when the user fling-scrolls the paint preview player.
+  </description>
+</action>
+
+<action name="PaintPreview.Player.LinkClicked">
+  <owner>ckitagawa@chromium.org</owner>
+  <owner>fredmello@chromium.org</owner>
+  <owner>mahmoudi@chromium.org</owner>
+  <description>
+    Records when a link is clicked in the paint preview player.
+  </description>
+</action>
+
+<action name="PaintPreview.Player.Scrolled">
+  <owner>ckitagawa@chromium.org</owner>
+  <owner>fredmello@chromium.org</owner>
+  <owner>mahmoudi@chromium.org</owner>
+  <description>
+    Records when the user scrolls the paint preview player.
+  </description>
+</action>
+
+<action name="PaintPreview.Player.Zoomed">
+  <owner>ckitagawa@chromium.org</owner>
+  <owner>fredmello@chromium.org</owner>
+  <owner>mahmoudi@chromium.org</owner>
+  <description>
+    Records when the user zooms in/out the paint preview player.
+  </description>
+</action>
+
+<action name="PaintPreview.TabbedPlayer.Removed">
+  <owner>ckitagawa@chromium.org</owner>
+  <owner>fredmello@chromium.org</owner>
+  <owner>mahmoudi@chromium.org</owner>
+  <description>
+    Records when the tabbed paint preview player is removed.
+  </description>
+</action>
+
 <action name="Panel_Minimize_Caption_Click">
   <obsolete>Removed 07/2018 since panels were deprecated in M-69.</obsolete>
   <owner>Please list the metric's owners. Add more owner tags as needed.</owner>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 9fb3ecf..29923589e 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -21138,6 +21138,8 @@
   <int value="736" label="URLBlocklist"/>
   <int value="737" label="URLAllowlist"/>
   <int value="738" label="ExtensionInstallAllowlist"/>
+  <int value="739" label="ShowFullUrlsInAddressBar"/>
+  <int value="740" label="ExtensionInstallBlocklist"/>
 </enum>
 
 <enum name="EnterprisePolicyDeviceIdValidity">
@@ -23896,6 +23898,7 @@
   <int value="1483" label="INPUTMETHODPRIVATE_SETAUTOCORRECTRANGE"/>
   <int value="1484" label="PLATFORMKEYSINTERNAL_GETPUBLICKEYBYSPKI"/>
   <int value="1485" label="CERTIFICATEPROVIDER_SETCERTIFICATES"/>
+  <int value="1486" label="AUTOTESTPRIVATE_DISABLEAUTOMATION"/>
 </enum>
 
 <enum name="ExtensionIconState">
@@ -40568,6 +40571,7 @@
   <int value="-1064302126"
       label="OmniboxAlternateMatchDescriptionSeparator:enabled"/>
   <int value="-1062119671" label="enable-password-force-saving"/>
+  <int value="-1060541241" label="ForcePreferredIntervalForVideo:enabled"/>
   <int value="-1060395248" label="PasswordSearchMobile:enabled"/>
   <int value="-1056860259"
       label="OmniboxEnableClipboardProviderImageSuggestions:enabled"/>
@@ -41384,6 +41388,7 @@
   <int value="-153299083" label="NewProfilePicker:disabled"/>
   <int value="-152677714" label="AsmJsToWebAssembly:enabled"/>
   <int value="-152632720" label="RuntimeHostPermissions:enabled"/>
+  <int value="-152439608" label="download-later-debug-on-wifi"/>
   <int value="-147283486" label="enable-network-portal-notification"/>
   <int value="-146552997" label="enable-affiliation-based-matching"/>
   <int value="-144134779" label="AndroidPayIntegrationV2:disabled"/>
@@ -42412,6 +42417,7 @@
   <int value="1004909189"
       label="ContentSuggestionsThumbnailDominantColor:disabled"/>
   <int value="1005684777" label="PictureInPicture:disabled"/>
+  <int value="1006080779" label="ForcePreferredIntervalForVideo:disabled"/>
   <int value="1006608931" label="ArcEnableUnifiedAudioFocus:disabled"/>
   <int value="1006691801" label="ConditionalTabStripAndroid:disabled"/>
   <int value="1007444341" label="enable-prefixed-encrypted-media"/>
@@ -51275,6 +51281,9 @@
 </enum>
 
 <enum name="OmniboxEditUrlSuggestionAction">
+  <obsolete>
+    Deprecated 06/2020 as histogram expired.
+  </obsolete>
   <int value="0" label="Edit tapped."/>
   <int value="1" label="Copy tapped."/>
   <int value="2" label="Share tapped."/>
@@ -52732,6 +52741,14 @@
   <int value="5" label="Metric value was less than 10 seconds"/>
 </enum>
 
+<enum name="PageLoadType">
+  <int value="0" label="Never foregrounded"/>
+  <int value="1"
+      label="Aborted: was foregrounded but never reached first contentful
+             paint"/>
+  <int value="2" label="Was foregrounded and reached first contentful paint"/>
+</enum>
+
 <enum name="PageScaleFactorRange">
   <int value="0" label="&lt;25%"/>
   <int value="1" label="25-49%"/>
@@ -75952,6 +75969,12 @@
   </int>
 </enum>
 
+<enum name="ZeroRttState">
+  <int value="0" label="Attempted and succeeded"/>
+  <int value="1" label="Attempted and rejected"/>
+  <int value="2" label="Not Attempted"/>
+</enum>
+
 <enum name="ZeroStateResultType">
   <int value="0" label="Unknown"/>
   <int value="1" label="Unanticipated"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index c51c5272..04a3c054 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -3623,7 +3623,10 @@
 </histogram>
 
 <histogram name="Android.IntentHeaders" enum="IntentHeadersResult"
-    expires_after="2020-11-08">
+    expires_after="M85">
+  <obsolete>
+    Removed in M86. No longer tracked.
+  </obsolete>
   <owner>peconn@chromium.org</owner>
   <summary>
     Records the usage of the Browser.EXTRA_HEADERS field for Intents that Chrome
@@ -3633,8 +3636,10 @@
 </histogram>
 
 <histogram name="Android.IntentNonSafelistedHeaderNames"
-    enum="AndroidIntentNonSafelistedHeaderNameHashes"
-    expires_after="2020-10-31">
+    enum="AndroidIntentNonSafelistedHeaderNameHashes" expires_after="M85">
+  <obsolete>
+    Removed in M86. No longer tracked.
+  </obsolete>
   <owner>jochen@chromium.org</owner>
   <owner>peconn@chromium.org</owner>
   <summary>
@@ -6453,7 +6458,7 @@
 </histogram>
 
 <histogram name="AppManagement.AppDetailViews" enum="AppManagementUserAction"
-    expires_after="2020-11-05">
+    expires_after="2021-01-10">
 <!-- Name completed by histogram_suffixes name="AppType" -->
 
   <owner>jshikaram@chromium.org</owner>
@@ -7575,7 +7580,7 @@
 </histogram>
 
 <histogram name="Apps.AppListShowSource" enum="AppListShowSource"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>newcomer@chromium.org</owner>
   <summary>
     The number of times the different sources for showing the app list are used.
@@ -8205,7 +8210,7 @@
 </histogram>
 
 <histogram name="Apps.ScrollableShelf.AnimationSmoothness" units="%"
-    expires_after="2020-11-06">
+    expires_after="2021-01-10">
 <!-- Name completed by histogram suffixes
      name="HomeLauncherVisibility" -->
 
@@ -8221,7 +8226,7 @@
 </histogram>
 
 <histogram base="true" name="Apps.ScrollableShelf.Drag.PresentationTime"
-    units="ms" expires_after="2020-11-08">
+    units="ms" expires_after="2021-01-10">
 <!-- Name completed by histogram_suffixes
      name="TabletOrClamshellMode" and
      name="HomeLauncherVisibility"-->
@@ -8500,7 +8505,7 @@
 </histogram>
 
 <histogram name="Arc.ContainerLifetimeEvent" enum="ArcContainerLifetimeEvent"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>elijahtaylor@google.com</owner>
   <owner>yusukes@google.com</owner>
   <summary>
@@ -9252,7 +9257,7 @@
 </histogram>
 
 <histogram name="Arc.StateByUserType" enum="ArcEnableState"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
 <!-- Name completed by histogram_suffixes name="ArcUserTypes" -->
 
   <owner>elijahtaylor@google.com</owner>
@@ -9754,7 +9759,7 @@
 </histogram>
 
 <histogram name="Ash.Desktop.TimeBetweenNavigateToTaskSwitches" units="seconds"
-    expires_after="2020-11-01">
+    expires_after="2021-01-10">
   <owner>tbuckley@chromium.org</owner>
   <owner>tclaiborne@chromium.org</owner>
   <summary>
@@ -10067,7 +10072,7 @@
 </histogram>
 
 <histogram name="Ash.Login.Lock.AuthMethod.Switched"
-    enum="AuthMethodSwitchType" expires_after="2020-11-01">
+    enum="AuthMethodSwitchType" expires_after="2021-01-10">
   <owner>rsorokin@chromium.org</owner>
   <owner>cros-oac@google.com</owner>
   <summary>
@@ -11034,7 +11039,7 @@
 </histogram>
 
 <histogram name="Ash.TouchView.TouchViewInactive" units="ms"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>girard@chromium.org</owner>
   <summary>The length of time between TouchView activations.</summary>
 </histogram>
@@ -14139,7 +14144,7 @@
 </histogram>
 
 <histogram name="Autofill.CardUnmask.CvcLength" units="digits"
-    expires_after="M86">
+    expires_after="2021-03-31">
   <owner>jsaul@google.com</owner>
   <owner>siyua@chromium.org</owner>
   <owner>payments-autofill-team@google.com</owner>
@@ -17805,7 +17810,7 @@
 </histogram>
 
 <histogram name="BackgroundSync.Event.PeriodicResultPattern"
-    enum="BackgroundSyncResultPattern" expires_after="2020-07-31">
+    enum="BackgroundSyncResultPattern" expires_after="2021-07-31">
   <owner>nator@chromium.org</owner>
   <owner>rayankans@chromium.org</owner>
   <owner>peter@chromium.org</owner>
@@ -17820,7 +17825,7 @@
 </histogram>
 
 <histogram name="BackgroundSync.Event.PeriodicStartedInForeground"
-    enum="BooleanInForeground" expires_after="2020-07-31">
+    enum="BooleanInForeground" expires_after="2021-07-31">
   <owner>nator@chromium.org</owner>
   <owner>rayankans@chromium.org</owner>
   <owner>peter@chromium.org</owner>
@@ -17970,7 +17975,7 @@
 </histogram>
 
 <histogram name="BackgroundSync.Registration.Periodic"
-    enum="BackgroundSyncStatus" expires_after="2020-12-13">
+    enum="BackgroundSyncStatus" expires_after="2021-07-31">
   <owner>nator@chromium.org</owner>
   <owner>rayankans@chromium.org</owner>
   <summary>
@@ -17980,7 +17985,7 @@
 </histogram>
 
 <histogram name="BackgroundSync.Registration.Periodic.IsDuplicate"
-    enum="BooleanRegistrationIsDuplicate" expires_after="2020-07-31">
+    enum="BooleanRegistrationIsDuplicate" expires_after="2021-07-31">
   <owner>nator@chromium.org</owner>
   <owner>rayankans@chromium.org</owner>
   <summary>
@@ -17991,7 +17996,7 @@
 </histogram>
 
 <histogram name="BackgroundSync.Registration.Periodic.MinInterval"
-    units="seconds" expires_after="2020-07-31">
+    units="seconds" expires_after="2021-07-31">
   <owner>nator@chromium.org</owner>
   <owner>rayankans@chromium.org</owner>
   <summary>
@@ -18034,7 +18039,7 @@
 </histogram>
 
 <histogram name="BackgroundSync.WakeupTaskFiredEvents.OneShot"
-    enum="BackgroundSyncFiredEvents" expires_after="2020-07-31">
+    enum="BackgroundSyncFiredEvents" expires_after="2021-07-31">
   <owner>nator@chromium.org</owner>
   <owner>peter@chromium.org</owner>
   <owner>rayankans@chromium.org</owner>
@@ -18047,7 +18052,7 @@
 </histogram>
 
 <histogram name="BackgroundSync.WakeupTaskFiredEvents.Periodic"
-    enum="BackgroundSyncFiredEvents" expires_after="2020-07-31">
+    enum="BackgroundSyncFiredEvents" expires_after="2021-07-31">
   <owner>nator@chromium.org</owner>
   <owner>peter@chromium.org</owner>
   <owner>rayankans@chromium.org</owner>
@@ -18208,7 +18213,7 @@
 </histogram>
 
 <histogram name="Blink.Animate.UpdateTime" units="microseconds"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
 
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
@@ -19586,7 +19591,7 @@
 </histogram>
 
 <histogram name="Blink.ForcedStyleAndLayout.UpdateTime" units="microseconds"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
 
   <owner>schenney@chromium.org</owner>
@@ -19851,7 +19856,7 @@
 </histogram>
 
 <histogram name="Blink.IntersectionObservation.UpdateTime" units="microseconds"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
 
   <owner>schenney@chromium.org</owner>
@@ -19992,7 +19997,7 @@
 </histogram>
 
 <histogram base="true" name="Blink.MainFrame.ForcedStyleAndLayoutRatio"
-    units="%" expires_after="2020-11-08">
+    units="%" expires_after="2021-01-10">
   <owner>schenney@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
 <!-- Name completed by histogram_suffixes name="BlinkMainFrameUpdateTimeSuffixes" -->
@@ -20054,7 +20059,7 @@
 </histogram>
 
 <histogram base="true" name="Blink.MainFrame.LayoutRatio" units="%"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>schenney@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
 <!-- Name completed by histogram_suffixes name="BlinkMainFrameUpdateTimeSuffixes" -->
@@ -20066,7 +20071,7 @@
 </histogram>
 
 <histogram base="true" name="Blink.MainFrame.PaintRatio" units="%"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>schenney@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
 <!-- Name completed by histogram_suffixes name="BlinkMainFrameUpdateTimeSuffixes" -->
@@ -20146,7 +20151,7 @@
 </histogram>
 
 <histogram base="true" name="Blink.MainFrame.UpdateLayersRatio" units="%"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>schenney@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
 <!-- Name completed by histogram_suffixes name="BlinkMainFrameUpdateTimeSuffixes" -->
@@ -20758,7 +20763,7 @@
 </histogram>
 
 <histogram name="Blink.ScrollingCoordinator.UpdateTime" units="microseconds"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
 
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
@@ -20953,7 +20958,7 @@
 </histogram>
 
 <histogram name="Blink.Style.UpdateTime" units="microseconds"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
 
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
@@ -21174,7 +21179,7 @@
 </histogram>
 
 <histogram name="Blink.UseCounter.FeaturePolicy.Header"
-    enum="FeaturePolicyFeature" expires_after="2020-11-08">
+    enum="FeaturePolicyFeature" expires_after="2021-01-10">
   <owner>iclelland@chromium.org</owner>
   <summary>
     Counts the use of a specific feature policy via the
@@ -21369,7 +21374,7 @@
 </histogram>
 
 <histogram name="Blink.UseCounter.File.Features" enum="FeatureObserver"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>yhirano@chromium.org</owner>
   <owner>mkwst@chromium.org</owner>
   <summary>
@@ -22239,7 +22244,7 @@
 </histogram>
 
 <histogram name="Bluetooth.ConnectedDeviceCount" units="devices"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>adlr@chromium.org</owner>
   <summary>
     Counts the number of simulataneously connected Bluetooth devices. Used to
@@ -22429,7 +22434,7 @@
 </histogram>
 
 <histogram name="Bluetooth.Web.ConnectGATT.Outcome"
-    enum="WebBluetoothConnectGATTOutcome" expires_after="2020-11-08">
+    enum="WebBluetoothConnectGATTOutcome" expires_after="2021-01-10">
   <owner>odejesush@chromium.org</owner>
   <owner>ortuno@chromium.org</owner>
   <owner>reillyg@chromium.org</owner>
@@ -22805,7 +22810,7 @@
   </summary>
 </histogram>
 
-<histogram name="BlueZ.AdapterLost" units="seconds" expires_after="2020-11-08">
+<histogram name="BlueZ.AdapterLost" units="seconds" expires_after="2021-01-10">
   <owner>mcchou@chromium.org</owner>
   <summary>
     This is specific to Chrome OS. Records a duration of a Bluetooth adapter
@@ -22894,7 +22899,7 @@
 </histogram>
 
 <histogram name="BlueZ.ResultOfConnection" enum="BlueZResultOfConnection"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>mcchou@chromium.org</owner>
   <summary>
     This is specific to Chrome OS. Records the outcomes of connection requests
@@ -23728,7 +23733,7 @@
 </histogram>
 
 <histogram name="BrowserRenderProcessHost.ChildCrashes" enum="RendererType"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>wfh@chromium.org</owner>
   <summary>Count of renderer process crashes grouped by type.</summary>
 </histogram>
@@ -24586,7 +24591,7 @@
 </histogram>
 
 <histogram name="CaptivePortal.DetectResult" enum="CaptivePortalDetectResult"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>meacer@chromium.org</owner>
   <summary>Records the result of a captive portal probe.</summary>
 </histogram>
@@ -25799,7 +25804,7 @@
 </histogram>
 
 <histogram name="ChromeColors.ColorOnLoad" enum="ChromeColorsInfo"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>gayane@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
   <summary>
@@ -28023,7 +28028,7 @@
 </histogram>
 
 <histogram name="Compositing.DirectRenderer.Software.DrawFrameUs"
-    units="microseconds" expires_after="2020-11-08">
+    units="microseconds" expires_after="2021-01-10">
   <owner>weiliangc@chromium.org</owner>
   <summary>
     Time spent drawing of composited layers by SoftwareRenderer, in
@@ -28130,7 +28135,7 @@
 </histogram>
 
 <histogram name="Compositing.Display.DrawToSwapUs" units="microseconds"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>backer@chromium.org</owner>
   <owner>rjkroege@chromium.org</owner>
   <summary>
@@ -28166,7 +28171,7 @@
 </histogram>
 
 <histogram name="Compositing.Display.FlattenedRenderPassCount" units="units"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>backer@chromium.org</owner>
   <owner>rjkroege@chromium.org</owner>
   <summary>
@@ -28179,7 +28184,7 @@
 
 <histogram
     name="Compositing.Display.OverlayProcessorOzone.IsCandidateSharedImage"
-    enum="Boolean" expires_after="2020-11-08">
+    enum="Boolean" expires_after="2021-01-10">
   <owner>samans@chromium.org</owner>
   <owner>rjkroege@chromium.org</owner>
   <summary>
@@ -28191,7 +28196,7 @@
 </histogram>
 
 <histogram name="Compositing.Display.OverlayProcessorOzone.SharedImageExists"
-    enum="Boolean" expires_after="2020-11-08">
+    enum="Boolean" expires_after="2021-01-10">
   <owner>samans@chromium.org</owner>
   <owner>rjkroege@chromium.org</owner>
   <summary>
@@ -31202,7 +31207,7 @@
 </histogram>
 
 <histogram name="ContextMenu.LensSupportStatus"
-    enum="ContextMenuLensSupportStatus" expires_after="2020-11-08">
+    enum="ContextMenuLensSupportStatus" expires_after="2021-01-10">
   <owner>benwgold@google.com</owner>
   <owner>lens-chrome@google.com</owner>
   <summary>
@@ -45690,7 +45695,7 @@
 </histogram>
 
 <histogram name="Download.Resume.AutoResumeLimitReached" enum="Boolean"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>shaktisahu@chromium.org</owner>
   <owner>clank-downloads@google.com</owner>
   <summary>
@@ -45699,7 +45704,7 @@
 </histogram>
 
 <histogram name="Download.Resume.AutoResumeLimitReached.LastReason"
-    enum="InterruptReason" expires_after="2020-11-08">
+    enum="InterruptReason" expires_after="2021-01-10">
   <owner>shaktisahu@chromium.org</owner>
   <owner>clank-downloads@google.com</owner>
   <summary>
@@ -46027,7 +46032,7 @@
 </histogram>
 
 <histogram name="Download.Service.Finish.Type"
-    enum="Download.Service.CompletionType" expires_after="2020-11-08">
+    enum="Download.Service.CompletionType" expires_after="2021-01-10">
   <owner>xingliu@chromium.org</owner>
   <summary>The completion type for downloads in download service.</summary>
 </histogram>
@@ -57431,7 +57436,7 @@
 </histogram>
 
 <histogram name="Extensions.GoogleDocOffline.AvailabilityOnResourceRequest"
-    enum="GoogleDocsExtensionAvailablity" expires_after="M86">
+    enum="GoogleDocsExtensionAvailablity" expires_after="M90">
   <owner>rhalavati@chromium.org</owner>
   <owner>chrome-privacy-core@google.com</owner>
   <summary>
@@ -63164,7 +63169,7 @@
   <summary>Number of retries before a GCM unregistration succeeds.</summary>
 </histogram>
 
-<histogram name="GCM.UserSignedIn" enum="Boolean" expires_after="2020-10-18">
+<histogram name="GCM.UserSignedIn" enum="Boolean" expires_after="2021-01-10">
   <owner>jianli@chromium.org</owner>
   <summary>
     Indicates whether the user was signed in when GCM started up.
@@ -64566,7 +64571,7 @@
 </histogram>
 
 <histogram name="GPU.DirectComposition.CreateVideoProcessorEnumerator"
-    enum="Hresult" expires_after="2020-11-01">
+    enum="Hresult" expires_after="2021-01-10">
   <owner>magchen@chromium.org</owner>
   <owner>zmo@chromium.org</owner>
   <summary>
@@ -64625,7 +64630,7 @@
 </histogram>
 
 <histogram name="GPU.DirectComposition.DcompDeviceCreateSurface" enum="Hresult"
-    expires_after="2020-11-01">
+    expires_after="2021-01-10">
   <owner>magchen@chromium.org</owner>
   <owner>zmo@chromium.org</owner>
   <summary>
@@ -64963,7 +64968,7 @@
 </histogram>
 
 <histogram name="GPU.DirectComposition.UploadVideoImages.CreateCopyTexture"
-    enum="Hresult" expires_after="2020-11-01">
+    enum="Hresult" expires_after="2021-01-10">
   <owner>magchen@chromium.org</owner>
   <owner>zmo@chromium.org</owner>
   <summary>
@@ -64973,7 +64978,7 @@
 </histogram>
 
 <histogram name="GPU.DirectComposition.UploadVideoImages.CreateStagingTexture"
-    enum="Hresult" expires_after="2020-11-01">
+    enum="Hresult" expires_after="2021-01-10">
   <owner>magchen@chromium.org</owner>
   <owner>zmo@chromium.org</owner>
   <summary>
@@ -64983,7 +64988,7 @@
 </histogram>
 
 <histogram name="GPU.DirectComposition.VideoDeviceCreateVideoProcessor"
-    enum="Hresult" expires_after="2020-11-01">
+    enum="Hresult" expires_after="2021-01-10">
   <owner>magchen@chromium.org</owner>
   <owner>zmo@chromium.org</owner>
   <summary>
@@ -65220,7 +65225,7 @@
 </histogram>
 
 <histogram name="GPU.GPUProcessExitCode" enum="GPUProcessExitCode"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>jbauman@chromium.org</owner>
   <summary>
     Counts for the exit codes returned by the GPU process when it terminated.
@@ -65623,7 +65628,7 @@
 </histogram>
 
 <histogram name="GPU.ProcessLifetimeEvents.HardwareAccelerated"
-    enum="GPUProcessLifetimeEvent" expires_after="2020-11-01">
+    enum="GPUProcessLifetimeEvent" expires_after="2021-01-10">
   <owner>vmiura@chromium.org</owner>
   <summary>
     Recorded once for every GPU process launch and crash when GPU process is
@@ -66208,7 +66213,7 @@
 </histogram>
 
 <histogram name="GPU.WatchdogThread.Timeout" enum="GpuWatchdogTimeoutEvent"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
 <!-- Name completed by histogram_suffixes name="GPU.WatchdogStage" -->
 
   <owner>magchen@chromium.org</owner>
@@ -68443,7 +68448,7 @@
   </summary>
 </histogram>
 
-<histogram name="HttpCache.BeforeSend" units="ms" expires_after="2020-11-08">
+<histogram name="HttpCache.BeforeSend" units="ms" expires_after="2021-01-10">
   <owner>morlovich@chromium.org</owner>
   <summary>
     For http cache transactions in which a network request was sent, the time
@@ -74854,7 +74859,7 @@
 </histogram>
 
 <histogram name="Login.BrowserShutdownTime" units="ms"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>xiyuan@chromium.org</owner>
   <summary>
     Tracks the browser process shutdown time from when SIGTERM is sent to the
@@ -75333,6 +75338,9 @@
 
 <histogram name="ManagedUsers.ChromeOS.PasswordChange"
     enum="ManagedUserPasswordChange" expires_after="M77">
+  <obsolete>
+    Removed 07/2020 as it is no longer used.
+  </obsolete>
   <owner>achuith@chromium.org</owner>
   <summary>
     Chrome OS histogram that keeps track of supervised user password change
@@ -98772,6 +98780,9 @@
 
 <histogram name="Net.QuicNumStreamFramesInPacket" units="units"
     expires_after="2021-05-11">
+  <obsolete>
+    Removed in 07/2020.
+  </obsolete>
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -98781,6 +98792,9 @@
 
 <histogram name="Net.QuicNumStreamFramesPerStreamInPacket" units="units"
     expires_after="2021-05-11">
+  <obsolete>
+    Removed in 07/2020.
+  </obsolete>
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -100668,6 +100682,13 @@
   </summary>
 </histogram>
 
+<histogram name="Net.QuicSession.ZeroRttState" enum="ZeroRttState"
+    expires_after="2021-05-11">
+  <owner>renjietang@chromium.org</owner>
+  <owner>src/net/quic/OWNERS</owner>
+  <summary>Whether 0-RTT was successfully used in the connection.</summary>
+</histogram>
+
 <histogram name="Net.QuicStreamFactory.AttemptMigrationBeforeHandshake"
     enum="BooleanAttempted" expires_after="2021-05-11">
   <owner>zhongyi@chromium.org</owner>
@@ -103496,7 +103517,7 @@
 </histogram>
 
 <histogram base="true" name="Net.TrustTokens.OperationBeginTime" units="ms"
-    expires_after="M86">
+    expires_after="2021-03-30">
 <!-- Name completed by histogram_suffixes name="TrustTokenOperationType" -->
 
   <owner>davidvc@chromium.org</owner>
@@ -103508,7 +103529,7 @@
 </histogram>
 
 <histogram base="true" name="Net.TrustTokens.OperationFinalizeTime" units="ms"
-    expires_after="M88">
+    expires_after="2021-03-30">
 <!-- Name completed by histogram_suffixes name="TrustTokenOperationType" -->
 
   <owner>davidvc@chromium.org</owner>
@@ -103520,7 +103541,7 @@
 </histogram>
 
 <histogram base="true" name="Net.TrustTokens.OperationServerTime" units="ms"
-    expires_after="M88">
+    expires_after="2021-03-30">
 <!-- Name completed by histogram_suffixes name="TrustTokenOperationType" -->
 
   <owner>davidvc@chromium.org</owner>
@@ -103533,7 +103554,7 @@
 </histogram>
 
 <histogram base="true" name="Net.TrustTokens.OperationTotalTime" units="ms"
-    expires_after="M88">
+    expires_after="2021-03-30">
 <!-- Name completed by histogram_suffixes name="TrustTokenOperationType" -->
 
   <owner>davidvc@chromium.org</owner>
@@ -114783,9 +114804,14 @@
 
 <histogram name="Omnibox.EditUrlSuggestionAction"
     enum="OmniboxEditUrlSuggestionAction" expires_after="M85">
-  <owner>mpearson@chromium.org</owner>
+  <obsolete>
+    Removed 06/2020. Use &quot;Omnibox.EditUrlSuggestion.*&quot; user actions
+    instead.
+  </obsolete>
   <owner>jdonnelly@chromium.org</owner>
   <owner>mdjones@chromium.org</owner>
+  <owner>fgorski@chromium.org</owner>
+  <owner>ender@chromium.org</owner>
   <summary>
     The action performed on the edit-URL omnibox suggestion that contains the
     &quot;what you typed&quot;/current URL. If visible, this is the first
@@ -117762,7 +117788,7 @@
 </histogram>
 
 <histogram name="Overscroll.Navigated3" enum="OverscrollNavigationType"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>nzolghadr@chromium.org</owner>
   <summary>
     Navigations that were triggered due to completed overscroll gesture. Note
@@ -117799,7 +117825,7 @@
 </histogram>
 
 <histogram name="Overscroll.Started3" enum="OverscrollNavigationType"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>nzolghadr@chromium.org</owner>
   <summary>
     Overscroll gestures initiated by the user. Note that not all overscroll
@@ -120591,6 +120617,20 @@
   </summary>
 </histogram>
 
+<histogram name="PageLoad.Experimental.PageLoadType" enum="PageLoadType"
+    expires_after="2021-01-30">
+  <owner>npm@chromium.org</owner>
+  <owner>speed-metrics-dev@chromium.org</owner>
+  <summary>
+    Determines the PageLoadType for this page load. That is, records whether the
+    page was never foregrounded, was foregrounded but did not reach FCP, or did
+    reach FCP. The metric is recorded at the end of each page load. As usual
+    with PageLoad metrics, we consider app backgrounding on Android to be the
+    end of page load as well as the app could be evicted by Android anytime
+    after that happens.
+  </summary>
+</histogram>
+
 <histogram name="PageLoad.Experimental.PageTiming.FirstPaintToFirstBackground"
     units="ms" expires_after="2017-02-28">
   <obsolete>
@@ -120983,6 +121023,22 @@
   </summary>
 </histogram>
 
+<histogram name="PageLoad.Experimental.TotalForegroundDuration" units="ms"
+    expires_after="2021-01-30">
+  <owner>npm@chromium.org</owner>
+  <owner>speed-metrics-dev@chromium.org</owner>
+  <summary>
+    The total amount of time the page spent in the foreground. Note that this
+    metric adds all foreground durations occurring for the page. For example: if
+    the page starts foregrounded during 1 second, then goes to the background
+    for a while, then is foregrounded again for 2 seconds, and then is unloaded,
+    this metric reports 3 seconds. The metric is recorded at the end of each
+    page load. As usual with PageLoad metrics, we consider app backgrounding on
+    Android to be the end of page load as well as the app could be evicted by
+    Android anytime after that happens.
+  </summary>
+</histogram>
+
 <histogram name="PageLoad.Experimental.TotalRequests.ParseStop"
     units="requests" expires_after="2018-01-17">
   <obsolete>
@@ -123068,7 +123124,9 @@
   </summary>
 </histogram>
 
-<histogram name="PartnerBookmark.LoadingTime" units="ms" expires_after="M77">
+<histogram name="PartnerBookmark.LoadingTime" units="ms"
+    expires_after="2021-07-10">
+  <owner>bttk@chromium.org</owner>
   <owner>wychen@chromium.org</owner>
   <summary>
     The time spent on loading partner bookmarks, from kickOffReading() to
@@ -123359,7 +123417,7 @@
 </histogram>
 
 <histogram name="PasswordManager.AccountChooserDialogMultipleAccounts"
-    enum="AccountChooserDismissalReason" expires_after="M86">
+    enum="AccountChooserDismissalReason" expires_after="2020-12-12">
   <owner>vasilii@chromium.org</owner>
   <owner>jdoerrie@chromium.org</owner>
   <summary>
@@ -123368,7 +123426,7 @@
 </histogram>
 
 <histogram name="PasswordManager.AccountChooserDialogOneAccount"
-    enum="AccountChooserDismissalReason" expires_after="M86">
+    enum="AccountChooserDismissalReason" expires_after="2020-12-12">
   <owner>vasilii@chromium.org</owner>
   <owner>jdoerrie@chromium.org</owner>
   <summary>
@@ -123967,7 +124025,7 @@
 </histogram>
 
 <histogram name="PasswordManager.AutoSigninFirstRunDialog"
-    enum="AutoSigninFirstRun" expires_after="M86">
+    enum="AutoSigninFirstRun" expires_after="2020-12-12">
   <owner>vasilii@chromium.org</owner>
   <owner>jdoerrie@chromium.org</owner>
   <summary>
@@ -127792,7 +127850,7 @@
 </histogram>
 
 <histogram name="PeriodicBackgroundSync.Event.BatchSize" units="events"
-    expires_after="2020-07-31">
+    expires_after="2021-07-31">
   <owner>nator@chromium.org</owner>
   <owner>rayankans@chromium.org</owner>
   <summary>
@@ -127803,7 +127861,7 @@
 </histogram>
 
 <histogram name="PeriodicBackgroundSync.Event.FromWakeupTask"
-    enum="BackgroundSyncWakeupTask" expires_after="M86">
+    enum="BackgroundSyncWakeupTask" expires_after="2021-07-31">
   <owner>nator@chromium.org</owner>
   <owner>rayankans@chromium.org</owner>
   <summary>
@@ -127813,7 +127871,7 @@
 </histogram>
 
 <histogram name="PeriodicBackgroundSync.Event.Time" units="ms"
-    expires_after="2020-07-31">
+    expires_after="2021-07-31">
   <owner>nator@chromium.org</owner>
   <owner>rayankans@chromium.org</owner>
   <summary>
@@ -133225,7 +133283,7 @@
 </histogram>
 
 <histogram name="Power.BatteryRemainingAtStartOfSessionOnBattery" units="%"
-    expires_after="2020-10-25">
+    expires_after="2021-01-10">
   <owner>tbroch@chromium.org</owner>
   <summary>
     Chrome OS remaining battery charge as percent of the maximum battery charge,
@@ -137694,7 +137752,7 @@
 </histogram>
 
 <histogram name="Privacy.DoNotTrackSetting" enum="BooleanEnabled"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>mkwst@chromium.org</owner>
   <owner>msramek@chromium.org</owner>
   <summary>
@@ -137826,7 +137884,7 @@
 </histogram>
 
 <histogram name="Profile.AddNewUser" enum="ProfileAddNewUser"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>rogerta@chromium.org</owner>
   <summary>The frequency of ways that new user profiles are added.</summary>
 </histogram>
@@ -141277,6 +141335,9 @@
 
 <histogram name="Renderer4.ImagePixelsPercentSRGB" units="%"
     expires_after="M85">
+  <obsolete>
+    Removed July 2020, expired and no longer useful.
+  </obsolete>
   <owner>ccameron@chromium.org</owner>
   <summary>
     For each cc::Layer that has more than zero discardable images, this metric
@@ -141286,6 +141347,9 @@
 </histogram>
 
 <histogram name="Renderer4.ImagesPercentSRGB" units="%" expires_after="M85">
+  <obsolete>
+    Removed July 2020, expired and no longer useful.
+  </obsolete>
   <owner>ccameron@chromium.org</owner>
   <summary>
     For each cc::Layer that has more than zero discardable images, this metric
@@ -148785,7 +148849,7 @@
 </histogram>
 
 <histogram name="SB2.RemoteCall.Result" enum="SB2RemoteCallResult"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>vakh@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -149268,7 +149332,7 @@
 </histogram>
 
 <histogram name="SBClientDownload.DownloadRequestNetError" enum="NetErrorCodes"
-    expires_after="2020-10-25">
+    expires_after="2021-01-10">
   <owner>vakh@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <owner>mattm@chromium.org</owner>
@@ -149904,7 +149968,7 @@
 </histogram>
 
 <histogram name="SBClientPhishing.ClassificationStart" enum="BooleanHit"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>vakh@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -150097,7 +150161,7 @@
 
 <histogram name="SBClientPhishing.PreClassificationCheckFail"
     enum="SBClientDetectionPreClassificationCheckFail"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>vakh@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -154726,8 +154790,9 @@
 </histogram>
 
 <histogram base="true" name="Security.PageEndReason" enum="PageEndReason"
-    expires_after="M86">
+    expires_after="2021-02-01">
   <owner>cthomp@chromium.org</owner>
+  <owner>security-enamel@chromium.org</owner>
   <summary>
     Records the reason the page visit ended (e.g., reload, tab closed, new
     navigation, etc.) for page loads that committed.
@@ -154811,8 +154876,10 @@
   </summary>
 </histogram>
 
-<histogram name="Security.PageInfo.TimeOpen" units="units" expires_after="M86">
+<histogram name="Security.PageInfo.TimeOpen" units="units"
+    expires_after="2021-02-01">
   <owner>cthomp@chromium.org</owner>
+  <owner>security-enamel@chromium.org</owner>
   <summary>
     Records the amount of time the Page Info bubble is open before the user
     closes it or takes an action which closes it.
@@ -154820,8 +154887,9 @@
 </histogram>
 
 <histogram name="Security.PageInfo.TimeOpen.Action" units="units"
-    expires_after="M86">
+    expires_after="2021-02-01">
   <owner>cthomp@chromium.org</owner>
+  <owner>security-enamel@chromium.org</owner>
   <summary>
     Records the amount of time the Page Info bubble is open before the user
     closes it, for cases where the user has performed an action inside it.
@@ -154829,8 +154897,9 @@
 </histogram>
 
 <histogram name="Security.PageInfo.TimeOpen.NoAction" units="units"
-    expires_after="M86">
+    expires_after="2021-02-01">
   <owner>cthomp@chromium.org</owner>
+  <owner>security-enamel@chromium.org</owner>
   <summary>
     Records the amount of time the Page Info bubble is open before the user
     closes it, for cases where the user performed no action inside it.
@@ -155037,8 +155106,9 @@
 </histogram>
 
 <histogram name="Security.SecurityLevel.DownloadStarted" enum="SecurityLevel"
-    expires_after="M86">
+    expires_after="2021-02-01">
   <owner>cthomp@chromium.org</owner>
+  <owner>security-enamel@chromium.org</owner>
   <summary>
     Records the security level of the page that initiated a download (rather
     than the security state of the connection to the download URL itself). The
@@ -155050,7 +155120,7 @@
 </histogram>
 
 <histogram name="Security.SecurityLevel.FormSubmission" enum="SecurityLevel"
-    expires_after="M86">
+    expires_after="2021-02-01">
   <owner>carlosil@chromium.org</owner>
   <owner>cthomp@chromium.org</owner>
   <summary>
@@ -155105,8 +155175,9 @@
 </histogram>
 
 <histogram name="Security.SecurityLevel.OnCommit" enum="SecurityLevel"
-    expires_after="M86">
+    expires_after="2021-02-01">
   <owner>cthomp@chromium.org</owner>
+  <owner>security-enamel@chromium.org</owner>
   <summary>
     Records the security level of a page at the time the navigation commits.
     Note that the security level of a page can change after commit time, so this
@@ -155116,8 +155187,9 @@
 </histogram>
 
 <histogram name="Security.SecurityLevel.OnComplete" enum="SecurityLevel"
-    expires_after="M86">
+    expires_after="2021-02-01">
   <owner>cthomp@chromium.org</owner>
+  <owner>security-enamel@chromium.org</owner>
   <summary>
     Records the security level of a page at the end of the page visit (i.e.,
     navigating away from the page, reloading the page, clicking a link, closing
@@ -155128,8 +155200,9 @@
 </histogram>
 
 <histogram base="true" name="Security.SiteEngagement" units="units"
-    expires_after="M86">
+    expires_after="2021-02-01">
   <owner>cthomp@chromium.org</owner>
+  <owner>security-enamel@chromium.org</owner>
   <summary>
     The final Site Engagement score (0 to 100) of a URL during a visit to a
     page. Recorded when the user closes the page or initiates a new navigation.
@@ -155137,8 +155210,9 @@
 </histogram>
 
 <histogram base="true" name="Security.SiteEngagementDelta" units="units"
-    expires_after="M86">
+    expires_after="2021-02-01">
   <owner>cthomp@chromium.org</owner>
+  <owner>security-enamel@chromium.org</owner>
   <summary>
     The change in Site Engagement score for a site between the page navigation
     committing and the user closing the page or navigating away. Recorded when
@@ -155167,8 +155241,9 @@
 </histogram>
 
 <histogram base="true" name="Security.TimeOnPage2" units="units"
-    expires_after="2020-08-16">
+    expires_after="2021-02-01">
   <owner>cthomp@chromium.org</owner>
+  <owner>security-enamel@chromium.org</owner>
   <summary>
     Records the time spent on the page (the time that the page was in the
     foreground from the start of the navigation to the page visit completing due
@@ -156735,6 +156810,10 @@
 
 <histogram name="ServiceWorker.RegistrationCount" units="registrations"
     expires_after="2020-08-25">
+  <obsolete>
+    No longer needed as found the cause of stability issue to be number of
+    registrations rather than size of registrations. Removed in July 2020.
+  </obsolete>
   <owner>nidhijaju@google.com</owner>
   <owner>bashi@chromium.org</owner>
   <owner>chrome-worker@google.com</owner>
@@ -156746,6 +156825,10 @@
 
 <histogram name="ServiceWorker.RegistrationInfo.ScopeLength" units="characters"
     expires_after="2020-08-25">
+  <obsolete>
+    No longer needed as found the cause of stability issue to be number of
+    registrations rather than size of registrations. Removed in July 2020.
+  </obsolete>
   <owner>nidhijaju@google.com</owner>
   <owner>bashi@chromium.org</owner>
   <owner>chrome-worker@google.com</owner>
@@ -157365,6 +157448,10 @@
 
 <histogram name="ServiceWorker.VersionInfo.ClientCount" units="clients"
     expires_after="2020-08-25">
+  <obsolete>
+    No longer needed as found the cause of stability issue to be number of
+    registrations rather than size of registrations. Removed in July 2020.
+  </obsolete>
   <owner>nidhijaju@google.com</owner>
   <owner>bashi@chromium.org</owner>
   <owner>chrome-worker@google.com</owner>
@@ -157378,6 +157465,10 @@
 
 <histogram name="ServiceWorker.VersionInfo.ScriptURLLength" units="characters"
     expires_after="2020-08-25">
+  <obsolete>
+    No longer needed as found the cause of stability issue to be number of
+    registrations rather than size of registrations. Removed in July 2020.
+  </obsolete>
   <owner>nidhijaju@google.com</owner>
   <owner>bashi@chromium.org</owner>
   <owner>chrome-worker@google.com</owner>
@@ -157949,6 +158040,21 @@
   </summary>
 </histogram>
 
+<histogram name="Session.TotalDuration.TouchMode" units="times"
+    expires_after="2021-01-01">
+  <owner>collinbaker@chromium.org</owner>
+  <owner>chrome-desktop-ui-sea@google.com</owner>
+  <summary>
+    Time spent in touch mode in each session (as defined by
+    DesktopSessionDurationTracker). Samples correspond one-to-one with
+    Session.TotalDuration samples.
+
+    This histogram should be analyzed with Session.TotalDuration. For example,
+    the sum of this histogram divided by the sum of Session.TotalDuration is the
+    total proportion of active browsing time spent in touch mode.
+  </summary>
+</histogram>
+
 <histogram name="Session.TotalDuration.WithAccount" units="ms"
     expires_after="never">
 <!-- expires-never: guiding metric (internal: go/chrome-browser-guiding-metrics) -->
@@ -167670,7 +167776,7 @@
 </histogram>
 
 <histogram name="Startup.AfterStartupTaskDelayedUntilTime" units="ms"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>michaeln@chromium.org</owner>
   <summary>
     Time from the process creation until deferred after-startup tasks began
@@ -168182,7 +168288,7 @@
 </histogram>
 
 <histogram name="Startup.BrowserWindow.FirstPaint" units="ms"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>mblsha@yandex-team.ru</owner>
   <summary>
     Time from application start to the time the first Browser window has
@@ -172983,6 +173089,20 @@
   </summary>
 </histogram>
 
+<histogram base="true" name="Sync.ModelTypeStoreCommitWriteBatchOutcome"
+    enum="LevelDBStatus" expires_after="2021-01-31">
+<!-- Name completed by histogram_suffixes name="SyncModelType" -->
+
+  <owner>qjw@chromium.org</owner>
+  <owner>ortuno@chromium.org</owner>
+  <owner>treib@chromium.org</owner>
+  <owner>mastiz@chromium.org</owner>
+  <summary>
+    Record the LevelDB Status outcome for ModelTypeStore CommitWriteBatch
+    operations.
+  </summary>
+</histogram>
+
 <histogram name="Sync.ModelTypeStoreInitResult"
     enum="SyncModelTypeStoreInitResult" expires_after="M77">
   <obsolete>
@@ -181898,7 +182018,7 @@
 </histogram>
 
 <histogram name="UMA.TruncatedEvents.UserAction" units="events"
-    expires_after="2021-01-03">
+    expires_after="2021-01-10">
   <owner>rkaplow@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
   <summary>
@@ -183778,7 +183898,7 @@
 </histogram>
 
 <histogram name="V8.DebugFeatureUsage" enum="V8DebugFeature"
-    expires_after="2020-11-01">
+    expires_after="2021-01-10">
   <owner>yangguo@chromium.org</owner>
   <summary>
     Debugger feature used at least once per isolate, recorded on first use.
@@ -186684,7 +186804,7 @@
   </summary>
 </histogram>
 
-<histogram name="VRSessionTime" units="ms" expires_after="2020-11-08">
+<histogram name="VRSessionTime" units="ms" expires_after="2021-01-10">
   <owner>alcooper@chromium.org</owner>
   <owner>xr-dev@chromium.org</owner>
   <summary>
@@ -186724,7 +186844,7 @@
   </summary>
 </histogram>
 
-<histogram name="VRViewerType" enum="VRViewerType" expires_after="2020-11-08">
+<histogram name="VRViewerType" enum="VRViewerType" expires_after="2021-01-10">
   <owner>alcooper@chromium.org</owner>
   <owner>xr-dev@chromium.org</owner>
   <summary>The type of headset being used for VR.</summary>
@@ -187364,7 +187484,7 @@
 </histogram>
 
 <histogram name="WebApk.Session.TotalDuration2" units="ms"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>hartmanng@chromium.org</owner>
   <owner>
     src/chrome/android/java/src/org/chromium/chrome/browser/webapps/OWNERS
@@ -192927,7 +193047,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.EndToEndDelayMaxInMs" units="ms"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>ilnik@chromium.org</owner>
   <owner>webrtc-video@google.com</owner>
   <summary>
@@ -195737,7 +195857,7 @@
 </histogram>
 
 <histogram name="XR.WebXR.ReferenceSpace.Succeeded" enum="XRReferenceSpaceType"
-    expires_after="2020-11-08">
+    expires_after="2021-01-10">
   <owner>alcooper@chromium.org</owner>
   <owner>xr-dev@chromium.org</owner>
   <summary>
@@ -213533,6 +213653,7 @@
   <affected-histogram name="Sync.ModelTypeEntityChange3"/>
   <affected-histogram name="Sync.ModelTypeErrorSite"/>
   <affected-histogram name="Sync.ModelTypeMemoryKB"/>
+  <affected-histogram name="Sync.ModelTypeStoreCommitWriteBatchOutcome"/>
   <affected-histogram name="Sync.NonReflectionUpdateFreshnessPossiblySkewed">
     <obsolete>
       Deprecated 06/2019. Replaced by
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index 594445e..7b78668 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -7895,6 +7895,16 @@
       for quiescent windows. The unit of time is ms.
     </summary>
   </metric>
+  <metric name="Experimental.PageLoadType" enum="PageLoadType">
+    <summary>
+      Determines the PageLoadType for this page load. That is, records whether
+      the page was never foregrounded, was foregrounded but did not reach FCP,
+      or did reach FCP. The metric is recorded at the end of each page load. As
+      usual with PageLoad metrics, we consider app backgrounding on Android to
+      be the end of page load as well as the app could be evicted by Android
+      anytime after that happens.
+    </summary>
+  </metric>
   <metric name="Experimental.PaintTiming.NavigationToFirstMeaningfulPaint">
     <summary>
       Measures the time in milliseconds from navigation timing's navigation
@@ -8005,6 +8015,20 @@
       http://bit.ly/fcp_plus_plus for more details.
     </summary>
   </metric>
+  <metric name="Experimental.TotalForegroundDuration">
+    <summary>
+      The total amount of time the page spent in the foreground, in
+      milliseconds. Note that this metric adds all foreground durations
+      occurring for the page. For example: if the page starts foregrounded
+      during 1 second, then goes to the background for a while, then is
+      foregrounded again for 2 seconds, and then is unloaded, this metric
+      reports 3 seconds. To see metrics about consecutive foreground sessions,
+      see the PageForegroundSession event instead. This metric is recorded at
+      the end of each page load. As usual with PageLoad metrics, we consider app
+      backgrounding on Android to be the end of page load as well as the app
+      could be evicted by Android anytime after that happens.
+    </summary>
+  </metric>
   <metric name="InteractiveTiming.FirstInputDelay">
     <obsolete>
       Deprecated on January 2019 in favor of InteractiveTiming.FirstInputDelay2,
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index c18946d..5facddd 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -1,16 +1,16 @@
 {
     "trace_processor_shell": {
         "win": {
-            "hash": "f1007a52f9325c87a36af6253cad89cdcc7db17e",
-            "remote_path": "perfetto_binaries/trace_processor_shell/win/85cf057d6c800c6722161c2e190ee082f0262adb/trace_processor_shell.exe"
+            "hash": "407d980fe52940546808b5ab66dfdda8e127e602",
+            "remote_path": "perfetto_binaries/trace_processor_shell/win/46adebf8761fcb2d9a93930d047c0e72702942cf/trace_processor_shell.exe"
         },
         "mac": {
-            "hash": "16faf26a7fe008bb05baeebf3e739c75cf3dccce",
-            "remote_path": "perfetto_binaries/trace_processor_shell/mac/af5653134716efec117a9428c7f9b44d3e5fbb1d/trace_processor_shell"
+            "hash": "b74caa5ca3d386d41b113730a359de1eaf5ae3cd",
+            "remote_path": "perfetto_binaries/trace_processor_shell/mac/46adebf8761fcb2d9a93930d047c0e72702942cf/trace_processor_shell"
         },
         "linux": {
-            "hash": "0cda4c1bf5f7b9a4c99f712de4491ced0e45fc60",
-            "remote_path": "perfetto_binaries/trace_processor_shell/linux/85cf057d6c800c6722161c2e190ee082f0262adb/trace_processor_shell"
+            "hash": "05d83747076d72c60d4cf8131fb547a74258312b",
+            "remote_path": "perfetto_binaries/trace_processor_shell/linux/46adebf8761fcb2d9a93930d047c0e72702942cf/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/perf/core/results_dashboard.py b/tools/perf/core/results_dashboard.py
index d8dd7736..f3c179d 100755
--- a/tools/perf/core/results_dashboard.py
+++ b/tools/perf/core/results_dashboard.py
@@ -99,7 +99,7 @@
         _SendHistogramJson(url, dashboard_data_str, token_generator_callback)
       else:
         # TODO(eakuefner): Remove this logic once all bots use histograms.
-        _SendResultsJson(url, dashboard_data_str)
+        _SendResultsJson(url, dashboard_data_str, token_generator_callback)
       all_data_uploaded = True
       break
     except SendResultsRetryException as e:
@@ -425,7 +425,7 @@
   return test_path
 
 
-def _SendResultsJson(url, results_json):
+def _SendResultsJson(url, results_json, token_generator_callback):
   """Make a HTTP POST with the given JSON to the Performance Dashboard.
 
   Args:
@@ -441,6 +441,9 @@
   data = urllib.urlencode({'data': results_json})
   req = urllib2.Request(url + SEND_RESULTS_PATH, data)
   try:
+    oauth_token = token_generator_callback()
+    req.headers['Authorization'] = 'Bearer %s' % oauth_token
+
     urllib2.urlopen(req, timeout=60 * 5)
   except (urllib2.HTTPError, urllib2.URLError, httplib.HTTPException):
     error = traceback.format_exc()
diff --git a/ui/accessibility/BUILD.gn b/ui/accessibility/BUILD.gn
index 0297bff..f299b5f 100644
--- a/ui/accessibility/BUILD.gn
+++ b/ui/accessibility/BUILD.gn
@@ -94,6 +94,8 @@
     "ax_action_data.h",
     "ax_action_handler.cc",
     "ax_action_handler.h",
+    "ax_action_handler_base.cc",
+    "ax_action_handler_base.h",
     "ax_action_target.h",
     "ax_active_popup.cc",
     "ax_active_popup.h",
diff --git a/ui/accessibility/ax_action_handler.cc b/ui/accessibility/ax_action_handler.cc
index a570205..3ac9ce8 100644
--- a/ui/accessibility/ax_action_handler.cc
+++ b/ui/accessibility/ax_action_handler.cc
@@ -8,15 +8,8 @@
 
 namespace ui {
 
-bool AXActionHandler::RequiresPerformActionPointInPixels() const {
-  return false;
-}
-
 AXActionHandler::AXActionHandler()
-    : tree_id_(AXTreeIDRegistry::GetInstance()->GetOrCreateAXTreeID(this)) {}
-
-AXActionHandler::~AXActionHandler() {
-  AXTreeIDRegistry::GetInstance()->RemoveAXTreeID(tree_id_);
-}
+    : AXActionHandlerBase(
+          AXTreeIDRegistry::GetInstance()->GetOrCreateAXTreeID(this)) {}
 
 }  // namespace ui
diff --git a/ui/accessibility/ax_action_handler.h b/ui/accessibility/ax_action_handler.h
index 0374ca1..d999a0e 100644
--- a/ui/accessibility/ax_action_handler.h
+++ b/ui/accessibility/ax_action_handler.h
@@ -5,43 +5,20 @@
 #ifndef UI_ACCESSIBILITY_AX_ACTION_HANDLER_H_
 #define UI_ACCESSIBILITY_AX_ACTION_HANDLER_H_
 
+#include "ui/accessibility/ax_action_handler_base.h"
 #include "ui/accessibility/ax_export.h"
-#include "ui/accessibility/ax_tree_id.h"
 
 namespace ui {
 
-struct AXActionData;
-
-// Classes that host an accessibility tree in the browser process that also wish
-// to become visible to accessibility clients (e.g. for relaying targets to
-// source accessibility trees), can subclass this class.
+// The class you normally want to inherit from other classes when you want to
+// make them visible to accessibility clients, since it automatically registers
+// a valid AXTreeID with the AXTreeIDRegistry when constructing the instance.
 //
-// Subclasses can use |tree_id| when annotating their |AXNodeData| for clients
-// to respond with the appropriate target node id.
-class AX_EXPORT AXActionHandler {
- public:
-  virtual ~AXActionHandler();
-
-  // Handle an action from an accessibility client.
-  virtual void PerformAction(const AXActionData& data) = 0;
-
-  // Returns whether this handler expects points in pixels (true) or dips
-  // (false) for data passed to |PerformAction|.
-  virtual bool RequiresPerformActionPointInPixels() const;
-
-  // A tree id appropriate for annotating events sent to an accessibility
-  // client.
-  const AXTreeID& ax_tree_id() const { return tree_id_; }
-
+// If you need more control over how the AXTreeID associated to this class is
+// set, please inherit directly from AXActionHandlerBase instead.
+class AX_EXPORT AXActionHandler : public AXActionHandlerBase {
  protected:
   AXActionHandler();
-
- private:
-  // Register or unregister this class with |AXTreeIDRegistry|.
-  void UpdateActiveState(bool active);
-
-  // Automatically assigned.
-  AXTreeID tree_id_;
 };
 
 }  // namespace ui
diff --git a/ui/accessibility/ax_action_handler_base.cc b/ui/accessibility/ax_action_handler_base.cc
new file mode 100644
index 0000000..43edc1e2
--- /dev/null
+++ b/ui/accessibility/ax_action_handler_base.cc
@@ -0,0 +1,32 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/accessibility/ax_action_handler_base.h"
+
+#include "ui/accessibility/ax_tree_id_registry.h"
+
+namespace ui {
+
+bool AXActionHandlerBase::RequiresPerformActionPointInPixels() const {
+  return false;
+}
+
+AXActionHandlerBase::AXActionHandlerBase()
+    : AXActionHandlerBase(ui::AXTreeIDUnknown()) {}
+
+AXActionHandlerBase::AXActionHandlerBase(const AXTreeID& ax_tree_id)
+    : tree_id_(ax_tree_id) {}
+
+AXActionHandlerBase::~AXActionHandlerBase() {
+  AXTreeIDRegistry::GetInstance()->RemoveAXTreeID(tree_id_);
+}
+
+void AXActionHandlerBase::SetAXTreeID(AXTreeID new_ax_tree_id) {
+  DCHECK_NE(new_ax_tree_id, ui::AXTreeIDUnknown());
+  AXTreeIDRegistry::GetInstance()->RemoveAXTreeID(tree_id_);
+  tree_id_ = new_ax_tree_id;
+  AXTreeIDRegistry::GetInstance()->SetAXTreeID(tree_id_, this);
+}
+
+}  // namespace ui
diff --git a/ui/accessibility/ax_action_handler_base.h b/ui/accessibility/ax_action_handler_base.h
new file mode 100644
index 0000000..2e2b5b09
--- /dev/null
+++ b/ui/accessibility/ax_action_handler_base.h
@@ -0,0 +1,60 @@
+// Copyright 2020 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_ACCESSIBILITY_AX_ACTION_HANDLER_BASE_H_
+#define UI_ACCESSIBILITY_AX_ACTION_HANDLER_BASE_H_
+
+#include "ui/accessibility/ax_export.h"
+#include "ui/accessibility/ax_tree_id.h"
+
+namespace ui {
+
+struct AXActionData;
+
+// Classes that host an accessibility tree in the browser process that also wish
+// to become visible to accessibility clients (e.g. for relaying targets to
+// source accessibility trees), can subclass this class. However, unless you
+// need to have more control over how |tree_id_| is set, most classes will want
+// to inherit from AXActionHandler instead, which manages it automatically.
+//
+// Subclasses can use |tree_id| when annotating their |AXNodeData| for clients
+// to respond with the appropriate target node id.
+class AX_EXPORT AXActionHandlerBase {
+ public:
+  virtual ~AXActionHandlerBase();
+
+  // Handle an action from an accessibility client.
+  virtual void PerformAction(const AXActionData& data) = 0;
+
+  // Returns whether this handler expects points in pixels (true) or dips
+  // (false) for data passed to |PerformAction|.
+  virtual bool RequiresPerformActionPointInPixels() const;
+
+  // A tree id appropriate for annotating events sent to an accessibility
+  // client.
+  const AXTreeID& ax_tree_id() const { return tree_id_; }
+
+ protected:
+  // Initializes the AXActionHandlerBase subclass with ui::AXTreeIDUnknown().
+  AXActionHandlerBase();
+
+  // Initializes the AXActionHandlerBase subclass with |ax_tree_id|. It is Ok to
+  // pass ui::AXTreeIDUnknown() and then call SetAXTreeID() at a later point.
+  explicit AXActionHandlerBase(const AXTreeID& ax_tree_id);
+
+  // Change the AXTreeID.
+  void SetAXTreeID(AXTreeID new_ax_tree_id);
+
+ private:
+  // Register or unregister this class with |AXTreeIDRegistry|.
+  void UpdateActiveState(bool active);
+
+  // Manually set in this base class, but automatically set by instances of the
+  // subclass AXActionHandler, which most classes inherit from.
+  AXTreeID tree_id_;
+};
+
+}  // namespace ui
+
+#endif  // UI_ACCESSIBILITY_AX_ACTION_HANDLER_BASE_H_
diff --git a/ui/accessibility/ax_tree_id_registry.cc b/ui/accessibility/ax_tree_id_registry.cc
index da65446..2c816da6 100644
--- a/ui/accessibility/ax_tree_id_registry.cc
+++ b/ui/accessibility/ax_tree_id_registry.cc
@@ -6,7 +6,7 @@
 
 #include "base/memory/singleton.h"
 #include "base/strings/string_number_conversions.h"
-#include "ui/accessibility/ax_action_handler.h"
+#include "ui/accessibility/ax_action_handler_base.h"
 
 namespace ui {
 
@@ -44,7 +44,7 @@
   return ui::AXTreeIDUnknown();
 }
 
-AXTreeID AXTreeIDRegistry::GetOrCreateAXTreeID(AXActionHandler* handler) {
+AXTreeID AXTreeIDRegistry::GetOrCreateAXTreeID(AXActionHandlerBase* handler) {
   for (auto it : id_to_action_handler_) {
     if (it.second == handler)
       return it.first;
@@ -54,13 +54,19 @@
   return new_id;
 }
 
-AXActionHandler* AXTreeIDRegistry::GetActionHandler(AXTreeID ax_tree_id) {
+AXActionHandlerBase* AXTreeIDRegistry::GetActionHandler(AXTreeID ax_tree_id) {
   auto it = id_to_action_handler_.find(ax_tree_id);
   if (it == id_to_action_handler_.end())
     return nullptr;
   return it->second;
 }
 
+void AXTreeIDRegistry::SetAXTreeID(const ui::AXTreeID& id,
+                                   AXActionHandlerBase* action_handler) {
+  DCHECK(id_to_action_handler_.find(id) == id_to_action_handler_.end());
+  id_to_action_handler_[id] = action_handler;
+}
+
 void AXTreeIDRegistry::RemoveAXTreeID(AXTreeID ax_tree_id) {
   auto frame_it = ax_tree_to_frame_id_map_.find(ax_tree_id);
   if (frame_it != ax_tree_to_frame_id_map_.end()) {
diff --git a/ui/accessibility/ax_tree_id_registry.h b/ui/accessibility/ax_tree_id_registry.h
index 44405bd7..8ccbfee 100644
--- a/ui/accessibility/ax_tree_id_registry.h
+++ b/ui/accessibility/ax_tree_id_registry.h
@@ -10,6 +10,7 @@
 #include <utility>
 
 #include "base/macros.h"
+#include "ui/accessibility/ax_action_handler.h"
 #include "ui/accessibility/ax_export.h"
 #include "ui/accessibility/ax_tree_id.h"
 
@@ -20,12 +21,12 @@
 
 namespace ui {
 
-class AXActionHandler;
+class AXActionHandlerBase;
 
 // This class generates and saves a runtime id for an accessibility tree.
 // It provides a few distinct forms of generating an id:
 //     - from a frame id (which consists of a process and routing id)
-//     - from a backing |AXActionHandler| object
+//     - from a backing |AXActionHandlerBase| object
 //
 // The first form allows underlying instances to change but refer to the same
 // frame.
@@ -43,8 +44,8 @@
   // Gets an ax tree id from a frame id.
   AXTreeID GetAXTreeID(FrameID frame_id);
 
-  // Retrieve an |AXActionHandler| based on an ax tree id.
-  AXActionHandler* GetActionHandler(AXTreeID ax_tree_id);
+  // Retrieve an |AXActionHandlerBase| based on an ax tree id.
+  AXActionHandlerBase* GetActionHandler(AXTreeID ax_tree_id);
 
   // Removes an ax tree id, and its associated delegate and frame id (if it
   // exists).
@@ -57,9 +58,14 @@
  private:
   friend struct base::DefaultSingletonTraits<AXTreeIDRegistry>;
   friend AXActionHandler;
+  friend AXActionHandlerBase;
 
   // Get or create a ax tree id keyed on |handler|.
-  AXTreeID GetOrCreateAXTreeID(AXActionHandler* handler);
+  AXTreeID GetOrCreateAXTreeID(AXActionHandlerBase* handler);
+
+  // Set a mapping between an AXTreeID and AXActionHandlerBase explicitly.
+  void SetAXTreeID(const AXTreeID& ax_tree_id,
+                   AXActionHandlerBase* action_handler);
 
   AXTreeIDRegistry();
   virtual ~AXTreeIDRegistry();
@@ -71,7 +77,7 @@
   std::map<FrameID, AXTreeID> frame_to_ax_tree_id_map_;
 
   // Maps an id to its handler.
-  std::map<AXTreeID, AXActionHandler*> id_to_action_handler_;
+  std::map<AXTreeID, AXActionHandlerBase*> id_to_action_handler_;
 
   DISALLOW_COPY_AND_ASSIGN(AXTreeIDRegistry);
 };
diff --git a/ui/base/clipboard/clipboard_data.cc b/ui/base/clipboard/clipboard_data.cc
index e7f1e7d..f70e3a1 100644
--- a/ui/base/clipboard/clipboard_data.cc
+++ b/ui/base/clipboard/clipboard_data.cc
@@ -8,6 +8,7 @@
 
 #include "base/notreached.h"
 #include "skia/ext/skia_utils_base.h"
+#include "ui/gfx/skia_util.h"
 
 namespace ui {
 
@@ -18,8 +19,6 @@
 ClipboardData::~ClipboardData() = default;
 
 bool ClipboardData::operator==(const ClipboardData& that) const {
-  // TODO(https://crbug.com/1102513): This does not check for equality of
-  // bitmaps. Hash them for comparison.
   return format_ == that.format() && text_ == that.text() &&
          markup_data_ == that.markup_data() && url_ == that.url() &&
          rtf_data_ == that.rtf_data() &&
@@ -27,7 +26,8 @@
          bookmark_url_ == that.bookmark_url() &&
          custom_data_format_ == that.custom_data_format() &&
          custom_data_data_ == that.custom_data_data() &&
-         web_smart_paste_ == that.web_smart_paste();
+         web_smart_paste_ == that.web_smart_paste() &&
+         gfx::BitmapsAreEqual(bitmap_, that.bitmap());
 }
 
 void ClipboardData::SetBitmapData(const SkBitmap& bitmap) {
diff --git a/ui/base/clipboard/clipboard_ozone.cc b/ui/base/clipboard/clipboard_ozone.cc
index a41bc2f..561e2909 100644
--- a/ui/base/clipboard/clipboard_ozone.cc
+++ b/ui/base/clipboard/clipboard_ozone.cc
@@ -487,15 +487,10 @@
     auto text_iter = objects.find(PortableFormat::kText);
     if (text_iter != objects.end()) {
       const ObjectMapParams& params_vector = text_iter->second;
-      if (params_vector.size()) {
+      if (!params_vector.empty()) {
         const ObjectMapParam& char_vector = params_vector[0];
-        const uint8_t* uint8_data =
-            reinterpret_cast<const uint8_t*>(char_vector.data());
-        if (char_vector.size()) {
-          std::vector<uint8_t> data(uint8_data,
-                                    uint8_data + char_vector.size());
-          async_clipboard_ozone_->InsertData(std::move(data), kMimeTypeText);
-        }
+        if (!char_vector.empty())
+          WriteText(&char_vector.front(), char_vector.size());
       }
       async_clipboard_ozone_->OfferData(ClipboardBuffer::kSelection);
     }
diff --git a/ui/base/dragdrop/os_exchange_data_provider_win.cc b/ui/base/dragdrop/os_exchange_data_provider_win.cc
index b51a016..66b83037 100644
--- a/ui/base/dragdrop/os_exchange_data_provider_win.cc
+++ b/ui/base/dragdrop/os_exchange_data_provider_win.cc
@@ -120,7 +120,7 @@
   // The cursor of the active enumeration - an index into |contents_|.
   size_t cursor_;
 
-  LONG ref_count_;
+  ULONG ref_count_;
 
   DISALLOW_COPY_AND_ASSIGN(FormatEtcEnumerator);
 };
diff --git a/ui/base/ime/win/tsf_input_scope.cc b/ui/base/ime/win/tsf_input_scope.cc
index 54e009e..9db8c86 100644
--- a/ui/base/ime/win/tsf_input_scope.cc
+++ b/ui/base/ime/win/tsf_input_scope.cc
@@ -96,8 +96,8 @@
   // The corresponding text input types.
   std::vector<InputScope> input_scopes_;
 
-  // The refrence count of this instance.
-  volatile LONG ref_count_;
+  // The reference count of this instance.
+  volatile ULONG ref_count_;
 
   DISALLOW_COPY_AND_ASSIGN(TSFInputScope);
 };
diff --git a/ui/base/pointer/touch_ui_controller.cc b/ui/base/pointer/touch_ui_controller.cc
index 07beade..326da1d 100644
--- a/ui/base/pointer/touch_ui_controller.cc
+++ b/ui/base/pointer/touch_ui_controller.cc
@@ -56,6 +56,11 @@
   controller_->SetTouchUiState(old_state_);
 }
 
+void TouchUiController::TouchUiScoperForTesting::UpdateState(bool enabled) {
+  controller_->SetTouchUiState(enabled ? TouchUiState::kEnabled
+                                       : TouchUiState::kDisabled);
+}
+
 // static
 TouchUiController* TouchUiController::Get() {
   static base::NoDestructor<TouchUiController> instance([] {
diff --git a/ui/base/pointer/touch_ui_controller.h b/ui/base/pointer/touch_ui_controller.h
index ba7aa42..0240f4e 100644
--- a/ui/base/pointer/touch_ui_controller.h
+++ b/ui/base/pointer/touch_ui_controller.h
@@ -40,6 +40,11 @@
     TouchUiScoperForTesting& operator=(const TouchUiScoperForTesting&) = delete;
     ~TouchUiScoperForTesting();
 
+    // Update the current touch mode state but still roll back to the
+    // original state at destruction. Allows a test to change the mode
+    // multiple times without creating multiple instances.
+    void UpdateState(bool enabled);
+
    private:
     TouchUiController* const controller_;
     const TouchUiState old_state_;
diff --git a/ui/display/manager/display_configurator.cc b/ui/display/manager/display_configurator.cc
index 3077495..7f7c2bbc 100644
--- a/ui/display/manager/display_configurator.cc
+++ b/ui/display/manager/display_configurator.cc
@@ -568,7 +568,6 @@
       configure_display_(chromeos::IsRunningAsSystemCompositor()),
       current_display_state_(MULTIPLE_DISPLAY_STATE_INVALID),
       current_power_state_(chromeos::DISPLAY_POWER_ALL_ON),
-      current_internal_display_(nullptr),
       requested_display_state_(MULTIPLE_DISPLAY_STATE_INVALID),
       pending_power_state_(chromeos::DISPLAY_POWER_ALL_ON),
       has_pending_power_state_(false),
@@ -783,28 +782,22 @@
                      display_id, degamma_lut, gamma_lut));
 }
 
-bool DisplayConfigurator::IsPrivacyScreenSupportedOnInternalDisplay() const {
-  return current_internal_display_ &&
-         current_internal_display_->privacy_screen_state() != kNotSupported &&
-         current_internal_display_->current_mode();
-}
-
-bool DisplayConfigurator::SetPrivacyScreenOnInternalDisplay(bool enabled) {
-  if (IsPrivacyScreenSupportedOnInternalDisplay()) {
-    native_display_delegate_->SetPrivacyScreen(
-        current_internal_display_->display_id(), enabled);
-    return true;
+void DisplayConfigurator::SetPrivacyScreen(int64_t display_id, bool enabled) {
+#if DCHECK_IS_ON()
+  DisplaySnapshot* internal_display = nullptr;
+  for (DisplaySnapshot* display : cached_displays_) {
+    if (display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL) {
+      internal_display = display;
+      break;
+    }
   }
+  DCHECK(internal_display);
+  DCHECK_EQ(internal_display->display_id(), display_id);
+  DCHECK_NE(internal_display->privacy_screen_state(), kNotSupported);
+  DCHECK(internal_display->current_mode());
+#endif
 
-  if (!current_internal_display_) {
-    LOG(ERROR) << "This device does not have an internal display.";
-  } else if (current_internal_display_->privacy_screen_state() ==
-             kNotSupported) {
-    LOG(ERROR) << "The internal display of this device does not support "
-                  "privacy screen.";
-  }
-
-  return false;
+  native_display_delegate_->SetPrivacyScreen(display_id, enabled);
 }
 
 chromeos::DisplayPowerState DisplayConfigurator::GetRequestedPowerState()
@@ -812,16 +805,6 @@
   return requested_power_state_.value_or(chromeos::DISPLAY_POWER_ALL_ON);
 }
 
-void DisplayConfigurator::UpdateInternalDisplayCache() {
-  for (DisplaySnapshot* display : cached_displays_) {
-    if (display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL) {
-      current_internal_display_ = display;
-      return;
-    }
-  }
-  current_internal_display_ = nullptr;
-}
-
 void DisplayConfigurator::PrepareForExit() {
   configure_display_ = false;
 }
@@ -1034,9 +1017,6 @@
   if (success) {
     current_display_state_ = new_display_state;
     UpdatePowerState(new_power_state);
-    UpdateInternalDisplayCache();
-  } else {
-    current_internal_display_ = nullptr;
   }
 
   configuration_task_.reset();
diff --git a/ui/display/manager/display_configurator.h b/ui/display/manager/display_configurator.h
index e435a06..fcf74a0b 100644
--- a/ui/display/manager/display_configurator.h
+++ b/ui/display/manager/display_configurator.h
@@ -266,11 +266,9 @@
                           const std::vector<GammaRampRGBEntry>& degamma_lut,
                           const std::vector<GammaRampRGBEntry>& gamma_lut);
 
-  // Enable/disable the privacy screen on the internal display of the device.
-  // For this to succeed, privacy screen must be supported by the internal
-  // display.
-  bool SetPrivacyScreenOnInternalDisplay(bool enabled);
-  bool IsPrivacyScreenSupportedOnInternalDisplay() const;
+  // Enable/disable the privacy screen on display with |display_id|.
+  // For this to succeed, privacy screen must be supported by the display.
+  void SetPrivacyScreen(int64_t display_id, bool enabled);
 
   // Returns the requested power state if set or the default power state.
   chromeos::DisplayPowerState GetRequestedPowerState() const;
@@ -372,7 +370,6 @@
   // Current configuration state.
   MultipleDisplayState current_display_state_;
   chromeos::DisplayPowerState current_power_state_;
-  DisplaySnapshot* current_internal_display_;  // Not owned.
 
   // Pending requests. These values are used when triggering the next display
   // configuration.
diff --git a/ui/display/manager/display_configurator_unittest.cc b/ui/display/manager/display_configurator_unittest.cc
index 538f0c4d..a650f954 100644
--- a/ui/display/manager/display_configurator_unittest.cc
+++ b/ui/display/manager/display_configurator_unittest.cc
@@ -1451,63 +1451,6 @@
   EXPECT_EQ(chromeos::DISPLAY_POWER_ALL_ON, observer_.latest_power_state());
 }
 
-TEST_F(DisplayConfiguratorTest, EnablePrivacyScreenOnSupportedEmbeddedDisplay) {
-  outputs_[0] = FakeDisplaySnapshot::Builder()
-                    .SetId(kDisplayIds[0])
-                    .SetNativeMode(small_mode_.Clone())
-                    .SetCurrentMode(small_mode_.Clone())
-                    .AddMode(big_mode_.Clone())
-                    .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
-                    .SetIsAspectPerservingScaling(true)
-                    .SetPrivacyScreen(kDisabled)
-                    .Build();
-
-  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_SINGLE);
-  InitWithOutputs(&small_mode_);
-  observer_.Reset();
-
-  EXPECT_TRUE(configurator_.SetPrivacyScreenOnInternalDisplay(true));
-  EXPECT_EQ(SetPrivacyScreenAction(kDisplayIds[0], true),
-            log_->GetActionsAndClear());
-}
-
-TEST_F(DisplayConfiguratorTest,
-       EnablePrivacyScreenOnUnsupportedEmbeddedDisplay) {
-  outputs_[0] = FakeDisplaySnapshot::Builder()
-                    .SetId(kDisplayIds[0])
-                    .SetNativeMode(big_mode_.Clone())
-                    .SetCurrentMode(big_mode_.Clone())
-                    .AddMode(small_mode_.Clone())
-                    .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
-                    .SetIsAspectPerservingScaling(true)
-                    .SetPrivacyScreen(kNotSupported)
-                    .Build();
-  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_SINGLE);
-  InitWithOutputs(&big_mode_);
-  observer_.Reset();
-
-  EXPECT_FALSE(configurator_.SetPrivacyScreenOnInternalDisplay(true));
-  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
-}
-
-TEST_F(DisplayConfiguratorTest, EnablePrivacyScreenOnExternalDisplay) {
-  outputs_[0] = FakeDisplaySnapshot::Builder()
-                    .SetId(kDisplayIds[0])
-                    .SetNativeMode(small_mode_.Clone())
-                    .SetCurrentMode(small_mode_.Clone())
-                    .SetType(DISPLAY_CONNECTION_TYPE_DISPLAYPORT)
-                    .SetIsAspectPerservingScaling(true)
-                    .SetPrivacyScreen(kNotSupported)
-                    .Build();
-
-  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_SINGLE);
-  InitWithOutputs(&small_mode_);
-  observer_.Reset();
-
-  EXPECT_FALSE(configurator_.SetPrivacyScreenOnInternalDisplay(true));
-  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
-}
-
 class DisplayConfiguratorMultiMirroringTest : public DisplayConfiguratorTest {
  public:
   DisplayConfiguratorMultiMirroringTest() = default;
diff --git a/ui/display/manager/display_manager_utilities.cc b/ui/display/manager/display_manager_utilities.cc
index ac8acd8..e5bda9d 100644
--- a/ui/display/manager/display_manager_utilities.cc
+++ b/ui/display/manager/display_manager_utilities.cc
@@ -73,13 +73,10 @@
   return ret;
 }
 
-bool ComputeBoundary(const Display& a_display,
-                     const Display& b_display,
-                     gfx::Rect* a_edge_in_screen,
-                     gfx::Rect* b_edge_in_screen) {
-  const gfx::Rect& a_bounds = a_display.bounds();
-  const gfx::Rect& b_bounds = b_display.bounds();
-
+bool ComputeBoundary(const gfx::Rect& a_bounds,
+                     const gfx::Rect& b_bounds,
+                     gfx::Rect* a_edge,
+                     gfx::Rect* b_edge) {
   // Find touching side.
   int rx = std::max(a_bounds.x(), b_bounds.x());
   int ry = std::max(a_bounds.y(), b_bounds.y());
@@ -125,11 +122,11 @@
       int left = std::max(a_bounds.x(), b_bounds.x());
       int right = std::min(a_bounds.right(), b_bounds.right());
       if (position == DisplayPlacement::TOP) {
-        a_edge_in_screen->SetRect(left, a_bounds.y(), right - left, 1);
-        b_edge_in_screen->SetRect(left, b_bounds.bottom() - 1, right - left, 1);
+        a_edge->SetRect(left, a_bounds.y(), right - left, 1);
+        b_edge->SetRect(left, b_bounds.bottom() - 1, right - left, 1);
       } else {
-        a_edge_in_screen->SetRect(left, a_bounds.bottom() - 1, right - left, 1);
-        b_edge_in_screen->SetRect(left, b_bounds.y(), right - left, 1);
+        a_edge->SetRect(left, a_bounds.bottom() - 1, right - left, 1);
+        b_edge->SetRect(left, b_bounds.y(), right - left, 1);
       }
       break;
     }
@@ -138,11 +135,11 @@
       int top = std::max(a_bounds.y(), b_bounds.y());
       int bottom = std::min(a_bounds.bottom(), b_bounds.bottom());
       if (position == DisplayPlacement::LEFT) {
-        a_edge_in_screen->SetRect(a_bounds.x(), top, 1, bottom - top);
-        b_edge_in_screen->SetRect(b_bounds.right() - 1, top, 1, bottom - top);
+        a_edge->SetRect(a_bounds.x(), top, 1, bottom - top);
+        b_edge->SetRect(b_bounds.right() - 1, top, 1, bottom - top);
       } else {
-        a_edge_in_screen->SetRect(a_bounds.right() - 1, top, 1, bottom - top);
-        b_edge_in_screen->SetRect(b_bounds.x(), top, 1, bottom - top);
+        a_edge->SetRect(a_bounds.right() - 1, top, 1, bottom - top);
+        b_edge->SetRect(b_bounds.x(), top, 1, bottom - top);
       }
       break;
     }
@@ -150,6 +147,14 @@
   return true;
 }
 
+bool ComputeBoundary(const Display& display_a,
+                     const Display& display_b,
+                     gfx::Rect* a_edge_in_screen,
+                     gfx::Rect* b_edge_in_screen) {
+  return ComputeBoundary(display_a.bounds(), display_b.bounds(),
+                         a_edge_in_screen, b_edge_in_screen);
+}
+
 DisplayIdList CreateDisplayIdList(const Displays& list) {
   return GenerateDisplayIdList(
       list.begin(), list.end(),
diff --git a/ui/display/manager/display_manager_utilities.h b/ui/display/manager/display_manager_utilities.h
index 99a957a..c326941 100644
--- a/ui/display/manager/display_manager_utilities.h
+++ b/ui/display/manager/display_manager_utilities.h
@@ -50,13 +50,21 @@
 // internal display.
 bool ForceFirstDisplayInternal();
 
-// Computes the bounds that defines the bounds between two displays.
-// Returns false if two displays do not intersect.
-DISPLAY_MANAGER_EXPORT bool ComputeBoundary(
-    const Display& primary_display,
-    const Display& secondary_display,
-    gfx::Rect* primary_edge_in_screen,
-    gfx::Rect* secondary_edge_in_screen);
+// If |a_bounds| and |b_bounds| share an edge, the shared edges are computed and
+// filled in |a_edge| and |b_edge|, and true is returned. Otherwise, it returns
+// false.
+DISPLAY_MANAGER_EXPORT bool ComputeBoundary(const gfx::Rect& a_bounds,
+                                            const gfx::Rect& b_bounds,
+                                            gfx::Rect* a_edge,
+                                            gfx::Rect* b_edge);
+
+// If |display_a| and |display_b| share an edge, the shared edges are computed
+// and filled in |a_edge_in_screen| and |b_edge_in_screen|, and true is
+// returned. Otherwise, it returns false.
+DISPLAY_MANAGER_EXPORT bool ComputeBoundary(const Display& display_a,
+                                            const Display& display_b,
+                                            gfx::Rect* a_edge_in_screen,
+                                            gfx::Rect* b_edge_in_screen);
 
 // Sorts id list using |CompareDisplayIds| below.
 DISPLAY_MANAGER_EXPORT void SortDisplayIdList(DisplayIdList* list);
diff --git a/ui/gfx/color_space.cc b/ui/gfx/color_space.cc
index b66b0c6..8ebf9197 100644
--- a/ui/gfx/color_space.cc
+++ b/ui/gfx/color_space.cc
@@ -152,6 +152,12 @@
 }
 
 // static
+ColorSpace ColorSpace::CreateHLG() {
+  return ColorSpace(PrimaryID::BT2020, TransferID::ARIB_STD_B67, MatrixID::RGB,
+                    RangeID::FULL);
+}
+
+// static
 ColorSpace ColorSpace::CreatePiecewiseHDR(
     PrimaryID primaries,
     float sdr_joint,
diff --git a/ui/gfx/color_space.h b/ui/gfx/color_space.h
index ab418e1..63f456c 100644
--- a/ui/gfx/color_space.h
+++ b/ui/gfx/color_space.h
@@ -192,6 +192,9 @@
   // HDR10 uses BT.2020 primaries with SMPTE ST 2084 PQ transfer function.
   static ColorSpace CreateHDR10(float sdr_white_point = 0.f);
 
+  // HLG uses the BT.2020 primaries with the ARIB_STD_B67 transfer function.
+  static ColorSpace CreateHLG();
+
   // Create a piecewise-HDR color space.
   // - If |primaries| is CUSTOM, then |custom_primary_matrix| must be
   //   non-nullptr.
diff --git a/ui/gfx/x/connection.cc b/ui/gfx/x/connection.cc
index a24dd8c..dbf43568 100644
--- a/ui/gfx/x/connection.cc
+++ b/ui/gfx/x/connection.cc
@@ -224,7 +224,7 @@
       setup_.roots[index].height_in_pixels = configure->height;
     }
   } else if (auto* screen = event.As<x11::RandR::ScreenChangeNotifyEvent>()) {
-    int index = ScreenIndexFromRootWindow(configure->window);
+    int index = ScreenIndexFromRootWindow(screen->root);
     DCHECK_GE(index, 0);
     bool portrait = static_cast<bool>(
         screen->rotation &
diff --git a/ui/gl/gl_gl_api_implementation.cc b/ui/gl/gl_gl_api_implementation.cc
index aa43cde2..6d0f5c7b 100644
--- a/ui/gl/gl_gl_api_implementation.cc
+++ b/ui/gl/gl_gl_api_implementation.cc
@@ -352,6 +352,18 @@
                                  height);
 }
 
+void RealGLApi::glTexStorageMem2DEXTFn(GLenum target,
+                                       GLsizei levels,
+                                       GLenum internalformat,
+                                       GLsizei width,
+                                       GLsizei height,
+                                       GLuint memory,
+                                       GLuint64 offset) {
+  internalformat = GetInternalFormat(version_.get(), internalformat);
+  GLApiBase::glTexStorageMem2DEXTFn(target, levels, internalformat, width,
+                                    height, memory, offset);
+}
+
 void RealGLApi::glRenderbufferStorageEXTFn(GLenum target,
                                            GLenum internalformat,
                                            GLsizei width,
diff --git a/ui/gl/gl_gl_api_implementation.h b/ui/gl/gl_gl_api_implementation.h
index c4f1061..16bc7bfb 100644
--- a/ui/gl/gl_gl_api_implementation.h
+++ b/ui/gl/gl_gl_api_implementation.h
@@ -81,6 +81,14 @@
                            GLsizei width,
                            GLsizei height) override;
 
+  void glTexStorageMem2DEXTFn(GLenum target,
+                              GLsizei levels,
+                              GLenum internalformat,
+                              GLsizei width,
+                              GLsizei height,
+                              GLuint memory,
+                              GLuint64 offset) override;
+
   void glRenderbufferStorageEXTFn(GLenum target,
                                   GLenum internalformat,
                                   GLsizei width,
diff --git a/ui/gl/gl_version_info.cc b/ui/gl/gl_version_info.cc
index f66cfd1..2d4368a 100644
--- a/ui/gl/gl_version_info.cc
+++ b/ui/gl/gl_version_info.cc
@@ -29,19 +29,7 @@
 
 GLVersionInfo::GLVersionInfo(const char* version_str,
                              const char* renderer_str,
-                             const gfx::ExtensionSet& extensions)
-    : is_es(false),
-      is_angle(false),
-      is_d3d(false),
-      is_mesa(false),
-      is_swiftshader(false),
-      is_angle_swiftshader(false),
-      major_version(0),
-      minor_version(0),
-      is_es2(false),
-      is_es3(false),
-      is_desktop_core_profile(false),
-      is_es3_capable(false) {
+                             const gfx::ExtensionSet& extensions) {
   Initialize(version_str, renderer_str, extensions);
 }
 
@@ -60,6 +48,7 @@
     if (is_angle) {
       is_angle_swiftshader =
           renderer_string.find("SwiftShader Device") != std::string::npos;
+      is_angle_vulkan = renderer_string.find("Vulkan") != std::string::npos;
     }
 
     is_swiftshader = base::StartsWith(renderer_str, "Google SwiftShader",
diff --git a/ui/gl/gl_version_info.h b/ui/gl/gl_version_info.h
index f44cf44..843236b 100644
--- a/ui/gl/gl_version_info.h
+++ b/ui/gl/gl_version_info.h
@@ -60,18 +60,19 @@
     return !is_angle && !is_swiftshader && (is_es3 || is_desktop_core_profile);
   }
 
-  bool is_es;
-  bool is_angle;
-  bool is_d3d;
-  bool is_mesa;
-  bool is_swiftshader;
-  bool is_angle_swiftshader;
-  unsigned major_version;
-  unsigned minor_version;
-  bool is_es2;
-  bool is_es3;
-  bool is_desktop_core_profile;
-  bool is_es3_capable;
+  bool is_es = false;
+  bool is_angle = false;
+  bool is_d3d = false;
+  bool is_mesa = false;
+  bool is_swiftshader = false;
+  bool is_angle_swiftshader = false;
+  bool is_angle_vulkan = false;
+  unsigned major_version = 0;
+  unsigned minor_version = 0;
+  bool is_es2 = false;
+  bool is_es3 = false;
+  bool is_desktop_core_profile = false;
+  bool is_es3_capable = false;
   std::string driver_vendor;
   std::string driver_version;
 
diff --git a/ui/gl/init/create_gr_gl_interface.cc b/ui/gl/init/create_gr_gl_interface.cc
index cb28362..c54abc1d 100644
--- a/ui/gl/init/create_gr_gl_interface.cc
+++ b/ui/gl/init/create_gr_gl_interface.cc
@@ -356,12 +356,18 @@
       gl->glDrawArraysInstancedANGLEFn, progress_reporter);
   functions->fDrawArraysInstancedBaseInstance = bind_slow_on_mac<true>(
       gl->glDrawArraysInstancedBaseInstanceANGLEFn, progress_reporter);
+  functions->fMultiDrawArraysInstancedBaseInstance = bind_slow_on_mac<true>(
+      gl->glMultiDrawArraysInstancedBaseInstanceANGLEFn, progress_reporter);
   functions->fDrawElementsInstanced = bind_slow_on_mac<true>(
       gl->glDrawElementsInstancedANGLEFn, progress_reporter);
   functions->fDrawElementsInstancedBaseVertexBaseInstance =
       bind_slow_on_mac<true>(
           gl->glDrawElementsInstancedBaseVertexBaseInstanceANGLEFn,
           progress_reporter);
+  functions->fMultiDrawElementsInstancedBaseVertexBaseInstance =
+      bind_slow_on_mac<true>(
+          gl->glMultiDrawElementsInstancedBaseVertexBaseInstanceANGLEFn,
+          progress_reporter);
 
   // GL 4.0 or GL_ARB_draw_indirect or ES 3.1
   functions->fDrawArraysIndirect =
diff --git a/ui/ozone/platform/wayland/host/wayland_window_drag_controller.cc b/ui/ozone/platform/wayland/host/wayland_window_drag_controller.cc
index 32bcb7a2..adcd51e 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_drag_controller.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_drag_controller.cc
@@ -120,6 +120,8 @@
                                               uint32_t serial) {
   DCHECK_GE(state_, State::kAttached);
   DCHECK(window);
+  DCHECK(data_source_);
+  DCHECK(data_offer_);
 
   // Forward focus change event to the input delegate, so other components, such
   // as WaylandScreen, are able to properly retrieve focus related info during
@@ -128,8 +130,13 @@
 
   VLOG(1) << "OnEnter. widget=" << window->GetWidget();
 
+  // TODO(crbug.com/1102946): Exo does not support custom mime types. In this
+  // case, |data_offer_| will hold an empty mime_types list and, at this point,
+  // it's safe just to skip the offer checks and requests here.
+  if (data_offer_->mime_types().empty())
+    return;
+
   // Ensure this is a valid "window drag" offer.
-  DCHECK(data_offer_);
   DCHECK_EQ(data_offer_->mime_types().size(), 1u);
   DCHECK_EQ(data_offer_->mime_types().front(), kMimeTypeChromiumWindow);
 
diff --git a/ui/ozone/platform/x11/x11_clipboard_ozone.cc b/ui/ozone/platform/x11/x11_clipboard_ozone.cc
index bea9cff..fba01a8 100644
--- a/ui/ozone/platform/x11/x11_clipboard_ozone.cc
+++ b/ui/ozone/platform/x11/x11_clipboard_ozone.cc
@@ -86,6 +86,8 @@
       x_property_(gfx::GetAtom(kChromeSelection)),
       connection_(x11::Connection::Get()),
       x_window_(CreateDummyWindow("Chromium Clipboard Window")) {
+  connection_->xfixes().QueryVersion(
+      {x11::XFixes::major_version, x11::XFixes::minor_version});
   if (!connection_->xfixes().present())
     return;
   using_xfixes_ = true;
@@ -114,7 +116,7 @@
   if (auto* notify = xev->As<x11::SelectionNotifyEvent>())
     return notify->requestor == x_window_ && OnSelectionNotify(*notify);
   if (auto* notify = xev->As<x11::XFixes::SelectionNotifyEvent>())
-    return notify->owner == x_window_ && OnSetSelectionOwnerNotify(*notify);
+    return notify->window == x_window_ && OnSetSelectionOwnerNotify(*notify);
 
   return false;
 }
diff --git a/ui/views/bubble/bubble_dialog_delegate_view.cc b/ui/views/bubble/bubble_dialog_delegate_view.cc
index ebe3b09..e2d0136 100644
--- a/ui/views/bubble/bubble_dialog_delegate_view.cc
+++ b/ui/views/bubble/bubble_dialog_delegate_view.cc
@@ -217,11 +217,11 @@
   }
 
   void OnWidgetDestroying(Widget* widget) override {
-    observer_.Remove(widget);
     owner_->OnWidgetDestroying(widget);
   }
 
   void OnWidgetDestroyed(Widget* widget) override {
+    observer_.Remove(widget);
     owner_->OnWidgetDestroyed(widget);
   }
 
diff --git a/ui/views/controls/button/label_button.cc b/ui/views/controls/button/label_button.cc
index 9fc78c95..1f0b758a 100644
--- a/ui/views/controls/button/label_button.cc
+++ b/ui/views/controls/button/label_button.cc
@@ -156,7 +156,7 @@
   if (GetMinSize() == min_size)
     return;
   min_size_ = min_size;
-  OnPropertyChanged(&min_size_, kPropertyEffectsNone);
+  OnPropertyChanged(&min_size_, kPropertyEffectsPreferredSizeChanged);
 }
 
 gfx::Size LabelButton::GetMaxSize() const {
@@ -167,7 +167,7 @@
   if (GetMaxSize() == max_size)
     return;
   max_size_ = max_size;
-  OnPropertyChanged(&max_size_, kPropertyEffectsNone);
+  OnPropertyChanged(&max_size_, kPropertyEffectsPreferredSizeChanged);
 }
 
 bool LabelButton::GetIsDefault() const {
@@ -195,7 +195,8 @@
   if (GetImageLabelSpacing() == spacing)
     return;
   image_label_spacing_ = spacing;
-  OnPropertyChanged(&image_label_spacing_, kPropertyEffectsLayout);
+  OnPropertyChanged(&image_label_spacing_,
+                    kPropertyEffectsPreferredSizeChanged);
 }
 
 bool LabelButton::GetImageCentered() const {
@@ -448,7 +449,7 @@
   label_->SetFontList(GetIsDefault() ? cached_default_button_font_list_
                                      : cached_normal_font_list_);
   ResetLabelEnabledColor();
-  return kPropertyEffectsLayout;
+  return kPropertyEffectsPreferredSizeChanged;
 }
 
 void LabelButton::ChildPreferredSizeChanged(View* child) {
@@ -548,7 +549,7 @@
 
 void LabelButton::VisualStateChanged() {
   UpdateImage();
-  label_->SetEnabled(GetState() != STATE_DISABLED);
+  label_->SetEnabled(GetVisualState() != STATE_DISABLED);
 }
 
 void LabelButton::ResetColorsFromNativeTheme() {
diff --git a/ui/views/controls/button/label_button_unittest.cc b/ui/views/controls/button/label_button_unittest.cc
index 156149d..b232473 100644
--- a/ui/views/controls/button/label_button_unittest.cc
+++ b/ui/views/controls/button/label_button_unittest.cc
@@ -93,6 +93,10 @@
     // used (which could be derived from the Widget's NativeTheme).
     test_widget_ = CreateTopLevelPlatformWidget();
 
+    // Ensure the Widget is active, since LabelButton appearance in inactive
+    // Windows is platform-dependent.
+    test_widget_->Show();
+
     // The test code below is not prepared to handle dark mode.
     test_widget_->GetNativeTheme()->set_use_dark_colors(false);
 
diff --git a/ui/views/controls/label.cc b/ui/views/controls/label.cc
index febce15..de48e32 100644
--- a/ui/views/controls/label.cc
+++ b/ui/views/controls/label.cc
@@ -89,7 +89,8 @@
 
 void Label::SetFontList(const gfx::FontList& font_list) {
   full_text_->SetFontList(font_list);
-  ResetLayout();
+  ClearDisplayText();
+  PreferredSizeChanged();
 }
 
 const base::string16& Label::GetText() const {
@@ -100,7 +101,9 @@
   if (new_text == GetText())
     return;
   full_text_->SetText(new_text);
-  OnPropertyChanged(&full_text_ + kLabelText, kPropertyEffectsLayout);
+  ClearDisplayText();
+  OnPropertyChanged(&full_text_ + kLabelText,
+                    kPropertyEffectsPreferredSizeChanged);
   stored_selection_range_ = gfx::Range::InvalidRange();
 }
 
@@ -130,6 +133,7 @@
   if (auto_color_readability_enabled_ == auto_color_readability_enabled)
     return;
   auto_color_readability_enabled_ = auto_color_readability_enabled;
+  RecalculateColors();
   OnPropertyChanged(&auto_color_readability_enabled_, kPropertyEffectsPaint);
 }
 
@@ -142,6 +146,7 @@
     return;
   requested_enabled_color_ = color;
   enabled_color_set_ = true;
+  RecalculateColors();
   OnPropertyChanged(&requested_enabled_color_, kPropertyEffectsPaint);
 }
 
@@ -154,6 +159,7 @@
     return;
   background_color_ = color;
   background_color_set_ = true;
+  RecalculateColors();
   OnPropertyChanged(&background_color_, kPropertyEffectsPaint);
 }
 
@@ -166,6 +172,7 @@
     return;
   requested_selection_text_color_ = color;
   selection_text_color_set_ = true;
+  RecalculateColors();
   OnPropertyChanged(&requested_selection_text_color_, kPropertyEffectsPaint);
 }
 
@@ -178,6 +185,7 @@
     return;
   selection_background_color_ = color;
   selection_background_color_set_ = true;
+  RecalculateColors();
   OnPropertyChanged(&selection_background_color_, kPropertyEffectsPaint);
 }
 
@@ -189,7 +197,9 @@
   if (full_text_->shadows() == shadows)
     return;
   full_text_->set_shadows(shadows);
-  OnPropertyChanged(&full_text_ + kLabelShadows, kPropertyEffectsLayout);
+  ClearDisplayText();
+  OnPropertyChanged(&full_text_ + kLabelShadows,
+                    kPropertyEffectsPreferredSizeChanged);
 }
 
 bool Label::GetSubpixelRenderingEnabled() const {
@@ -212,8 +222,9 @@
   if (GetHorizontalAlignment() == alignment)
     return;
   full_text_->SetHorizontalAlignment(alignment);
+  ClearDisplayText();
   OnPropertyChanged(&full_text_ + kLabelHorizontalAlignment,
-                    kPropertyEffectsLayout);
+                    kPropertyEffectsPaint);
 }
 
 gfx::VerticalAlignment Label::GetVerticalAlignment() const {
@@ -224,9 +235,9 @@
   if (GetVerticalAlignment() == alignment)
     return;
   full_text_->SetVerticalAlignment(alignment);
-  // TODO(dfried): consider if this should be kPropertyEffectsPaint instead.
+  ClearDisplayText();
   OnPropertyChanged(&full_text_ + kLabelVerticalAlignment,
-                    kPropertyEffectsLayout);
+                    kPropertyEffectsPaint);
 }
 
 int Label::GetLineHeight() const {
@@ -237,7 +248,9 @@
   if (GetLineHeight() == height)
     return;
   full_text_->SetMinLineHeight(height);
-  OnPropertyChanged(&full_text_ + kLabelLineHeight, kPropertyEffectsLayout);
+  ClearDisplayText();
+  OnPropertyChanged(&full_text_ + kLabelLineHeight,
+                    kPropertyEffectsPreferredSizeChanged);
 }
 
 bool Label::GetMultiLine() const {
@@ -251,7 +264,8 @@
     return;
   multi_line_ = multi_line;
   full_text_->SetMultiline(multi_line);
-  OnPropertyChanged(&multi_line_, kPropertyEffectsLayout);
+  ClearDisplayText();
+  OnPropertyChanged(&multi_line_, kPropertyEffectsPreferredSizeChanged);
 }
 
 int Label::GetMaxLines() const {
@@ -262,7 +276,7 @@
   if (max_lines_ == max_lines)
     return;
   max_lines_ = max_lines;
-  OnPropertyChanged(&max_lines_, kPropertyEffectsLayout);
+  OnPropertyChanged(&max_lines_, kPropertyEffectsPreferredSizeChanged);
 }
 
 bool Label::GetObscured() const {
@@ -273,9 +287,11 @@
   if (this->GetObscured() == obscured)
     return;
   full_text_->SetObscured(obscured);
+  ClearDisplayText();
   if (obscured)
     SetSelectable(false);
-  OnPropertyChanged(&full_text_ + kLabelObscured, kPropertyEffectsLayout);
+  OnPropertyChanged(&full_text_ + kLabelObscured,
+                    kPropertyEffectsPreferredSizeChanged);
 }
 
 bool Label::IsDisplayTextTruncated() const {
@@ -290,8 +306,7 @@
 }
 
 bool Label::GetAllowCharacterBreak() const {
-  return full_text_->word_wrap_behavior() == gfx::WRAP_LONG_WORDS ? true
-                                                                  : false;
+  return full_text_->word_wrap_behavior() == gfx::WRAP_LONG_WORDS;
 }
 
 void Label::SetAllowCharacterBreak(bool allow_character_break) {
@@ -300,8 +315,9 @@
   if (full_text_->word_wrap_behavior() == behavior)
     return;
   full_text_->SetWordWrapBehavior(behavior);
+  ClearDisplayText();
   OnPropertyChanged(&full_text_ + kLabelAllowCharacterBreak,
-                    kPropertyEffectsLayout);
+                    kPropertyEffectsPreferredSizeChanged);
 }
 
 size_t Label::GetTextIndexOfLine(size_t line) const {
@@ -322,7 +338,8 @@
   if (elide_behavior_ == elide_behavior)
     return;
   elide_behavior_ = elide_behavior;
-  OnPropertyChanged(&elide_behavior_, kPropertyEffectsLayout);
+  ClearDisplayText();
+  OnPropertyChanged(&elide_behavior_, kPropertyEffectsPreferredSizeChanged);
 }
 
 base::string16 Label::GetTooltipText() const {
@@ -365,7 +382,7 @@
   if (max_width_ == max_width)
     return;
   max_width_ = max_width;
-  OnPropertyChanged(&max_width_, kPropertyEffectsLayout);
+  OnPropertyChanged(&max_width_, kPropertyEffectsPreferredSizeChanged);
 }
 
 bool Label::GetCollapseWhenHidden() const {
@@ -385,7 +402,6 @@
 }
 
 base::string16 Label::GetDisplayTextForTesting() {
-  ClearDisplayText();
   MaybeBuildDisplayText();
   return display_text_ ? display_text_->GetDisplayText() : base::string16();
 }
@@ -576,15 +592,6 @@
   return base::string16();
 }
 
-void Label::OnHandlePropertyChangeEffects(PropertyEffects property_effects) {
-  if (property_effects & kPropertyEffectsPreferredSizeChanged)
-    SizeToPreferredSize();
-  if (property_effects & kPropertyEffectsLayout)
-    ResetLayout();
-  if (property_effects & kPropertyEffectsPaint)
-    RecalculateColors();
-}
-
 std::unique_ptr<gfx::RenderText> Label::CreateRenderText() const {
   // Multi-line labels only support NO_ELIDE and ELIDE_TAIL for now.
   // TODO(warx): Investigate more elide text support.
@@ -804,7 +811,7 @@
   // When the device scale factor is changed, some font rendering parameters is
   // changed (especially, hinting). The bounding box of the text has to be
   // re-computed based on the new parameters. See crbug.com/441439
-  ResetLayout();
+  PreferredSizeChanged();
 }
 
 void Label::VisibilityChanged(View* starting_from, bool is_visible) {
@@ -983,12 +990,6 @@
   AddAccelerator(ui::Accelerator(ui::VKEY_C, ui::EF_CONTROL_DOWN));
 }
 
-void Label::ResetLayout() {
-  PreferredSizeChanged();
-  SchedulePaint();
-  ClearDisplayText();
-}
-
 void Label::MaybeBuildDisplayText() const {
   if (display_text_)
     return;
@@ -1087,7 +1088,7 @@
           (GetMultiLine() && text_size.height() > size.height()));
 }
 
-void Label::ClearDisplayText() const {
+void Label::ClearDisplayText() {
   // The HasSelection() call below will build |display_text_| in case it is
   // empty. Return early to avoid this.
   if (!display_text_)
@@ -1099,6 +1100,8 @@
         GetRenderTextForSelectionController()->selection();
   }
   display_text_ = nullptr;
+
+  SchedulePaint();
 }
 
 base::string16 Label::GetSelectedText() const {
diff --git a/ui/views/controls/label.h b/ui/views/controls/label.h
index 5d47ce0..e6c2b16 100644
--- a/ui/views/controls/label.h
+++ b/ui/views/controls/label.h
@@ -186,7 +186,10 @@
   // returns the text position of the first character of that line.
   size_t GetTextIndexOfLine(size_t line) const;
 
-  // Set the truncate length of the rendered text.
+  // Set the truncate length of the |full_text_|.
+  // NOTE: This does not affect the |display_text_|, since right now the only
+  // consumer does not need that; if you need this function, you may need to
+  // implement this.
   void SetTruncateLength(size_t truncate_length);
 
   // Gets/Sets the eliding or fading behavior, applied as necessary. The default
@@ -273,7 +276,6 @@
   WordLookupClient* GetWordLookupClient() override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
   base::string16 GetTooltipText(const gfx::Point& p) const override;
-  void OnHandlePropertyChangeEffects(PropertyEffects property_effects) override;
 
  protected:
   // Create a single RenderText instance to actually be painted.
@@ -353,8 +355,6 @@
             const gfx::FontList& font_list,
             gfx::DirectionalityMode directionality_mode);
 
-  void ResetLayout();
-
   // Set up |display_text_| to actually be painted.
   void MaybeBuildDisplayText() const;
 
@@ -377,7 +377,10 @@
   bool ShouldShowDefaultTooltip() const;
 
   // Clears |display_text_| and updates |stored_selection_range_|.
-  void ClearDisplayText() const;
+  // TODO(crbug.com/1103804) Most uses of this function are inefficient; either
+  // replace with setting attributes on both RenderTexts or collapse them to one
+  // RenderText.
+  void ClearDisplayText();
 
   // Returns the currently selected text.
   base::string16 GetSelectedText() const;
diff --git a/ui/views/controls/label_unittest.cc b/ui/views/controls/label_unittest.cc
index 33d9698..040d4f1 100644
--- a/ui/views/controls/label_unittest.cc
+++ b/ui/views/controls/label_unittest.cc
@@ -88,13 +88,6 @@
   EXPECT_EQ(rtl, base::i18n::IsRTL());
 }
 
-// Returns true if |current| is bigger than |last|. Sets |last| to |current|.
-bool Increased(int current, int* last) {
-  bool increased = current > *last;
-  *last = current;
-  return increased;
-}
-
 base::string16 GetClipboardText(ui::ClipboardBuffer clipboard_buffer) {
   base::string16 clipboard_text;
   ui::Clipboard::GetForCurrentThread()->ReadText(clipboard_buffer,
@@ -920,11 +913,15 @@
 // Ensures SchedulePaint() calls are not made in OnPaint().
 TEST_F(LabelTest, NoSchedulePaintInOnPaint) {
   TestLabel label;
+  int count = 0;
+  const auto expect_paint_count_increased = [&]() {
+    EXPECT_GT(label.schedule_paint_count(), count);
+    count = label.schedule_paint_count();
+  };
 
   // Initialization should schedule at least one paint, but the precise number
   // doesn't really matter.
-  int count = label.schedule_paint_count();
-  EXPECT_LT(0, count);
+  expect_paint_count_increased();
 
   // Painting should never schedule another paint.
   label.SimulatePaint();
@@ -932,16 +929,16 @@
 
   // Test a few things that should schedule paints. Multiple times is OK.
   label.SetEnabled(false);
-  EXPECT_TRUE(Increased(label.schedule_paint_count(), &count));
+  expect_paint_count_increased();
 
   label.SetText(label.GetText() + ASCIIToUTF16("Changed"));
-  EXPECT_TRUE(Increased(label.schedule_paint_count(), &count));
+  expect_paint_count_increased();
 
   label.SizeToPreferredSize();
-  EXPECT_TRUE(Increased(label.schedule_paint_count(), &count));
+  expect_paint_count_increased();
 
   label.SetEnabledColor(SK_ColorBLUE);
-  EXPECT_TRUE(Increased(label.schedule_paint_count(), &count));
+  expect_paint_count_increased();
 
   label.SimulatePaint();
   EXPECT_EQ(count, label.schedule_paint_count());  // Unchanged.
diff --git a/ui/views/view.cc b/ui/views/view.cc
index 186b6c5..428df308 100644
--- a/ui/views/view.cc
+++ b/ui/views/view.cc
@@ -2076,7 +2076,6 @@
     InvalidateLayout();
   if (effects & kPropertyEffectsPaint)
     SchedulePaint();
-  OnHandlePropertyChangeEffects(effects);
 }
 
 PropertyChangedSubscription View::AddPropertyChangedCallback(
diff --git a/ui/views/view.h b/ui/views/view.h
index 802144f..a7302ff 100644
--- a/ui/views/view.h
+++ b/ui/views/view.h
@@ -1578,11 +1578,6 @@
   void OnPropertyChanged(PropertyKey property,
                          PropertyEffects property_effects);
 
-  // Empty function called in HandlePropertyChangeEffects to be overridden in
-  // subclasses if they have custom functions for property changes.
-  virtual void OnHandlePropertyChangeEffects(PropertyEffects property_effects) {
-  }
-
  private:
   friend class internal::PreEventDispatchHandler;
   friend class internal::PostEventDispatchHandler;
diff --git a/ui/webui/resources/cr_components/chromeos/network/BUILD.gn b/ui/webui/resources/cr_components/chromeos/network/BUILD.gn
index ccd5bda..3146e1c 100644
--- a/ui/webui/resources/cr_components/chromeos/network/BUILD.gn
+++ b/ui/webui/resources/cr_components/chromeos/network/BUILD.gn
@@ -246,6 +246,7 @@
   is_polymer3 = true
   deps = [
     ":mojo_interface_provider.m",
+
     #  ":cr_policy_network_behavior_mojo.m",
     #  ":cr_policy_network_indicator_mojo.m",
     #  ":network_apnlist.m",
@@ -256,11 +257,13 @@
     #  ":network_config_select.m",
     #  ":network_config_toggle.m",
     ":network_icon.m",
+
     #  ":network_ip_config.m",
     #  ":network_list.m",
     #  ":network_list_item.m",
     #  ":network_list_types.m",
     ":network_listener_behavior.m",
+
     #  ":network_nameservers.m",
     #  ":network_password_input.m",
     #  ":network_property_list_mojo.m",
diff --git a/ui/webui/resources/cr_components/chromeos/quick_unlock/BUILD.gn b/ui/webui/resources/cr_components/chromeos/quick_unlock/BUILD.gn
index a27e999..c897fa1 100644
--- a/ui/webui/resources/cr_components/chromeos/quick_unlock/BUILD.gn
+++ b/ui/webui/resources/cr_components/chromeos/quick_unlock/BUILD.gn
@@ -36,20 +36,21 @@
   extra_sources = [ "$interfaces_path/quick_unlock_private_interface.js" ]
 }
 
-# TODO: Uncomment as the Polymer3 migration makes progress.
-# js_type_check("closure_compile_module") {
-#    is_polymer3 = true
-#    deps = [
-#      ":lock_screen_constants.m",
-#      ":pin_keyboard.m",
-#      ":setup_pin_keyboard.m"
-#    ]
-# }
+js_type_check("closure_compile_module") {
+  is_polymer3 = true
+  deps = [
+    ":lock_screen_constants.m",
+    ":pin_keyboard.m",
+    ":setup_pin_keyboard.m",
+  ]
+}
 
 js_library("lock_screen_constants.m") {
   sources = [ "$root_gen_dir/ui/webui/resources/cr_components/chromeos/quick_unlock/lock_screen_constants.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/cr_elements/cr_profile_avatar_selector:cr_profile_avatar_selector.m",
+    "//ui/webui/resources/js:cr.m",
   ]
   extra_deps = [ ":lock_screen_constants_module" ]
 }
@@ -57,7 +58,9 @@
 js_library("pin_keyboard.m") {
   sources = [ "$root_gen_dir/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/cr_elements/cr_input:cr_input.m",
+    "//ui/webui/resources/js:i18n_behavior.m",
   ]
   extra_deps = [ ":pin_keyboard_module" ]
 }
@@ -65,7 +68,11 @@
 js_library("setup_pin_keyboard.m") {
   sources = [ "$root_gen_dir/ui/webui/resources/cr_components/chromeos/quick_unlock/setup_pin_keyboard.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":lock_screen_constants.m",
+    ":pin_keyboard.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/cr_components/chromeos/quick_unlock:lock_screen_constants.m",
+    "//ui/webui/resources/js:i18n_behavior.m",
   ]
   extra_deps = [ ":setup_pin_keyboard_module" ]
 }
diff --git a/ui/webui/resources/cr_components/chromeos/quick_unlock/lock_screen_constants.js b/ui/webui/resources/cr_components/chromeos/quick_unlock/lock_screen_constants.js
index dbc85a3..fd1606d2 100644
--- a/ui/webui/resources/cr_components/chromeos/quick_unlock/lock_screen_constants.js
+++ b/ui/webui/resources/cr_components/chromeos/quick_unlock/lock_screen_constants.js
@@ -16,7 +16,7 @@
    * Stages the user can enter while setting up pin unlock.
    * @enum {number}
    */
-  const LockScreenProgress = {
+  /* #export */ const LockScreenProgress = {
     START_SCREEN_LOCK: 0,
     ENTER_PASSWORD_CORRECTLY: 1,
     CHOOSE_PIN_OR_PASSWORD: 2,
@@ -29,7 +29,7 @@
    * histogram.
    * @param {settings.LockScreenProgress} currentProgress
    */
-  const recordLockScreenProgress = function(currentProgress) {
+  /* #export */ const recordLockScreenProgress = function(currentProgress) {
     if (currentProgress >= LockScreenProgress.MAX_BUCKET) {
       console.error(
           'Expected a enumeration value of ' + LockScreenProgress.MAX_BUCKET +
diff --git a/ui/webui/resources/cr_elements/chromeos/BUILD.gn b/ui/webui/resources/cr_elements/chromeos/BUILD.gn
index 33cc937..da86dbe 100644
--- a/ui/webui/resources/cr_elements/chromeos/BUILD.gn
+++ b/ui/webui/resources/cr_elements/chromeos/BUILD.gn
@@ -19,7 +19,7 @@
 group("polymer3_elements") {
   public_deps = [
     ":cros_color_overrides_module",
-    "cr_picture:modulize",
+    "cr_picture:polymer3_elements",
   ]
 }
 
diff --git a/ui/webui/resources/cr_elements/chromeos/cr_picture/BUILD.gn b/ui/webui/resources/cr_elements/chromeos/cr_picture/BUILD.gn
index be7345e..ea1ba54 100644
--- a/ui/webui/resources/cr_elements/chromeos/cr_picture/BUILD.gn
+++ b/ui/webui/resources/cr_elements/chromeos/cr_picture/BUILD.gn
@@ -3,7 +3,9 @@
 # found in the LICENSE file.
 
 import("//third_party/closure_compiler/compile_js.gni")
+import("//tools/polymer/polymer.gni")
 import("//ui/webui/resources/tools/js_modulizer.gni")
+import("../os_cr_elements.gni")
 
 js_type_check("closure_compile") {
   deps = [
@@ -44,12 +46,20 @@
 }
 
 js_modulizer("modulize") {
-  input_files = [ "png.js" ]
+  input_files = [
+    "png.js",
+    "cr_picture_types.js",
+  ]
 }
 
 js_type_check("closure_compile_module") {
-  uses_js_modules = true
-  deps = [ ":png.m" ]
+  is_polymer3 = true
+  deps = [
+    ":cr_camera.m",
+    ":cr_picture_list.m",
+    ":cr_picture_pane.m",
+    ":png.m",
+  ]
 }
 
 js_library("png.m") {
@@ -63,3 +73,75 @@
   ]
   extra_deps = [ ":modulize" ]
 }
+
+js_library("cr_picture_types.m") {
+  sources = [ "$root_gen_dir/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_types.m.js" ]
+  extra_deps = [ ":modulize" ]
+}
+
+group("polymer3_elements") {
+  public_deps = [
+    ":cr_camera_module",
+    ":cr_picture_list_module",
+    ":cr_picture_pane_module",
+    ":icons_module",
+    ":modulize",
+  ]
+}
+
+js_library("cr_picture_pane.m") {
+  sources = [ "$root_gen_dir/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_pane.m.js" ]
+  deps = [
+    ":cr_camera.m",
+    ":cr_picture_types.m",
+    ":png.m",
+  ]
+  extra_deps = [ ":cr_picture_pane_module" ]
+}
+
+js_library("cr_camera.m") {
+  sources = [ "$root_gen_dir/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_camera.m.js" ]
+  deps = [ ":png.m" ]
+  extra_deps = [ ":cr_camera_module" ]
+}
+
+js_library("cr_picture_list.m") {
+  sources = [ "$root_gen_dir/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_list.m.js" ]
+  deps = [
+    ":cr_picture_types.m",
+    ":png.m",
+    "//third_party/polymer/v3_0/components-chromium/iron-selector:iron-selector",
+    "//ui/webui/resources/js:assert.m",
+  ]
+  extra_deps = [ ":cr_picture_list_module" ]
+}
+
+polymer_modulizer("cr_camera") {
+  js_file = "cr_camera.js"
+  html_file = "cr_camera.html"
+  html_type = "dom-module"
+  namespace_rewrites = cr_elements_chromeos_namespace_rewrites
+  auto_imports = cr_elements_chromeos_auto_imports
+}
+
+polymer_modulizer("cr_picture_list") {
+  js_file = "cr_picture_list.js"
+  html_file = "cr_picture_list.html"
+  html_type = "dom-module"
+  namespace_rewrites = cr_elements_chromeos_namespace_rewrites
+  auto_imports = cr_elements_chromeos_auto_imports
+}
+
+polymer_modulizer("cr_picture_pane") {
+  js_file = "cr_picture_pane.js"
+  html_file = "cr_picture_pane.html"
+  html_type = "dom-module"
+  namespace_rewrites = cr_elements_chromeos_namespace_rewrites
+  auto_imports = cr_elements_chromeos_auto_imports
+}
+
+polymer_modulizer("icons") {
+  js_file = "icons.m.js"
+  html_file = "icons.html"
+  html_type = "iron-iconset"
+}
diff --git a/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_camera.js b/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_camera.js
index 11be5874..1b14dea 100644
--- a/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_camera.js
+++ b/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_camera.js
@@ -129,8 +129,11 @@
     const interval = setInterval(() => {
       /** Stop capturing frames when all allocated frames have been consumed. */
       if (frames.length) {
-        capturedFrames.push(
-            this.captureFrame_(this.$.cameraVideo, frames.pop()));
+        capturedFrames.push(this.captureFrame_(
+            /**
+             * @type {!HTMLVideoElement}
+             */
+            (this.$.cameraVideo), frames.pop()));
       } else {
         clearInterval(interval);
         this.fire(
diff --git a/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_list.js b/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_list.js
index 3ce3dd7e..6630fd5 100644
--- a/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_list.js
+++ b/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_list.js
@@ -120,7 +120,11 @@
     if (!selected) {
       return;
     }
-    this.setSelectedImage_(this.$.profileImage);
+    this.setSelectedImage_(
+        /**
+         * @type {!CrPicture.ImageElement}
+         */
+        (this.$.profileImage));
   },
 
   /**
@@ -131,7 +135,11 @@
       return image.dataset.url === imageUrl;
     });
     if (image) {
-      this.setSelectedImage_(image);
+      this.setSelectedImage_(
+          /**
+           * @type {!CrPicture.ImageElement}
+           */
+          (image));
       this.selectedImageUrl_ = '';
     } else {
       this.selectedImageUrl_ = imageUrl;
@@ -159,9 +167,17 @@
     } else if (
         this.fallbackImage_ &&
         this.fallbackImage_.dataset.type !== CrPicture.SelectionTypes.OLD) {
-      this.selectImage_(this.fallbackImage_, true /* activate */);
+      this.selectImage_(
+          /**
+           * @type {!CrPicture.ImageElement}
+           */
+          (this.fallbackImage_), true /* activate */);
     } else {
-      this.selectImage_(this.$.profileImage, true /* activate */);
+      this.selectImage_(
+          /**
+           * @type {!CrPicture.ImageElement}
+           */
+          (this.$.profileImage), true /* activate */);
     }
   },
 
diff --git a/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_types.js b/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_types.js
index 8afb666d2..cf6c7f3 100644
--- a/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_types.js
+++ b/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_types.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-const CrPicture = {};
+/* #export */ const CrPicture = {};
 
 /**
  * Contains the possible types for picture list image elements.
diff --git a/ui/webui/resources/cr_elements/chromeos/os_cr_elements.gni b/ui/webui/resources/cr_elements/chromeos/os_cr_elements.gni
new file mode 100644
index 0000000..ad32b62
--- /dev/null
+++ b/ui/webui/resources/cr_elements/chromeos/os_cr_elements.gni
@@ -0,0 +1,15 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/closure_compiler/compile_js.gni")
+
+cr_elements_chromeos_namespace_rewrites = [
+  "cr.png.convertImageSequenceToPng|convertImageSequenceToPng",
+  "cr.png.isEncodedPngDataUrlAnimated|isEncodedPngDataUrlAnimated",
+]
+
+cr_elements_chromeos_auto_imports = [
+  "ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_types.html|CrPicture",
+  "ui/webui/resources/cr_elements/chromeos/cr_picture/png.html|convertImageSequenceToPng,isEncodedPngDataUrlAnimated",
+]
diff --git a/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_search_field.html b/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_search_field.html
index dc0756f..86e320e 100644
--- a/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_search_field.html
+++ b/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_search_field.html
@@ -142,11 +142,13 @@
       }
 
       :host(:not([narrow])) #prompt {
-        margin-inline-start:
-            var(--cr-toolbar-search-field-prompt-margin-inline-start);
         opacity: var(--cr-toolbar-search-field-prompt-opacity);
       }
 
+      :host([narrow]) #prompt {
+        opacity: var(--cr-toolbar-search-field-narrow-mode-prompt-opacity, 0);
+      }
+
       :host([narrow]:not([showing-search])) #searchTerm {
         display: none;
       }
diff --git a/ui/webui/resources/cr_elements_resources_v3.grdp b/ui/webui/resources/cr_elements_resources_v3.grdp
index d8c2e3d..a17e15a 100644
--- a/ui/webui/resources/cr_elements_resources_v3.grdp
+++ b/ui/webui/resources/cr_elements_resources_v3.grdp
@@ -202,6 +202,27 @@
            file="${root_gen_dir}/ui/webui/resources/cr_elements/chromeos/cr_picture/png.m.js"
            use_base_dir="false"
            type="BINDATA" />
+    <include name="IDR_CR_ELEMENTS_CHROMEOS_CR_PICTURE_CAMERA_M_JS"
+           file="${root_gen_dir}/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_camera.m.js"
+           use_base_dir="false"
+           type="BINDATA" />
+    <include name="IDR_CR_ELEMENTS_CHROMEOS_CR_PICTURE_PICTURE_LIST_M_JS"
+           file="${root_gen_dir}/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_list.m.js"
+           use_base_dir="false"
+           type="BINDATA" />
+    <include name="IDR_CR_ELEMENTS_CHROMEOS_CR_PICTURE_PICTURE_PANE_M_JS"
+           file="${root_gen_dir}/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_pane.m.js"
+           use_base_dir="false"
+           type="BINDATA" />
+    <include name="IDR_CR_ELEMENTS_CHROMEOS_CR_PICTURE_PICTURE_TYPES_M_JS"
+           file="${root_gen_dir}/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_types.m.js"
+           use_base_dir="false"
+           type="BINDATA" />
+    <include name="IDR_CR_ELEMENTS_CHROMEOS_CR_PICTURE_ICONS_M_JS"
+           file="${root_gen_dir}/ui/webui/resources/cr_elements/chromeos/cr_picture/icons.m.js"
+           use_base_dir="false"
+           preprocess="true"
+           type="BINDATA" />
   </if>
   <include name="IDR_CR_ELEMENTS_LOTTIE_M_JS"
          file="${root_gen_dir}/ui/webui/resources/cr_elements/cr_lottie/cr_lottie.m.js"
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NavigationTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NavigationTest.java
index a0879a3d..4ae6148 100644
--- a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NavigationTest.java
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NavigationTest.java
@@ -67,6 +67,7 @@
             private List<Uri> mRedirectChain;
             private @LoadError int mLoadError;
             private @NavigationState int mNavigationState;
+            private boolean mIsRendererInitiatedNavigation;
 
             public void notifyCalled(Navigation navigation) {
                 mUri = navigation.getUri();
@@ -75,6 +76,7 @@
                 mRedirectChain = navigation.getRedirectChain();
                 mLoadError = navigation.getLoadError();
                 mNavigationState = navigation.getState();
+                mIsRendererInitiatedNavigation = navigation.isRendererInitiated();
                 notifyCalled();
             }
 
@@ -111,6 +113,10 @@
             public int getNavigationState() {
                 return mNavigationState;
             }
+
+            public boolean isRendererInitiated() {
+                return mIsRendererInitiatedNavigation;
+            }
         }
 
         public static class UriCallbackHelper extends CallbackHelper {
@@ -224,6 +230,7 @@
         mCallback.onReadyToCommitCallback.assertCalledWith(curCommittedCount, URL2);
         mCallback.onCompletedCallback.assertCalledWith(curCompletedCount, URL2);
         mCallback.onFirstContentfulPaintCallback.waitForCallback(curOnFirstContentfulPaintCount);
+        assertFalse(mCallback.onStartedCallback.isRendererInitiated());
         assertEquals(mCallback.onCompletedCallback.getHttpStatusCode(), 200);
     }
 
@@ -775,4 +782,18 @@
         }
         Assert.fail("Expected IndexOutOfBoundsException.");
     }
+
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(86)
+    public void testRendererInitiated() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
+        setNavigationCallback(activity);
+        String initialUrl = mActivityTestRule.getTestDataURL("simple_page4.html");
+        mActivityTestRule.navigateAndWait(initialUrl);
+        String refreshUrl = mActivityTestRule.getTestDataURL("simple_page.html");
+        mCallback.onCompletedCallback.assertCalledWith(
+                mCallback.onCompletedCallback.getCallCount(), refreshUrl);
+        assertTrue(mCallback.onCompletedCallback.isRendererInitiated());
+    }
 }
diff --git a/weblayer/browser/java/BUILD.gn b/weblayer/browser/java/BUILD.gn
index a424a6d..7f6bfdb7 100644
--- a/weblayer/browser/java/BUILD.gn
+++ b/weblayer/browser/java/BUILD.gn
@@ -180,8 +180,6 @@
     "//components/url_formatter/android:url_formatter_java",
     "//components/variations/android:variations_java",
     "//components/version_info/android:version_constants_java",
-    "//components/webapk/android/libs/client:java",
-    "//components/webapk/android/libs/common:java",
     "//components/webrtc/android:java",
     "//content/public/android:content_java",
     "//mojo/public/java:bindings_java",
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/ExternalNavigationDelegateImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/ExternalNavigationDelegateImpl.java
index 0038ec5..fdf5a8e 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/ExternalNavigationDelegateImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/ExternalNavigationDelegateImpl.java
@@ -10,15 +10,12 @@
 
 import androidx.annotation.Nullable;
 
-import org.chromium.base.ContextUtils;
 import org.chromium.base.PackageManagerUtils;
 import org.chromium.components.embedder_support.util.UrlUtilities;
 import org.chromium.components.external_intents.ExternalNavigationDelegate;
 import org.chromium.components.external_intents.ExternalNavigationDelegate.StartActivityIfNeededResult;
 import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResult;
 import org.chromium.components.external_intents.ExternalNavigationParams;
-import org.chromium.components.webapk.lib.client.ChromeWebApkHostSignature;
-import org.chromium.components.webapk.lib.client.WebApkValidator;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.base.WindowAndroid;
@@ -28,7 +25,6 @@
  * WebLayer's implementation of the {@link ExternalNavigationDelegate}.
  */
 public class ExternalNavigationDelegateImpl implements ExternalNavigationDelegate {
-    private static boolean sWebApkValidatorInitialized;
     private final TabImpl mTab;
     private boolean mTabDestroyed;
 
@@ -207,16 +203,6 @@
     }
 
     @Override
-    public boolean isValidWebApk(String packageName) {
-        if (!sWebApkValidatorInitialized) {
-            WebApkValidator.init(ChromeWebApkHostSignature.EXPECTED_SIGNATURE,
-                    ChromeWebApkHostSignature.PUBLIC_KEY);
-            sWebApkValidatorInitialized = true;
-        }
-        return WebApkValidator.isValidWebApk(ContextUtils.getApplicationContext(), packageName);
-    }
-
-    @Override
     public boolean handleWithAutofillAssistant(ExternalNavigationParams params, Intent targetIntent,
             String browserFallbackUrl, boolean isGoogleReferrer) {
         return false;
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/NavigationImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/NavigationImpl.java
index cff0bc6..edca37b 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/NavigationImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/NavigationImpl.java
@@ -147,6 +147,13 @@
         return NavigationImplJni.get().wasStopCalled(mNativeNavigationImpl);
     }
 
+    @Override
+    public boolean isRendererInitiated() {
+        StrictModeWorkaround.apply();
+        throwIfNativeDestroyed();
+        return NavigationImplJni.get().isRendererInitiated(mNativeNavigationImpl);
+    }
+
     private void throwIfNativeDestroyed() {
         if (mNativeNavigationImpl == 0) {
             throw new IllegalStateException("Using Navigation after native destroyed");
@@ -195,5 +202,6 @@
         boolean isValidRequestHeaderName(String name);
         boolean isValidRequestHeaderValue(String value);
         boolean setUserAgentString(long nativeNavigationImpl, String value);
+        boolean isRendererInitiated(long nativeNavigationImpl);
     }
 }
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigation.aidl b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigation.aidl
index 9d42c48..5ac15a1 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigation.aidl
+++ b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigation.aidl
@@ -29,4 +29,7 @@
   boolean isDownload() = 9;
 
   boolean wasStopCalled() = 10;
+
+  // @since 86
+  boolean isRendererInitiated() = 11;
 }
diff --git a/weblayer/browser/navigation_impl.cc b/weblayer/browser/navigation_impl.cc
index 211f2f0..7a8a725 100644
--- a/weblayer/browser/navigation_impl.cc
+++ b/weblayer/browser/navigation_impl.cc
@@ -87,6 +87,10 @@
 
 #endif
 
+bool NavigationImpl::IsRendererInitiated() {
+  return navigation_handle_->IsRendererInitiated();
+}
+
 GURL NavigationImpl::GetURL() {
   return navigation_handle_->GetURL();
 }
diff --git a/weblayer/browser/navigation_impl.h b/weblayer/browser/navigation_impl.h
index c002d88..e41a171 100644
--- a/weblayer/browser/navigation_impl.h
+++ b/weblayer/browser/navigation_impl.h
@@ -69,6 +69,7 @@
   jboolean SetUserAgentString(
       JNIEnv* env,
       const base::android::JavaParamRef<jstring>& value);
+  jboolean IsRendererInitiated(JNIEnv* env) { return IsRendererInitiated(); }
 
   base::android::ScopedJavaGlobalRef<jobject> java_navigation() {
     return java_navigation_;
@@ -89,6 +90,7 @@
   void SetRequestHeader(const std::string& name,
                         const std::string& value) override;
   void SetUserAgentString(const std::string& value) override;
+  bool IsRendererInitiated() override;
 
   content::NavigationHandle* navigation_handle_;
 
diff --git a/weblayer/public/java/org/chromium/weblayer/Navigation.java b/weblayer/public/java/org/chromium/weblayer/Navigation.java
index 85c880d..1aba7cc 100644
--- a/weblayer/public/java/org/chromium/weblayer/Navigation.java
+++ b/weblayer/public/java/org/chromium/weblayer/Navigation.java
@@ -216,4 +216,31 @@
             throw new APICallException(e);
         }
     }
+
+    /**
+     * Returns whether the navigation was initiated by the renderer. Examples of renderer-initiated
+     * navigations:
+     * * Clicking <a> links.
+     * * changing window.location.href
+     * * redirect via the <meta http-equiv="refresh"> tag
+     * * using window.history.pushState
+     *
+     * This method returns false for navigations initiated by the WebLayer API, including using
+     *  window.history.forward() or window.history.back().
+     *
+     * @return Whether the navigation was initiated by the renderer.
+     *
+     * @since 86
+     */
+    public boolean isRendererInitiated() {
+        ThreadCheck.ensureOnUiThread();
+        if (WebLayer.getSupportedMajorVersionInternal() < 86) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return mNavigationImpl.isRendererInitiated();
+        } catch (RemoteException e) {
+            throw new APICallException(e);
+        }
+    }
 }
diff --git a/weblayer/public/navigation.h b/weblayer/public/navigation.h
index 725962e49..0e82d7d 100644
--- a/weblayer/public/navigation.h
+++ b/weblayer/public/navigation.h
@@ -104,6 +104,17 @@
   // must not contain any illegal characters as documented in
   // SetRequestHeader().
   virtual void SetUserAgentString(const std::string& value) = 0;
+
+  // Whether the navigation was initiated by the renderer process. Examples of
+  // renderer-initiated navigations include:
+  //  * <a> link click
+  //  * changing window.location.href
+  //  * redirect via the <meta http-equiv="refresh"> tag
+  //  * using window.history.pushState
+  //
+  // This method returns false for navigations initiated by the WebLayer
+  // API, including using window.history.forward() or window.history.back().
+  virtual bool IsRendererInitiated() = 0;
 };
 
 }  // namespace weblayer
diff --git a/weblayer/test/data/simple_page4.html b/weblayer/test/data/simple_page4.html
index 44b258a..52e1a4d 100644
--- a/weblayer/test/data/simple_page4.html
+++ b/weblayer/test/data/simple_page4.html
@@ -1,6 +1,6 @@
 <html>
 <head><title>OK</title></head>
 <body>
-      <meta http-equiv = "refresh" content = "2; url = /simple_page.html" />
+      <meta http-equiv = "refresh" content = "2; url = simple_page.html" />
 </body>
 </html>