diff --git a/DEPS b/DEPS
index 8740f1f0..4468b0f 100644
--- a/DEPS
+++ b/DEPS
@@ -207,7 +207,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'b09f16ee7aeb7f2063c29fdbe600644d0ffe07b8',
+  'angle_revision': '1fd3e5d89a8f840f859fb9c379e58defe9c399ab',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -258,7 +258,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': '6b79223314a55ddee356ff91f56dd7f676824e87',
+  'catapult_revision': '8c88c750bd4516075f6879b4aed5e3bf22ef4424',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -322,7 +322,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'quiche_revision': '3c3e8905ad7ef0dded8d6231087fcc6e3b6a1051',
+  'quiche_revision': '89ec3381ca3daf5d6aa9b151a8da6bf03edbe3be',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ios_webkit
   # and whatever else without interference from each other.
@@ -896,12 +896,12 @@
 
   # For Linux and Chromium OS.
   'src/third_party/cros_system_api': {
-      'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + 'd5ad6a50d168252d431f99cd227e327a86c567ff',
+      'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + '5df72efed1de3c81f7590dbc09b240cb6edc4cd6',
       'condition': 'checkout_linux',
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'b677e985297d9c668afa697cb773b9eee5154ab0',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '69e30b23c45f22df585dc12f464479ca3deafb69',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1332,7 +1332,7 @@
       'packages': [
           {
               'package': 'fuchsia/third_party/aemu/linux-amd64',
-              'version': 'UABC8VAzZj56bPNLe3ou7AIlBjwHbiXOu6R9f5RbZWcC'
+              'version': 'KmGtEkDfwHxQu0H8Vjg__Xqkj_7wy2sK8nS-v1WZhSwC'
           },
       ],
       'condition': 'host_os == "linux" and checkout_fuchsia',
@@ -1486,7 +1486,7 @@
   },
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'ab4a49259c0ea3ab7e7dc28c7bda8f443181cf92',
+    Var('webrtc_git') + '/src.git' + '@' + '5b5abd79106a1770bb6fdab9e40f7270c7d4259a',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1558,7 +1558,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@110e287f7da023e2e7b26285a5cbb648869c51b5',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@fbeee8fe0c0bc1ee5a70f7375dd0cb048ea7bafe',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 1af75a3..ab30915e 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -328,13 +328,11 @@
   '^chrome/browser/download/',
   '^chrome/browser/extensions/',
   '^chrome/browser/history/',
-  '^chrome/browser/installable/installable_manager_browsertest.cc',
   '^chrome/browser/lifetime/',
   '^chrome/browser/media_galleries/',
   '^chrome/browser/media/',
   '^chrome/browser/metrics/',
   '^chrome/browser/nacl_host/test/gdb_debug_stub_browsertest.cc',
-  '^chrome/browser/nearby_sharing/client/nearby_share_api_call_flow_impl_unittest.cc', # pylint: disable=line-too-long
   '^chrome/browser/net/',
   '^chrome/browser/notifications/',
   '^chrome/browser/ntp_tiles/ntp_tiles_browsertest.cc',
diff --git a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
index 6a7d804..8ade01e 100644
--- a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
+++ b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
@@ -7,6 +7,7 @@
 import org.chromium.base.BaseSwitches;
 import org.chromium.cc.base.CcSwitches;
 import org.chromium.components.metrics.MetricsSwitches;
+import org.chromium.gpu.config.GpuFeatures;
 import org.chromium.gpu.config.GpuSwitches;
 
 /**
@@ -64,7 +65,8 @@
             Flag.commandLine(GpuSwitches.IGNORE_GPU_BLOCKLIST,
                     "Overrides the built-in software rendering list and enables "
                             + "GPU acceleration on unsupported device configurations."),
-            Flag.baseFeature("EnableSharedImageForWebview", "Enables shared images for WebView."),
+            Flag.baseFeature(GpuFeatures.ENABLE_SHARED_IMAGE_FOR_WEBVIEW,
+                    "Enables shared images for WebView."),
             Flag.baseFeature("VizForWebView", "Enables Viz for WebView."),
             Flag.baseFeature(AwFeatures.WEBVIEW_CONNECTIONLESS_SAFE_BROWSING,
                     "Uses GooglePlayService's 'connectionless' APIs for Safe Browsing "
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index a88d144..d94683d1 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -275,8 +275,6 @@
     "capture_mode/capture_mode_type_view.cc",
     "capture_mode/capture_mode_type_view.h",
     "capture_mode/capture_mode_types.h",
-    "capture_mode/capture_window_observer.cc",
-    "capture_mode/capture_window_observer.h",
     "capture_mode/stop_recording_button_tray.cc",
     "capture_mode/stop_recording_button_tray.h",
     "capture_mode/view_with_ink_drop.h",
diff --git a/ash/ambient/ui/ambient_background_image_view.cc b/ash/ambient/ui/ambient_background_image_view.cc
index 1bf423e..eac7be9 100644
--- a/ash/ambient/ui/ambient_background_image_view.cc
+++ b/ash/ambient/ui/ambient_background_image_view.cc
@@ -8,6 +8,7 @@
 
 #include "ash/ambient/ambient_constants.h"
 #include "ash/ambient/ui/glanceable_info_view.h"
+#include "ash/ambient/ui/media_string_view.h"
 #include "ash/ambient/util/ambient_util.h"
 #include "ash/assistant/ui/assistant_view_ids.h"
 #include "base/rand_util.h"
@@ -30,6 +31,7 @@
 // Appearance.
 constexpr int kMarginDip = 16;
 constexpr int kSpacingDip = 8;
+constexpr int kMediaStringMarginDip = 32;
 
 // Typography.
 constexpr SkColor kTextColor = SK_ColorWHITE;
@@ -228,6 +230,25 @@
   details_label_->SetShadows(ambient::util::GetTextShadowValues());
   details_label_->SetPaintToLayer();
   details_label_->layer()->SetFillsBoundsOpaquely(false);
+
+  // Inits the media string view. The media string view is positioned on the
+  // right-top corner of the container.
+  views::View* media_string_view_container_ =
+      AddChildView(std::make_unique<views::View>());
+  views::BoxLayout* media_string_layout =
+      media_string_view_container_->SetLayoutManager(
+          std::make_unique<views::BoxLayout>(
+              views::BoxLayout::Orientation::kVertical));
+  media_string_layout->set_main_axis_alignment(
+      views::BoxLayout::MainAxisAlignment::kStart);
+  media_string_layout->set_cross_axis_alignment(
+      views::BoxLayout::CrossAxisAlignment::kEnd);
+  media_string_layout->set_inside_border_insets(
+      gfx::Insets(kMediaStringMarginDip + shadow_insets.top(), 0, 0,
+                  kMediaStringMarginDip + shadow_insets.right()));
+  media_string_view_ = media_string_view_container_->AddChildView(
+      std::make_unique<MediaStringView>());
+  media_string_view_->SetVisible(false);
 }
 
 void AmbientBackgroundImageView::UpdateGlanceableInfoPosition() {
@@ -262,6 +283,13 @@
   transform.Translate(current_x_translation, current_y_translation);
   glanceable_info_view_->layer()->SetTransform(transform);
   details_label_->layer()->SetTransform(transform);
+
+  if (media_string_view_->GetVisible()) {
+    gfx::Transform media_string_transform;
+    media_string_transform.Translate(-current_x_translation,
+                                     -current_y_translation);
+    media_string_view_->layer()->SetTransform(media_string_transform);
+  }
 }
 
 bool AmbientBackgroundImageView::UpdateRelatedImageViewVisibility() {
diff --git a/ash/ambient/ui/ambient_background_image_view.h b/ash/ambient/ui/ambient_background_image_view.h
index efffebe..deb9f83 100644
--- a/ash/ambient/ui/ambient_background_image_view.h
+++ b/ash/ambient/ui/ambient_background_image_view.h
@@ -22,6 +22,7 @@
 namespace ash {
 
 class GlanceableInfoView;
+class MediaStringView;
 
 // AmbientBackgroundImageView--------------------------------------------------
 // A custom ImageView to display photo image and details information on ambient.
@@ -90,6 +91,8 @@
   // current image. Owned by the view hierarchy.
   views::Label* details_label_ = nullptr;
 
+  MediaStringView* media_string_view_ = nullptr;
+
   ScopedObserver<views::View, views::ViewObserver> observed_views_{this};
 };
 }  // namespace ash
diff --git a/ash/ambient/ui/ambient_container_view.cc b/ash/ambient/ui/ambient_container_view.cc
index aec77ea..d7cd087 100644
--- a/ash/ambient/ui/ambient_container_view.cc
+++ b/ash/ambient/ui/ambient_container_view.cc
@@ -9,7 +9,6 @@
 
 #include "ash/ambient/ui/ambient_assistant_container_view.h"
 #include "ash/ambient/ui/ambient_view_delegate.h"
-#include "ash/ambient/ui/media_string_view.h"
 #include "ash/ambient/ui/photo_view.h"
 #include "ash/ambient/util/ambient_util.h"
 #include "ash/assistant/ui/assistant_view_ids.h"
@@ -38,7 +37,6 @@
 
 // Appearance.
 constexpr int kAssistantPreferredHeightDip = 128;
-constexpr int kMediaStringMarginDip = 32;
 
 // A tolerance threshold used to ignore spurious mouse move.
 constexpr int kMouseMoveErrorTolerancePx = 3;
@@ -135,7 +133,7 @@
 void AmbientContainerView::Layout() {
   // Layout child views first to have proper bounds set for children.
   LayoutPhotoView();
-  LayoutMediaStringView();
+
   // The assistant view may not exist if |kAmbientAssistant| feature is
   // disabled.
   if (ambient_assistant_container_view_)
@@ -156,9 +154,6 @@
 
   photo_view_ = AddChildView(std::make_unique<PhotoView>(delegate_));
 
-  media_string_view_ = AddChildView(std::make_unique<MediaStringView>());
-  media_string_view_->SetVisible(false);
-
   if (IsAmbientAssistantEnabled()) {
     ambient_assistant_container_view_ =
         AddChildView(std::make_unique<AmbientAssistantContainerView>());
@@ -178,19 +173,6 @@
       gfx::Rect(0, 0, preferred_width, preferred_height));
 }
 
-void AmbientContainerView::LayoutMediaStringView() {
-  const gfx::Size container_size = GetLocalBounds().size();
-  const gfx::Size preferred_size = media_string_view_->GetPreferredSize();
-
-  // The media string view is positioned on the right-top corner of the
-  // container.
-  int x =
-      container_size.width() - kMediaStringMarginDip - preferred_size.width();
-  int y = kMediaStringMarginDip;
-  media_string_view_->SetBoundsRect(
-      gfx::Rect(x, y, preferred_size.width(), preferred_size.height()));
-}
-
 void AmbientContainerView::HandleEvent() {
   delegate_->OnBackgroundPhotoEvents();
 }
diff --git a/ash/ambient/ui/ambient_container_view.h b/ash/ambient/ui/ambient_container_view.h
index 2cd56ea6..cd44646 100644
--- a/ash/ambient/ui/ambient_container_view.h
+++ b/ash/ambient/ui/ambient_container_view.h
@@ -16,7 +16,6 @@
 class AmbientAssistantContainerView;
 class AmbientViewDelegate;
 class PhotoView;
-class MediaStringView;
 
 // Container view to display all Ambient Mode related views, i.e. photo frame,
 // weather info.
@@ -42,7 +41,6 @@
   // Layout(). See b/163170162.
   void LayoutPhotoView();
   void LayoutAssistantView();
-  void LayoutMediaStringView();
 
   // Invoked on specific types of events.
   void HandleEvent();
@@ -52,7 +50,6 @@
   // Owned by view hierarchy.
   PhotoView* photo_view_ = nullptr;
   AmbientAssistantContainerView* ambient_assistant_container_view_ = nullptr;
-  MediaStringView* media_string_view_ = nullptr;
 
   // Observes events from its host widget.
   std::unique_ptr<HostWidgetEventObserver> event_observer_;
diff --git a/ash/ambient/ui/media_string_view_unittest.cc b/ash/ambient/ui/media_string_view_unittest.cc
index 2a4ffaa..c6435481 100644
--- a/ash/ambient/ui/media_string_view_unittest.cc
+++ b/ash/ambient/ui/media_string_view_unittest.cc
@@ -28,6 +28,11 @@
     GetSessionControllerClient()->set_show_lock_screen_views(true);
   }
 
+  void TearDown() override {
+    CloseAmbientScreen();
+    AmbientAshTestBase::TearDown();
+  }
+
   const base::string16& GetText() {
     return GetMediaStringViewTextLabel()->GetText();
   }
@@ -260,6 +265,8 @@
   metadata.title = base::ASCIIToUTF16("title");
   metadata.artist = base::ASCIIToUTF16("artist");
 
+  SimulateMediaPlaybackStateChanged(
+      media_session::mojom::MediaPlaybackState::kPlaying);
   SimulateMediaMetadataChanged(metadata);
   // Force re-layout.
   container_view()->Layout();
@@ -277,6 +284,8 @@
   metadata.title = base::ASCIIToUTF16("A super duper long title");
   metadata.artist = base::ASCIIToUTF16("A super duper long artist name");
 
+  SimulateMediaPlaybackStateChanged(
+      media_session::mojom::MediaPlaybackState::kPlaying);
   SimulateMediaMetadataChanged(metadata);
   // Force re-layout.
   container_view()->Layout();
@@ -294,6 +303,8 @@
   metadata.title = base::ASCIIToUTF16("title");
   metadata.artist = base::ASCIIToUTF16("artist");
 
+  SimulateMediaPlaybackStateChanged(
+      media_session::mojom::MediaPlaybackState::kPlaying);
   SimulateMediaMetadataChanged(metadata);
   // Force re-layout.
   container_view()->Layout();
@@ -357,7 +368,6 @@
   PrefService* pref =
       Shell::Get()->session_controller()->GetPrimaryUserPrefService();
   pref->SetBoolean(prefs::kLockScreenMediaControlsEnabled, false);
-
   // Simulates Ambient Mode shown on lock-screen.
   LockScreen();
   FastForwardToInactivity();
@@ -370,4 +380,28 @@
   EXPECT_FALSE(GetMediaStringView()->GetVisible());
 }
 
+TEST_F(MediaStringViewTest, ShouldHasDifferentTransform) {
+  ShowAmbientScreen();
+
+  // Sets metadata for current session.
+  media_session::MediaMetadata metadata;
+  metadata.title = base::ASCIIToUTF16("title");
+  metadata.artist = base::ASCIIToUTF16("artist");
+
+  SimulateMediaPlaybackStateChanged(
+      media_session::mojom::MediaPlaybackState::kPlaying);
+  SimulateMediaMetadataChanged(metadata);
+  EXPECT_TRUE(GetMediaStringView()->GetVisible());
+
+  // It is theoretically that the transforms could be the same in two
+  // consecutive updates, therefore we test with two updates.
+  gfx::Transform transform1 =
+      GetMediaStringView()->layer()->GetTargetTransform();
+  FastForwardToNextImage();
+  FastForwardToNextImage();
+  gfx::Transform transform2 =
+      GetMediaStringView()->layer()->GetTargetTransform();
+  EXPECT_NE(transform1, transform2);
+}
+
 }  // namespace ash
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index f257ea6..78eff98 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -2590,6 +2590,10 @@
         Pin to shelf
       </message>
 
+      <message name="IDS_ASH_GLOBAL_MEDIA_CONTROLS_NO_MEDIA_TEXT" desc="Text indicating no media is currently playing.">
+        No media playing
+      </message>
+
       <!-- Power off menu  -->
       <message name="IDS_ASH_POWER_BUTTON_MENU_POWER_OFF_BUTTON" desc="Text shown on power off button in power button menu.">
         Power off
diff --git a/ash/ash_strings_grd/IDS_ASH_GLOBAL_MEDIA_CONTROLS_NO_MEDIA_TEXT.png.sha1 b/ash/ash_strings_grd/IDS_ASH_GLOBAL_MEDIA_CONTROLS_NO_MEDIA_TEXT.png.sha1
new file mode 100644
index 0000000..524c2c4
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_GLOBAL_MEDIA_CONTROLS_NO_MEDIA_TEXT.png.sha1
@@ -0,0 +1 @@
+d8a52632861b5b591d9b4bf354b24c426ec91bf4
\ No newline at end of file
diff --git a/ash/capture_mode/capture_mode_session.cc b/ash/capture_mode/capture_mode_session.cc
index 8bb0a2a..6ea2bfa 100644
--- a/ash/capture_mode/capture_mode_session.cc
+++ b/ash/capture_mode/capture_mode_session.cc
@@ -6,7 +6,6 @@
 
 #include "ash/capture_mode/capture_mode_bar_view.h"
 #include "ash/capture_mode/capture_mode_controller.h"
-#include "ash/capture_mode/capture_window_observer.h"
 #include "ash/display/mouse_cursor_event_filter.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/resources/vector_icons/vector_icons.h"
@@ -32,7 +31,6 @@
 #include "ui/views/background.h"
 #include "ui/views/controls/button/label_button.h"
 #include "ui/views/controls/label.h"
-#include "ui/wm/core/coordinate_conversion.h"
 
 namespace ash {
 
@@ -170,11 +168,6 @@
   capture_mode_bar_widget_.Show();
 
   RefreshStackingOrder(parent);
-
-  if (controller_->source() == CaptureModeSource::kWindow) {
-    capture_window_observer_ =
-        std::make_unique<CaptureWindowObserver>(this, controller_->type());
-  }
 }
 
 CaptureModeSession::~CaptureModeSession() {
@@ -183,18 +176,15 @@
 }
 
 aura::Window* CaptureModeSession::GetSelectedWindow() const {
-  return capture_window_observer_ ? capture_window_observer_->window()
-                                  : nullptr;
+  // Note that the capture bar widget is activatable, so we can't use
+  // window_util::GetActiveWindow(). Instead, we use the MRU window tracker and
+  // get the top-most window if any.
+  auto mru_windows =
+      Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk);
+  return mru_windows.empty() ? nullptr : mru_windows[0];
 }
 
 void CaptureModeSession::OnCaptureSourceChanged(CaptureModeSource new_source) {
-  if (new_source == CaptureModeSource::kWindow) {
-    capture_window_observer_ =
-        std::make_unique<CaptureWindowObserver>(this, controller_->type());
-  } else {
-    capture_window_observer_.reset();
-  }
-
   capture_mode_bar_view_->OnCaptureSourceChanged(new_source);
   SetMouseWarpEnabled(new_source != CaptureModeSource::kRegion);
   UpdateCaptureRegionWidgets();
@@ -202,8 +192,6 @@
 }
 
 void CaptureModeSession::OnCaptureTypeChanged(CaptureModeType new_type) {
-  if (controller_->source() == CaptureModeSource::kWindow)
-    capture_window_observer_->OnCaptureTypeChanged(new_type);
   capture_mode_bar_view_->OnCaptureTypeChanged(new_type);
 }
 
@@ -342,45 +330,13 @@
 
 void CaptureModeSession::OnLocatedEvent(ui::LocatedEvent* event,
                                         bool is_touch) {
-  // No need to handle events if the current source is kFullscreen.
-  const CaptureModeSource capture_source = controller_->source();
-  if (capture_source == CaptureModeSource::kFullscreen)
+  // No need to handle events if the current source is not region.
+  if (controller_->source() != CaptureModeSource::kRegion)
     return;
 
   gfx::Point location = event->location();
   aura::Window* source = static_cast<aura::Window*>(event->target());
   aura::Window::ConvertPointToTarget(source, current_root_, &location);
-  const bool is_event_on_capture_bar =
-      CaptureModeBarView::GetBounds(current_root_).Contains(location);
-
-  if (capture_source == CaptureModeSource::kWindow) {
-    // Do not handle any event located on the capture mode bar.
-    if (is_event_on_capture_bar)
-      return;
-
-    event->SetHandled();
-    event->StopPropagation();
-
-    switch (event->type()) {
-      case ui::ET_MOUSE_MOVED:
-      case ui::ET_TOUCH_PRESSED:
-      case ui::ET_TOUCH_MOVED: {
-        gfx::Point screen_location(event->location());
-        ::wm::ConvertPointToScreen(source, &screen_location);
-        capture_window_observer_->UpdateSelectedWindowAtPosition(
-            screen_location);
-        break;
-      }
-      case ui::ET_MOUSE_RELEASED:
-      case ui::ET_TOUCH_RELEASED:
-        if (GetSelectedWindow())
-          controller_->PerformCapture();
-        break;
-      default:
-        break;
-    }
-    return;
-  }
 
   // Let the capture button handle any events within its bounds.
   if (capture_button_widget_ &&
@@ -390,7 +346,7 @@
 
   // Allow events that are located on the capture mode bar to pass through so we
   // can click the buttons.
-  if (!is_event_on_capture_bar) {
+  if (!CaptureModeBarView::GetBounds(current_root_).Contains(location)) {
     event->SetHandled();
     event->StopPropagation();
   }
diff --git a/ash/capture_mode/capture_mode_session.h b/ash/capture_mode/capture_mode_session.h
index 46f72e0a..b3b1263 100644
--- a/ash/capture_mode/capture_mode_session.h
+++ b/ash/capture_mode/capture_mode_session.h
@@ -24,7 +24,6 @@
 
 class CaptureModeBarView;
 class CaptureModeController;
-class CaptureWindowObserver;
 
 // Encapsulates an active capture mode session (i.e. an instance of this class
 // lives as long as capture mode is active). It creates and owns the capture
@@ -171,9 +170,6 @@
   // Caches the old status of mouse warping before the session started to be
   // restored at the end.
   bool old_mouse_warp_status_;
-
-  // Observer to observe the current selected to-be-captured window.
-  std::unique_ptr<CaptureWindowObserver> capture_window_observer_;
 };
 
 }  // namespace ash
diff --git a/ash/capture_mode/capture_mode_unittests.cc b/ash/capture_mode/capture_mode_unittests.cc
index c03b5a5..68ad8bc 100644
--- a/ash/capture_mode/capture_mode_unittests.cc
+++ b/ash/capture_mode/capture_mode_unittests.cc
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <memory>
-
 #include "ash/capture_mode/capture_mode_bar_view.h"
 #include "ash/capture_mode/capture_mode_close_button.h"
 #include "ash/capture_mode/capture_mode_controller.h"
@@ -20,7 +18,6 @@
 #include "ash/shell.h"
 #include "ash/system/status_area_widget.h"
 #include "ash/test/ash_test_base.h"
-#include "ash/wm/window_state.h"
 #include "base/test/scoped_feature_list.h"
 #include "ui/events/keycodes/keyboard_codes_posix.h"
 #include "ui/gfx/geometry/insets.h"
@@ -500,38 +497,4 @@
             dimensions_label_window->bounds().bottom());
 }
 
-TEST_F(CaptureModeTest, WindowCapture) {
-  // Create 2 windows that overlap with each other.
-  const gfx::Rect bounds1(0, 0, 200, 200);
-  std::unique_ptr<aura::Window> window1(CreateTestWindow(bounds1));
-  const gfx::Rect bounds2(150, 150, 200, 200);
-  std::unique_ptr<aura::Window> window2(CreateTestWindow(bounds2));
-
-  auto* controller = CaptureModeController::Get();
-  controller->SetSource(CaptureModeSource::kWindow);
-  controller->SetType(CaptureModeType::kImage);
-  controller->Start();
-  EXPECT_TRUE(controller->IsActive());
-
-  auto* event_generator = GetEventGenerator();
-  event_generator->MoveMouseToCenterOf(window1.get());
-  auto* capture_mode_session = controller->capture_mode_session();
-  EXPECT_EQ(capture_mode_session->GetSelectedWindow(), window1.get());
-  event_generator->MoveMouseToCenterOf(window2.get());
-  EXPECT_EQ(capture_mode_session->GetSelectedWindow(), window2.get());
-
-  // Now move the mouse to the overlapped area.
-  event_generator->MoveMouseTo(gfx::Point(175, 175));
-  EXPECT_EQ(capture_mode_session->GetSelectedWindow(), window2.get());
-  // Close the current selected window should automatically focus to next one.
-  window2.reset();
-  EXPECT_EQ(capture_mode_session->GetSelectedWindow(), window1.get());
-  // Open another one on top also change the selected window.
-  std::unique_ptr<aura::Window> window3(CreateTestWindow(bounds2));
-  EXPECT_EQ(capture_mode_session->GetSelectedWindow(), window3.get());
-  // Minimize the window should also automatically change the selected window.
-  WindowState::Get(window3.get())->Minimize();
-  EXPECT_EQ(capture_mode_session->GetSelectedWindow(), window1.get());
-}
-
 }  // namespace ash
diff --git a/ash/capture_mode/capture_window_observer.cc b/ash/capture_mode/capture_window_observer.cc
deleted file mode 100644
index b9dedc1b8..0000000
--- a/ash/capture_mode/capture_window_observer.cc
+++ /dev/null
@@ -1,178 +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 "ash/capture_mode/capture_window_observer.h"
-
-#include "ash/capture_mode/capture_mode_controller.h"
-#include "ash/capture_mode/capture_mode_session.h"
-#include "ash/public/cpp/shell_window_ids.h"
-#include "ash/public/cpp/window_finder.h"
-#include "ash/resources/vector_icons/vector_icons.h"
-#include "ash/shell.h"
-#include "ui/base/cursor/cursor_factory.h"
-#include "ui/base/cursor/cursor_util.h"
-#include "ui/display/screen.h"
-#include "ui/gfx/paint_vector_icon.h"
-#include "ui/wm/core/window_util.h"
-#include "ui/wm/public/activation_client.h"
-
-namespace ash {
-
-CaptureWindowObserver::CaptureWindowObserver(
-    CaptureModeSession* capture_mode_session,
-    CaptureModeType type)
-    : capture_type_(type),
-      original_cursor_(Shell::Get()->cursor_manager()->GetCursor()),
-      capture_mode_session_(capture_mode_session) {
-  Shell::Get()->activation_client()->AddObserver(this);
-}
-
-CaptureWindowObserver::~CaptureWindowObserver() {
-  auto* shell = Shell::Get();
-  shell->activation_client()->RemoveObserver(this);
-  StopObserving();
-  ::wm::CursorManager* cursor_manager = shell->cursor_manager();
-  if (is_cursor_locked_) {
-    cursor_manager->UnlockCursor();
-    cursor_manager->SetCursor(original_cursor_);
-    is_cursor_locked_ = false;
-  }
-}
-
-void CaptureWindowObserver::UpdateSelectedWindowAtPosition(
-    const gfx::Point& location_in_screen) {
-  UpdateSelectedWindowAtPosition(location_in_screen, /*ignore_windows=*/{});
-}
-
-void CaptureWindowObserver::OnCaptureTypeChanged(CaptureModeType new_type) {
-  capture_type_ = new_type;
-  UpdateMouseCursor();
-}
-
-void CaptureWindowObserver::OnWindowBoundsChanged(
-    aura::Window* window,
-    const gfx::Rect& old_bounds,
-    const gfx::Rect& new_bounds,
-    ui::PropertyChangeReason reason) {
-  DCHECK_EQ(window, window_);
-  RepaintCaptureRegion();
-}
-
-void CaptureWindowObserver::OnWindowVisibilityChanging(aura::Window* window,
-                                                       bool visible) {
-  DCHECK_EQ(window, window_);
-  DCHECK(!visible);
-  StopObserving();
-  UpdateSelectedWindowAtPosition(location_in_screen_,
-                                 /*ignore_windows=*/{window});
-}
-
-void CaptureWindowObserver::OnWindowDestroying(aura::Window* window) {
-  DCHECK_EQ(window, window_);
-  StopObserving();
-  UpdateSelectedWindowAtPosition(location_in_screen_,
-                                 /*ignore_windows=*/{window});
-}
-
-void CaptureWindowObserver::OnWindowActivated(ActivationReason reason,
-                                              aura::Window* gained_active,
-                                              aura::Window* lost_active) {
-  // If another window is activated on top of the current selected window, we
-  // may change the selected window to the activated window if it's under the
-  // current event location. If there is no selected window at the moment, we
-  // also want to check if new activated window should be focused.
-  UpdateSelectedWindowAtPosition(location_in_screen_, /*ignore_windows=*/{});
-}
-
-void CaptureWindowObserver::StartObserving(aura::Window* window) {
-  window_ = window;
-  window_->AddObserver(this);
-}
-
-void CaptureWindowObserver::StopObserving() {
-  if (window_) {
-    window_->RemoveObserver(this);
-    window_ = nullptr;
-  }
-}
-
-void CaptureWindowObserver::UpdateSelectedWindowAtPosition(
-    const gfx::Point& location_in_screen,
-    const std::set<aura::Window*>& ignore_windows) {
-  location_in_screen_ = location_in_screen;
-  // Find the toplevel window under the mouse/touch position.
-  aura::Window* window =
-      GetTopmostWindowAtPoint(location_in_screen_, ignore_windows);
-  if (window_ == window)
-    return;
-
-  // Don't capture wallpaper window.
-  if (window && window->parent() &&
-      window->parent()->id() == kShellWindowId_WallpaperContainer) {
-    window = nullptr;
-  }
-
-  // Stop observing the current selected window if there is one.
-  aura::Window* previous_selected_window = window_;
-  StopObserving();
-  if (window)
-    StartObserving(window);
-  RepaintCaptureRegion();
-
-  // Change mouse cursor depending on capture type and capture window if
-  // applicable.
-  const bool should_update_cursor =
-      !previous_selected_window != !window_ &&
-      Shell::Get()->cursor_manager()->IsCursorVisible();
-  if (should_update_cursor)
-    UpdateMouseCursor();
-}
-
-void CaptureWindowObserver::RepaintCaptureRegion() {
-  ui::Layer* layer = capture_mode_session_->layer();
-  layer->SchedulePaint(layer->bounds());
-}
-
-void CaptureWindowObserver::UpdateMouseCursor() {
-  ::wm::CursorManager* cursor_manager = Shell::Get()->cursor_manager();
-  if (window_) {
-    // Change the mouse cursor to a capture icon or a recording icon.
-    ui::Cursor cursor(ui::mojom::CursorType::kCustom);
-    const display::Display display =
-        display::Screen::GetScreen()->GetDisplayNearestWindow(window_);
-    const float device_scale_factor = display.device_scale_factor();
-    // TODO: Adjust the icon color after spec is updated.
-    const gfx::ImageSkia icon = gfx::CreateVectorIcon(
-        capture_type_ == CaptureModeType::kImage ? kCaptureModeImageIcon
-                                                 : kCaptureModeVideoIcon,
-        SK_ColorBLACK);
-    SkBitmap bitmap = *icon.bitmap();
-    gfx::Point hotspot(bitmap.width() / 2, bitmap.height() / 2);
-    ui::ScaleAndRotateCursorBitmapAndHotpoint(
-        device_scale_factor, display.panel_rotation(), &bitmap, &hotspot);
-    auto* cursor_factory = ui::CursorFactory::GetInstance();
-    ui::PlatformCursor platform_cursor =
-        cursor_factory->CreateImageCursor(bitmap, hotspot);
-    cursor.SetPlatformCursor(platform_cursor);
-    cursor.set_custom_bitmap(bitmap);
-    cursor.set_custom_hotspot(hotspot);
-    cursor_factory->UnrefImageCursor(platform_cursor);
-
-    // Unlock the cursor first so that it can be changed.
-    if (is_cursor_locked_)
-      cursor_manager->UnlockCursor();
-    cursor_manager->SetCursor(cursor);
-    cursor_manager->LockCursor();
-    is_cursor_locked_ = true;
-  } else {
-    // Revert back to its previous mouse cursor setting.
-    if (is_cursor_locked_) {
-      cursor_manager->UnlockCursor();
-      is_cursor_locked_ = false;
-    }
-    cursor_manager->SetCursor(original_cursor_);
-  }
-}
-
-}  // namespace ash
diff --git a/ash/capture_mode/capture_window_observer.h b/ash/capture_mode/capture_window_observer.h
deleted file mode 100644
index f02ade3..0000000
--- a/ash/capture_mode/capture_window_observer.h
+++ /dev/null
@@ -1,98 +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 ASH_CAPTURE_MODE_CAPTURE_WINDOW_OBSERVER_H_
-#define ASH_CAPTURE_MODE_CAPTURE_WINDOW_OBSERVER_H_
-
-#include <set>
-
-#include "ash/ash_export.h"
-#include "ash/capture_mode/capture_mode_types.h"
-#include "ui/aura/window_observer.h"
-#include "ui/base/cursor/cursor.h"
-#include "ui/gfx/geometry/point.h"
-#include "ui/gfx/native_widget_types.h"
-#include "ui/wm/public/activation_change_observer.h"
-
-namespace aura {
-class Window;
-}  // namespace aura
-
-namespace ash {
-
-class CaptureModeSession;
-
-// Class to observe the current selected to-be-captured window and update the
-// capture region if applicable.
-class ASH_EXPORT CaptureWindowObserver : public aura::WindowObserver,
-                                         public ::wm::ActivationChangeObserver {
- public:
-  CaptureWindowObserver(CaptureModeSession* capture_mode_session,
-                        CaptureModeType type);
-  CaptureWindowObserver(const CaptureWindowObserver&) = delete;
-  CaptureWindowObserver& operator=(const CaptureWindowObserver&) = delete;
-
-  ~CaptureWindowObserver() override;
-
-  // Updates selected window depending on the mouse/touch event location. If
-  // there is an eligible window under the current mouse/touch event location,
-  // its bounds will be highlighted.
-  void UpdateSelectedWindowAtPosition(const gfx::Point& location_in_screen);
-
-  // Called when capture type changes. The mouse cursor image may update
-  // accordingly.
-  void OnCaptureTypeChanged(CaptureModeType new_type);
-
-  // aura::WindowObserver:
-  void OnWindowBoundsChanged(aura::Window* window,
-                             const gfx::Rect& old_bounds,
-                             const gfx::Rect& new_bounds,
-                             ui::PropertyChangeReason reason) override;
-  void OnWindowVisibilityChanging(aura::Window* window, bool visible) override;
-  void OnWindowDestroying(aura::Window* window) override;
-
-  // ::wm::ActivationChangeObserver:
-  void OnWindowActivated(ActivationReason reason,
-                         aura::Window* gained_active,
-                         aura::Window* lost_active) override;
-
-  aura::Window* window() { return window_; }
-
- private:
-  void StartObserving(aura::Window* window);
-  void StopObserving();
-
-  // Updates selected window depending on the mouse/touch event location with
-  // ignoring |ignore_windows|.
-  void UpdateSelectedWindowAtPosition(
-      const gfx::Point& location_in_screen,
-      const std::set<aura::Window*>& ignore_windows);
-
-  // Repaints the window capture region.
-  void RepaintCaptureRegion();
-
-  // Updates the mouse cursor to change it to a capture or record icon when the
-  // mouse hovers over an eligible window.
-  void UpdateMouseCursor();
-
-  // Current observed window.
-  aura::Window* window_ = nullptr;
-
-  // Stores current mouse or touch location in screen coordinate.
-  gfx::Point location_in_screen_;
-
-  // Current capture type.
-  CaptureModeType capture_type_;
-
-  // True if the current cursor is locked by this.
-  bool is_cursor_locked_ = false;
-  const gfx::NativeCursor original_cursor_;
-
-  // Pointer to current capture session. Not nullptr during this lifecycle.
-  CaptureModeSession* const capture_mode_session_;
-};
-
-}  // namespace ash
-
-#endif  // ASH_CAPTURE_MODE_CAPTURE_WINDOW_OBSERVER_H_
diff --git a/ash/resources/vector_icons/BUILD.gn b/ash/resources/vector_icons/BUILD.gn
index ecc8a62..b711c67 100644
--- a/ash/resources/vector_icons/BUILD.gn
+++ b/ash/resources/vector_icons/BUILD.gn
@@ -77,6 +77,7 @@
     "login_screen_enterprise.icon",
     "login_screen_menu_dropdown.icon",
     "mic.icon",
+    "music_note.icon",
     "muted_microphone.icon",
     "network_badge_captive_portal.icon",
     "network_badge_off.icon",
diff --git a/ash/resources/vector_icons/music_note.icon b/ash/resources/vector_icons/music_note.icon
new file mode 100644
index 0000000..e2f88b4
--- /dev/null
+++ b/ash/resources/vector_icons/music_note.icon
@@ -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.
+
+CANVAS_DIMENSIONS, 20,
+MOVE_TO, 10, 3,
+R_LINE_TO, 0.01f, 7.79f,
+R_ARC_TO, 3.33f, 3.33f, 0, 0, 0, -1.67f, -0.46f,
+R_ARC_TO, 3.33f, 3.33f, 0, 1, 0, 0, 6.67f,
+CUBIC_TO, 10.19f, 17, 12, 15.51f, 12, 13.67f,
+V_LINE_TO, 6,
+R_H_LINE_TO, 3,
+V_LINE_TO, 3,
+R_H_LINE_TO, -5,
+CLOSE
diff --git a/ash/style/ash_color_provider.cc b/ash/style/ash_color_provider.cc
index 6d0feaad..0c5bed9 100644
--- a/ash/style/ash_color_provider.cc
+++ b/ash/style/ash_color_provider.cc
@@ -7,6 +7,7 @@
 #include <math.h>
 
 #include "ash/public/cpp/ash_constants.h"
+#include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/ash_pref_names.h"
 #include "ash/public/cpp/login_constants.h"
 #include "ash/session/session_controller_impl.h"
@@ -290,6 +291,38 @@
   // added separately so its easier to monitor performance.
 }
 
+void AshColorProvider::DecorateIconButton(views::ImageButton* button,
+                                          ButtonType type,
+                                          const gfx::VectorIcon& icon,
+                                          bool toggled,
+                                          int icon_size) {
+  DCHECK(!icon.is_empty());
+  const SkColor normal_color =
+      GetContentLayerColor(ContentLayerType::kButtonIconColor);
+  const SkColor toggled_icon_color =
+      GetContentLayerColor(ContentLayerType::kButtonIconColorPrimary);
+  const SkColor icon_color = toggled ? toggled_icon_color : normal_color;
+
+  // Skip repainting if the incoming icon is the same as the current icon. If
+  // the icon has been painted before, |gfx::CreateVectorIcon()| will simply
+  // grab the ImageSkia from a cache, so it will be cheap. Note that this
+  // assumes that toggled/disabled images changes at the same time as the normal
+  // image, which it currently does.
+  const gfx::ImageSkia new_normal_image =
+      gfx::CreateVectorIcon(icon, icon_size, icon_color);
+  const gfx::ImageSkia& old_normal_image =
+      button->GetImage(views::Button::STATE_NORMAL);
+  if (!new_normal_image.isNull() && !old_normal_image.isNull() &&
+      new_normal_image.BackedBySameObjectAs(old_normal_image)) {
+    return;
+  }
+
+  button->SetImage(views::Button::STATE_NORMAL, new_normal_image);
+  button->SetImage(
+      views::Button::STATE_DISABLED,
+      gfx::CreateVectorIcon(icon, icon_size, GetDisabledColor(normal_color)));
+}
+
 void AshColorProvider::AddObserver(ColorModeObserver* observer) {
   observers_.AddObserver(observer);
 }
@@ -299,7 +332,7 @@
 }
 
 bool AshColorProvider::IsDarkModeEnabled() const {
-  if (override_light_mode_as_default_)
+  if (!features::IsDarkLightModeEnabled() && override_light_mode_as_default_)
     return false;
 
   if (!active_user_pref_service_)
diff --git a/ash/style/ash_color_provider.h b/ash/style/ash_color_provider.h
index d5d4c02..6af3166b 100644
--- a/ash/style/ash_color_provider.h
+++ b/ash/style/ash_color_provider.h
@@ -121,6 +121,7 @@
   enum class ButtonType {
     kPillButtonWithIcon,
     kCloseButtonWithSmallBase,
+    kIconButtonSmallOrMedium,
   };
 
   // Attributes of ripple, includes the base color, opacity of inkdrop and
@@ -185,6 +186,11 @@
                            ButtonType type,
                            int button_size,
                            const gfx::VectorIcon& icon);
+  void DecorateIconButton(views::ImageButton* button,
+                          ButtonType type,
+                          const gfx::VectorIcon& icon,
+                          bool toggled,
+                          int icon_size);
 
   void AddObserver(ColorModeObserver* observer);
   void RemoveObserver(ColorModeObserver* observer);
@@ -227,8 +233,9 @@
   // Default color mode is dark, which is controlled by pref |kDarkModeEnabled|
   // currently. But we can also override it to light through
   // ScopedLightModeAsDefault. This is done to help keeping some of the UI
-  // elements as light by default before launching dark/light mode. This will be
-  // removed once enabling dark/light mode.
+  // elements as light by default before launching dark/light mode. Overriding
+  // only if the kDarkLightMode feature is disabled. This variable will be
+  // removed once enabled dark/light mode.
   bool override_light_mode_as_default_ = false;
 
   base::ObserverList<ColorModeObserver> observers_;
diff --git a/ash/style/scoped_light_mode_as_default.h b/ash/style/scoped_light_mode_as_default.h
index 52e902d..d2f91f1 100644
--- a/ash/style/scoped_light_mode_as_default.h
+++ b/ash/style/scoped_light_mode_as_default.h
@@ -10,7 +10,8 @@
 // A helper class to set default color mode to light. Color mode is dark by
 // default, which is controlled by pref |kDarkModeEnabled| currently. Some of
 // the components need to be kept as LIGHT by default before launching
-// dark/light mode. E.g. power button menu and system toast.
+// dark/light mode. E.g. power button menu and system toast. Overriding only if
+// the kDarkLightMode feature is disabled.
 class ScopedLightModeAsDefault {
  public:
   ScopedLightModeAsDefault();
diff --git a/ash/system/media/unified_media_controls_controller.cc b/ash/system/media/unified_media_controls_controller.cc
index a25bf344..e98683a6 100644
--- a/ash/system/media/unified_media_controls_controller.cc
+++ b/ash/system/media/unified_media_controls_controller.cc
@@ -109,9 +109,12 @@
   // Start hide controls timer if there is no active session, wait to
   // see if we will receive a new session.
   if (!request_id.has_value()) {
+    if (hide_artwork_timer_->IsRunning())
+      hide_artwork_timer_->Stop();
+
     hide_controls_timer_->Start(
         FROM_HERE, kHideControlsDelay,
-        base::BindOnce(&UnifiedMediaControlsController::HideControls,
+        base::BindOnce(&UnifiedMediaControlsController::ShowEmptyState,
                        base::Unretained(this)));
     return;
   }
@@ -119,6 +122,7 @@
   if (!media_session_id_.has_value())
     delegate_->ShowMediaControls();
   media_session_id_ = request_id;
+  media_controls_->OnNewMediaSession();
 }
 
 void UnifiedMediaControlsController::MediaControllerImageChanged(
@@ -175,9 +179,9 @@
   media_session::PerformMediaSessionAction(action, media_controller_remote_);
 }
 
-void UnifiedMediaControlsController::HideControls() {
+void UnifiedMediaControlsController::ShowEmptyState() {
   media_session_id_ = base::nullopt;
-  delegate_->HideMediaControls();
+  media_controls_->ShowEmptyState();
 }
 
 void UnifiedMediaControlsController::FlushForTesting() {
diff --git a/ash/system/media/unified_media_controls_controller.h b/ash/system/media/unified_media_controls_controller.h
index 1944089..d6d6cff 100644
--- a/ash/system/media/unified_media_controls_controller.h
+++ b/ash/system/media/unified_media_controls_controller.h
@@ -30,7 +30,6 @@
    public:
     virtual ~Delegate() = default;
     virtual void ShowMediaControls() = 0;
-    virtual void HideMediaControls() = 0;
     virtual void OnMediaControlsViewClicked() = 0;
   };
 
@@ -70,7 +69,7 @@
   }
 
  private:
-  void HideControls();
+  void ShowEmptyState();
 
   // Weak ptr, owned by view hierarchy.
   UnifiedMediaControlsView* media_controls_ = nullptr;
diff --git a/ash/system/media/unified_media_controls_controller_unittest.cc b/ash/system/media/unified_media_controls_controller_unittest.cc
index dd0e5b81..88051bc 100644
--- a/ash/system/media/unified_media_controls_controller_unittest.cc
+++ b/ash/system/media/unified_media_controls_controller_unittest.cc
@@ -36,7 +36,6 @@
   ~MockMediaControlsDelegate() override = default;
 
   void ShowMediaControls() override { visible_ = true; }
-  void HideMediaControls() override { visible_ = false; }
   MOCK_METHOD0(OnMediaControlsViewClicked, void());
 
   bool IsControlsVisible() { return visible_; }
@@ -130,6 +129,10 @@
     return static_cast<views::Button*>(*it);
   }
 
+  bool IsMediaControlsInEmptyState() const {
+    return media_controls_->is_in_empty_state_;
+  }
+
   SkPath GetArtworkClipPath() { return media_controls_->GetArtworkClipPath(); }
 
   views::View* button_row() { return media_controls_->button_row_; }
@@ -364,31 +367,123 @@
 }
 
 TEST_F(UnifiedMediaControlsControllerTest,
-       ShowHideControlsOnMediaSessionChanged) {
+       UpdateControlsStateOnMediaSessionChanged) {
   auto request_id = base::UnguessableToken::Create();
 
   EXPECT_FALSE(delegate()->IsControlsVisible());
   controller()->MediaSessionChanged(request_id);
   EXPECT_TRUE(delegate()->IsControlsVisible());
+  EXPECT_FALSE(IsMediaControlsInEmptyState());
 
   controller()->MediaSessionChanged(base::nullopt);
-  EXPECT_TRUE(delegate()->IsControlsVisible());
+  EXPECT_FALSE(IsMediaControlsInEmptyState());
 
-  // Still visible since we are within waiting delay time frame.
+  // Still in normal state since we are within waiting delay time frame.
   task_environment()->FastForwardBy(
       base::TimeDelta::FromMilliseconds(kHideControlsDelay - 1));
-  EXPECT_TRUE(delegate()->IsControlsVisible());
+  EXPECT_FALSE(IsMediaControlsInEmptyState());
 
-  // Session resumes, controls should still be visible.
+  // Session resumes, controls should still be in normal state.
   controller()->MediaSessionChanged(request_id);
   task_environment()->FastForwardBy(base::TimeDelta::FromMilliseconds(1));
-  EXPECT_TRUE(delegate()->IsControlsVisible());
+  EXPECT_FALSE(IsMediaControlsInEmptyState());
 
-  // Hide controls timer expired, controls should be hidden.
+  // Hide controls timer expired, controls should be in empty state.
   controller()->MediaSessionChanged(base::nullopt);
   task_environment()->FastForwardBy(
       base::TimeDelta::FromMilliseconds(kHideControlsDelay));
+  EXPECT_TRUE(IsMediaControlsInEmptyState());
+  EXPECT_TRUE(delegate()->IsControlsVisible());
+}
+
+TEST_F(UnifiedMediaControlsControllerTest, MediaControlsEmptyState) {
+  CreateWidget();
+
+  // Show media controls.
+  auto request_id = base::UnguessableToken::Create();
+  controller()->MediaSessionChanged(request_id);
+  EXPECT_TRUE(delegate()->IsControlsVisible());
+  EXPECT_FALSE(IsMediaControlsInEmptyState());
+
+  EnableAction(MediaSessionAction::kPlay);
+  EnableAction(MediaSessionAction::kPause);
+  EnableAction(MediaSessionAction::kPreviousTrack);
+  EnableAction(MediaSessionAction::kNextTrack);
+
+  EXPECT_TRUE(artist_label()->GetVisible());
+  EXPECT_FALSE(artwork_view()->GetVisible());
+  for (views::View* button : button_row()->children())
+    EXPECT_TRUE(button->GetEnabled());
+
+  // Media controls should be in empty state after getting empty session.
+  controller()->MediaSessionChanged(base::nullopt);
+  task_environment()->FastForwardBy(
+      base::TimeDelta::FromMilliseconds(kHideControlsDelay));
+
+  EXPECT_TRUE(IsMediaControlsInEmptyState());
+
+  // When in empty state, artist label should be hidden; artwork view
+  // should be hidden since it was hidden before getting into empty
+  // state; all action buttons should be disabled.
+  EXPECT_FALSE(artist_label()->GetVisible());
+  EXPECT_FALSE(artwork_view()->GetVisible());
+  for (views::View* button : button_row()->children())
+    EXPECT_FALSE(button->GetEnabled());
+
+  // Tapping on the media controls when we are in empty state should not
+  // notify delegate.
+  EXPECT_CALL(*delegate(), OnMediaControlsViewClicked).Times(0);
+  ui::test::EventGenerator* generator = GetEventGenerator();
+  generator->MoveMouseTo(
+      media_controls_view()->GetBoundsInScreen().CenterPoint());
+  generator->ClickLeftButton();
+
+  // Media controls should get back to normal state.
+  controller()->MediaSessionChanged(request_id);
+  EXPECT_FALSE(IsMediaControlsInEmptyState());
+
+  EXPECT_TRUE(artist_label()->GetVisible());
+  EXPECT_FALSE(artwork_view()->GetVisible());
+  for (views::View* button : button_row()->children())
+    EXPECT_TRUE(button->GetEnabled());
+
+  // User should be able to tap the controls for detailed view again.
+  EXPECT_CALL(*delegate(), OnMediaControlsViewClicked).Times(1);
+  generator->ClickLeftButton();
+}
+
+TEST_F(UnifiedMediaControlsControllerTest, MediaControlsEmptyStateWithArtwork) {
+  auto request_id = base::UnguessableToken::Create();
+
   EXPECT_FALSE(delegate()->IsControlsVisible());
+  controller()->MediaSessionChanged(request_id);
+  EXPECT_TRUE(delegate()->IsControlsVisible());
+  EXPECT_FALSE(IsMediaControlsInEmptyState());
+
+  // Artwork changed, and artwork view should have an empty background in normal
+  // state.
+  SkBitmap artwork;
+  artwork.allocN32Pixels(40, 40);
+  controller()->MediaControllerImageChanged(
+      media_session::mojom::MediaSessionImageType::kArtwork, artwork);
+  EXPECT_TRUE(artwork_view()->GetVisible());
+  EXPECT_EQ(artwork_view()->background(), nullptr);
+
+  controller()->MediaSessionChanged(base::nullopt);
+  task_environment()->FastForwardBy(
+      base::TimeDelta::FromMilliseconds(kHideControlsDelay));
+
+  // Artwork view should still be visible and have an background in empty state.
+  EXPECT_TRUE(IsMediaControlsInEmptyState());
+  EXPECT_TRUE(artwork_view()->GetVisible());
+  EXPECT_NE(artwork_view()->background(), nullptr);
+
+  // Session and artwork updated, artwotk view should be back in normal state.
+  controller()->MediaSessionChanged(request_id);
+  controller()->MediaControllerImageChanged(
+      media_session::mojom::MediaSessionImageType::kArtwork, artwork);
+  EXPECT_TRUE(artwork_view()->GetVisible());
+  EXPECT_EQ(artwork_view()->background(), nullptr);
 }
 
 TEST_F(UnifiedMediaControlsControllerTest, FreezeControlsWhenUpdateSession) {
diff --git a/ash/system/media/unified_media_controls_view.cc b/ash/system/media/unified_media_controls_view.cc
index d725157..602f8c5 100644
--- a/ash/system/media/unified_media_controls_view.cc
+++ b/ash/system/media/unified_media_controls_view.cc
@@ -41,6 +41,7 @@
 constexpr gfx::Insets kTrackColumnInsets = gfx::Insets(1, 0, 1, 0);
 constexpr gfx::Insets kMediaControlsViewInsets = gfx::Insets(8, 8, 8, 12);
 
+constexpr gfx::Size kEmptyArtworkIconSize = gfx::Size(20, 20);
 constexpr gfx::Size kArtworkSize = gfx::Size(40, 40);
 constexpr gfx::Size kMediaButtonSize = gfx::Size(32, 32);
 
@@ -116,6 +117,12 @@
                GetVectorIconForMediaAction(action), kMediaButtonIconSize,
                AshColorProvider::Get()->GetContentLayerColor(
                    AshColorProvider::ContentLayerType::kIconColorPrimary)));
+
+  SetImage(views::Button::STATE_DISABLED,
+           CreateVectorIcon(
+               GetVectorIconForMediaAction(action), kMediaButtonIconSize,
+               AshColorProvider::Get()->GetContentLayerColor(
+                   AshColorProvider::ContentLayerType::kIconColorSecondary)));
 }
 
 std::unique_ptr<views::InkDrop>
@@ -186,7 +193,7 @@
       kUnifiedMenuMoreIcon,
       AshColorProvider::Get()->GetContentLayerColor(
           AshColorProvider::ContentLayerType::kIconColorPrimary)));
-  title_row->AddChildView(std::move(drop_down_icon));
+  drop_down_icon_ = title_row->AddChildView(std::move(drop_down_icon));
 
   title_row_layout->SetFlexForView(title_label_, 1);
   track_column->AddChildView(std::move(title_row));
@@ -223,6 +230,9 @@
 
 void UnifiedMediaControlsView::ButtonPressed(views::Button* sender,
                                              const ui::Event& event) {
+  if (is_in_empty_state_)
+    return;
+
   if (sender == this) {
     controller_->OnMediaControlsViewClicked();
     return;
@@ -286,15 +296,66 @@
     button_row_->InvalidateLayout();
 }
 
-SkPath UnifiedMediaControlsView::GetArtworkClipPath() {
+void UnifiedMediaControlsView::ShowEmptyState() {
+  is_in_empty_state_ = true;
+
+  title_label_->SetText(
+      l10n_util::GetStringUTF16(IDS_ASH_GLOBAL_MEDIA_CONTROLS_NO_MEDIA_TEXT));
+  title_label_->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
+      AshColorProvider::ContentLayerType::kTextColorSecondary));
+  artist_label_->SetVisible(false);
+  drop_down_icon_->SetVisible(false);
+
+  for (views::View* button : button_row_->children())
+    button->SetEnabled(false);
+  InvalidateLayout();
+
+  if (!artwork_view_->GetVisible())
+    return;
+
+  artwork_view_->SetBackground(views::CreateSolidBackground(
+      AshColorProvider::Get()->GetControlsLayerColor(
+          AshColorProvider::ControlsLayerType::
+              kControlBackgroundColorInactive)));
+  artwork_view_->SetImageSize(kEmptyArtworkIconSize);
+  artwork_view_->SetImage(CreateVectorIcon(
+      kMusicNoteIcon, kEmptyArtworkIconSize.width(),
+      AshColorProvider::Get()->GetContentLayerColor(
+          AshColorProvider::ContentLayerType::kIconColorSecondary)));
+
+  artwork_view_->SetClipPath(GetArtworkClipPath(kArtworkSize));
+}
+
+void UnifiedMediaControlsView::OnNewMediaSession() {
+  if (!is_in_empty_state_)
+    return;
+
+  is_in_empty_state_ = false;
+  title_label_->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
+      AshColorProvider::ContentLayerType::kTextColorPrimary));
+  artist_label_->SetVisible(true);
+  drop_down_icon_->SetVisible(true);
+
+  for (views::View* button : button_row_->children())
+    button->SetEnabled(true);
+  InvalidateLayout();
+
+  if (!artwork_view_->GetVisible())
+    return;
+  artwork_view_->SetBackground(nullptr);
+}
+
+SkPath UnifiedMediaControlsView::GetArtworkClipPath(
+    base::Optional<gfx::Size> image_size) {
   // Calculate image bounds since we might need to draw this when image is
   // not visible (i.e. when quick setting bubble is collapsed).
-  gfx::Size image_size = artwork_view_->GetImageBounds().size();
-  int x = (kArtworkSize.width() - image_size.width()) / 2;
-  int y = (kArtworkSize.height() - image_size.height()) / 2;
+  if (!image_size.has_value())
+    image_size = artwork_view_->GetImageBounds().size();
+  int x = (kArtworkSize.width() - image_size->width()) / 2;
+  int y = (kArtworkSize.height() - image_size->height()) / 2;
   SkPath path;
-  path.addRoundRect(gfx::RectToSkRect(gfx::Rect(x, y, image_size.width(),
-                                                image_size.height())),
+  path.addRoundRect(gfx::RectToSkRect(gfx::Rect(x, y, image_size->width(),
+                                                image_size->height())),
                     kArtworkCornerRadius, kArtworkCornerRadius);
   return path;
 }
diff --git a/ash/system/media/unified_media_controls_view.h b/ash/system/media/unified_media_controls_view.h
index 91575e5..215745f 100644
--- a/ash/system/media/unified_media_controls_view.h
+++ b/ash/system/media/unified_media_controls_view.h
@@ -43,6 +43,13 @@
       const base::flat_set<media_session::mojom::MediaSessionAction>&
           enabled_actions);
 
+  // Show an empty state representing no media is playing.
+  void ShowEmptyState();
+
+  // Called when receiving new media session, update controls to normal state
+  // if necessary.
+  void OnNewMediaSession();
+
   views::ImageView* artwork_view() { return artwork_view_; }
 
  private:
@@ -65,15 +72,19 @@
     std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override;
   };
 
-  SkPath GetArtworkClipPath();
+  SkPath GetArtworkClipPath(
+      base::Optional<gfx::Size> image_size = base::nullopt);
 
   UnifiedMediaControlsController* const controller_ = nullptr;
 
   views::ImageView* artwork_view_ = nullptr;
+  views::ImageView* drop_down_icon_ = nullptr;
   views::Label* title_label_ = nullptr;
   views::Label* artist_label_ = nullptr;
   MediaActionButton* play_pause_button_ = nullptr;
   views::View* button_row_ = nullptr;
+
+  bool is_in_empty_state_ = false;
 };
 
 }  // namespace ash
diff --git a/ash/system/unified/feature_pod_button.cc b/ash/system/unified/feature_pod_button.cc
index e0c09d2..7394707 100644
--- a/ash/system/unified/feature_pod_button.cc
+++ b/ash/system/unified/feature_pod_button.cc
@@ -144,32 +144,9 @@
   if (!icon_)
     return;
 
-  const auto* color_provider = AshColorProvider::Get();
-  const SkColor normal_color =
-      color_provider->GetContentLayerColor(ContentLayerType::kButtonIconColor);
-  const SkColor toggled_icon_color = color_provider->GetContentLayerColor(
-      ContentLayerType::kButtonIconColorPrimary);
-  const SkColor icon_color = toggled_ ? toggled_icon_color : normal_color;
-
-  // Skip repainting if the incoming icon is the same as the current icon. If
-  // the icon has been painted before, |gfx::CreateVectorIcon()| will simply
-  // grab the ImageSkia from a cache, so it will be cheap. Note that this
-  // assumes that toggled/disabled images changes at the same time as the normal
-  // image, which it currently does.
-  const gfx::ImageSkia new_normal_image = gfx::CreateVectorIcon(
-      *icon_, kUnifiedFeaturePodVectorIconSize, icon_color);
-  const gfx::ImageSkia& old_normal_image =
-      GetImage(views::Button::STATE_NORMAL);
-  if (!new_normal_image.isNull() && !old_normal_image.isNull() &&
-      new_normal_image.BackedBySameObjectAs(old_normal_image)) {
-    return;
-  }
-
-  SetImage(views::Button::STATE_NORMAL, new_normal_image);
-  SetImage(
-      views::Button::STATE_DISABLED,
-      gfx::CreateVectorIcon(*icon_, kUnifiedFeaturePodVectorIconSize,
-                            AshColorProvider::GetDisabledColor(normal_color)));
+  AshColorProvider::Get()->DecorateIconButton(
+      this, AshColorProvider::ButtonType::kIconButtonSmallOrMedium, *icon_,
+      toggled_, kUnifiedFeaturePodVectorIconSize);
 }
 
 FeaturePodLabelButton::FeaturePodLabelButton(views::ButtonListener* listener)
diff --git a/ash/system/unified/unified_slider_view.cc b/ash/system/unified/unified_slider_view.cc
index 54b7a7c..90be5b0 100644
--- a/ash/system/unified/unified_slider_view.cc
+++ b/ash/system/unified/unified_slider_view.cc
@@ -74,7 +74,7 @@
 UnifiedSliderButton::UnifiedSliderButton(views::ButtonListener* listener,
                                          const gfx::VectorIcon& icon,
                                          int accessible_name_id)
-    : views::ToggleImageButton(listener) {
+    : views::ImageButton(listener) {
   SetImageHorizontalAlignment(ALIGN_CENTER);
   SetImageVerticalAlignment(ALIGN_MIDDLE);
   if (accessible_name_id)
@@ -105,24 +105,13 @@
 }
 
 void UnifiedSliderButton::SetVectorIcon(const gfx::VectorIcon& icon) {
-  const SkColor toggled_color = AshColorProvider::Get()->GetContentLayerColor(
-      ContentLayerType::kButtonIconColorPrimary);
-  const SkColor icon_color = AshColorProvider::Get()->GetContentLayerColor(
-      ContentLayerType::kButtonIconColor);
-
-  SetImage(views::Button::STATE_NORMAL,
-           gfx::CreateVectorIcon(icon, icon_color));
-
-  toggled_icon_ = gfx::CreateVectorIcon(icon, toggled_color);
-  SetToggledImage(views::Button::STATE_NORMAL, &toggled_icon_);
-
-  SetImage(views::Button::STATE_DISABLED,
-           gfx::CreateVectorIcon(icon, icon_color));
+  icon_ = &icon;
+  UpdateVectorIcon();
 }
 
 void UnifiedSliderButton::SetToggled(bool toggled) {
   toggled_ = toggled;
-  views::ToggleImageButton::SetToggled(toggled);
+  UpdateVectorIcon();
 }
 
 void UnifiedSliderButton::PaintButtonContents(gfx::Canvas* canvas) {
@@ -160,12 +149,21 @@
 void UnifiedSliderButton::GetAccessibleNodeData(ui::AXNodeData* node_data) {
   if (!GetEnabled())
     return;
-  views::ToggleImageButton::GetAccessibleNodeData(node_data);
+  views::ImageButton::GetAccessibleNodeData(node_data);
   node_data->role = ax::mojom::Role::kToggleButton;
   node_data->SetCheckedState(toggled_ ? ax::mojom::CheckedState::kTrue
                                       : ax::mojom::CheckedState::kFalse);
 }
 
+void UnifiedSliderButton::UpdateVectorIcon() {
+  if (!icon_)
+    return;
+
+  AshColorProvider::Get()->DecorateIconButton(
+      this, AshColorProvider::ButtonType::kIconButtonSmallOrMedium, *icon_,
+      toggled_, GetDefaultSizeOfVectorIcon(*icon_));
+}
+
 UnifiedSliderView::UnifiedSliderView(UnifiedSliderListener* listener,
                                      const gfx::VectorIcon& icon,
                                      int accessible_name_id,
diff --git a/ash/system/unified/unified_slider_view.h b/ash/system/unified/unified_slider_view.h
index c6a929c..49e1fa83 100644
--- a/ash/system/unified/unified_slider_view.h
+++ b/ash/system/unified/unified_slider_view.h
@@ -54,7 +54,7 @@
 };
 
 // A button used in a slider row of UnifiedSystemTray. The button is togglable.
-class UnifiedSliderButton : public views::ToggleImageButton {
+class UnifiedSliderButton : public views::ImageButton {
  public:
   UnifiedSliderButton(views::ButtonListener* listener,
                       const gfx::VectorIcon& icon,
@@ -73,7 +73,7 @@
   // views::Button:
   const char* GetClassName() const override;
 
-  // views::ToggleImageButton:
+  // views::ImageButton:
   std::unique_ptr<views::InkDrop> CreateInkDrop() override;
   std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override;
   std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
@@ -83,11 +83,12 @@
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
 
  private:
+  void UpdateVectorIcon();
+
   // True if the button is currently toggled.
   bool toggled_ = false;
 
-  // Icon used when the button is toggled.
-  gfx::ImageSkia toggled_icon_;
+  const gfx::VectorIcon* icon_ = nullptr;
 
   DISALLOW_COPY_AND_ASSIGN(UnifiedSliderButton);
 };
diff --git a/ash/system/unified/unified_system_tray_controller.cc b/ash/system/unified/unified_system_tray_controller.cc
index 17547310..35b66c4 100644
--- a/ash/system/unified/unified_system_tray_controller.cc
+++ b/ash/system/unified/unified_system_tray_controller.cc
@@ -430,10 +430,6 @@
   unified_view_->ShowMediaControls();
 }
 
-void UnifiedSystemTrayController::HideMediaControls() {
-  unified_view_->HideMediaControls();
-}
-
 void UnifiedSystemTrayController::OnMediaControlsViewClicked() {
   ShowMediaControlsDetailedView();
 }
diff --git a/ash/system/unified/unified_system_tray_controller.h b/ash/system/unified/unified_system_tray_controller.h
index b633f32..68c99b7a 100644
--- a/ash/system/unified/unified_system_tray_controller.h
+++ b/ash/system/unified/unified_system_tray_controller.h
@@ -134,7 +134,6 @@
 
   // UnifedMediaControlsController::Delegate;
   void ShowMediaControls() override;
-  void HideMediaControls() override;
   void OnMediaControlsViewClicked() override;
 
   UnifiedSystemTrayModel* model() { return model_; }
diff --git a/ash/system/unified/unified_system_tray_view.cc b/ash/system/unified/unified_system_tray_view.cc
index e909239..a064283 100644
--- a/ash/system/unified/unified_system_tray_view.cc
+++ b/ash/system/unified/unified_system_tray_view.cc
@@ -292,11 +292,6 @@
   PreferredSizeChanged();
 }
 
-void UnifiedSystemTrayView::HideMediaControls() {
-  media_controls_container_->SetShouldShowMediaControls(false);
-  PreferredSizeChanged();
-}
-
 void UnifiedSystemTrayView::SetDetailedView(views::View* detailed_view) {
   auto system_tray_size = system_tray_container_->GetPreferredSize();
   system_tray_container_->SetVisible(false);
diff --git a/ash/system/unified/unified_system_tray_view.h b/ash/system/unified/unified_system_tray_view.h
index 00fe842..f838c30 100644
--- a/ash/system/unified/unified_system_tray_view.h
+++ b/ash/system/unified/unified_system_tray_view.h
@@ -130,9 +130,8 @@
   // Settings).
   bool IsDetailedViewShown() const;
 
-  // Show and hide media controls view.
+  // Show media controls view.
   void ShowMediaControls();
-  void HideMediaControls();
 
   // views::View:
   gfx::Size CalculatePreferredSize() const override;
diff --git a/base/allocator/BUILD.gn b/base/allocator/BUILD.gn
index fbf0e7a..2ca6714 100644
--- a/base/allocator/BUILD.gn
+++ b/base/allocator/BUILD.gn
@@ -6,13 +6,6 @@
 import("//build/buildflag_header.gni")
 import("//build/config/compiler/compiler.gni")
 
-# This file depends on the legacy global sources assignment filter. It should
-# be converted to check target platform before assigning source files to the
-# sources variable. Remove this import and set_sources_assignment_filter call
-# when the file has been converted. See https://crbug.com/1018739 for details.
-import("//build/config/deprecated_default_sources_assignment_filter.gni")
-set_sources_assignment_filter(deprecated_default_sources_assignment_filter)
-
 declare_args() {
   # Provide a way to force disable debugallocation in Debug builds,
   # e.g. for profiling (it's more rare to profile Debug builds,
@@ -95,9 +88,6 @@
       # Generated for our configuration from tcmalloc's build
       # and checked in.
       "$tcmalloc_dir/src/config.h",
-      "$tcmalloc_dir/src/config_android.h",
-      "$tcmalloc_dir/src/config_linux.h",
-      "$tcmalloc_dir/src/config_win.h",
 
       # tcmalloc native and forked files.
       "$tcmalloc_dir/src/base/abort.cc",
@@ -186,6 +176,18 @@
       #"win_allocator.cc",
     ]
 
+    if (is_android) {
+      sources += [ "$tcmalloc_dir/src/config_android.h" ]
+    }
+
+    if (is_linux || is_chromeos) {
+      sources += [ "$tcmalloc_dir/src/config_linux.h" ]
+    }
+
+    if (is_win) {
+      sources += [ "$tcmalloc_dir/src/config_win.h" ]
+    }
+
     # Not included on mips64el.
     if (current_cpu == "mips64el") {
       sources -= [
diff --git a/base/values.cc b/base/values.cc
index c06458bc..4928e830 100644
--- a/base/values.cc
+++ b/base/values.cc
@@ -1442,10 +1442,6 @@
   list().clear();
 }
 
-void ListValue::Reserve(size_t n) {
-  list().reserve(n);
-}
-
 bool ListValue::Set(size_t index, std::unique_ptr<Value> in_value) {
   if (!in_value)
     return false;
diff --git a/base/values.h b/base/values.h
index a7fba47..00ba152 100644
--- a/base/values.h
+++ b/base/values.h
@@ -621,10 +621,10 @@
   Value* SetString(StringPiece path, StringPiece in_value);
   // DEPRECATED, use Value::SetStringPath().
   Value* SetString(StringPiece path, const string16& in_value);
-  // DEPRECATED, use Value::SetPath() or Value::SetDictPath()
+  // DEPRECATED, use Value::SetPath().
   DictionaryValue* SetDictionary(StringPiece path,
                                  std::unique_ptr<DictionaryValue> in_value);
-  // DEPRECATED, use Value::SetPath() or Value::SetListPath()
+  // DEPRECATED, use Value::SetPath().
   ListValue* SetList(StringPiece path, std::unique_ptr<ListValue> in_value);
 
   // Like Set(), but without special treatment of '.'.  This allows e.g. URLs to
@@ -801,11 +801,6 @@
   // DEPRECATED, use GetList()::empty() instead.
   bool empty() const { return list().empty(); }
 
-  // Reserves storage for at least |n| values.
-  // DEPRECATED, first construct a base::Value::ListStorage and use
-  // base::Value::ListStorage::reserve() instead.
-  void Reserve(size_t n);
-
   // Sets the list item at the given index to be the Value specified by
   // the value given.  If the index beyond the current end of the list, null
   // Values will be used to pad out the list.
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 3e92f4f..900c75e9 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-0.20201001.2.1
+0.20201001.3.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 3e92f4f..900c75e9 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-0.20201001.2.1
+0.20201001.3.1
diff --git a/cc/benchmarks/rasterize_and_record_benchmark.cc b/cc/benchmarks/rasterize_and_record_benchmark.cc
index 3f15604a..5c23cd8 100644
--- a/cc/benchmarks/rasterize_and_record_benchmark.cc
+++ b/cc/benchmarks/rasterize_and_record_benchmark.cc
@@ -8,12 +8,12 @@
 
 #include <algorithm>
 #include <limits>
+#include <memory>
 #include <string>
 
 #include "base/bind.h"
 #include "base/memory/ptr_util.h"
 #include "base/strings/stringprintf.h"
-#include "base/timer/lap_timer.h"
 #include "base/values.h"
 #include "cc/benchmarks/rasterize_and_record_benchmark_impl.h"
 #include "cc/layers/content_layer_client.h"
@@ -30,29 +30,6 @@
 
 const int kDefaultRecordRepeatCount = 100;
 
-const char* kModeSuffixes[RecordingSource::RECORDING_MODE_COUNT] = {
-    "",
-    "_caching_disabled",
-    "_subsequence_caching_disabled",
-    "_partial_invalidation"};
-
-ContentLayerClient::PaintingControlSetting
-RecordingModeToPaintingControlSetting(RecordingSource::RecordingMode mode) {
-  switch (mode) {
-    case RecordingSource::RECORD_NORMALLY:
-      return ContentLayerClient::PAINTING_BEHAVIOR_NORMAL_FOR_TEST;
-    case RecordingSource::RECORD_WITH_CACHING_DISABLED:
-      return ContentLayerClient::DISPLAY_LIST_CACHING_DISABLED;
-    case RecordingSource::RECORD_WITH_SUBSEQUENCE_CACHING_DISABLED:
-      return ContentLayerClient::SUBSEQUENCE_CACHING_DISABLED;
-    case RecordingSource::RECORD_WITH_PARTIAL_INVALIDATION:
-      return ContentLayerClient::PARTIAL_INVALIDATION;
-    case RecordingSource::RECORDING_MODE_COUNT:
-      NOTREACHED();
-  }
-  return ContentLayerClient::PAINTING_BEHAVIOR_NORMAL_FOR_TEST;
-}
-
 }  // namespace
 
 RasterizeAndRecordBenchmark::RasterizeAndRecordBenchmark(
@@ -88,6 +65,10 @@
   for (auto* layer : *layer_tree_host)
     layer->RunMicroBenchmark(this);
 
+  PaintBenchmarkResult paint_benchmark_result;
+  layer_tree_host->client()->RunPaintBenchmark(record_repeat_count_,
+                                               paint_benchmark_result);
+
   DCHECK(!results_.get());
   results_ = base::WrapUnique(new base::DictionaryValue);
   results_->SetInteger("pixels_recorded", record_results_.pixels_recorded);
@@ -97,12 +78,21 @@
                        static_cast<int>(record_results_.paint_op_memory_usage));
   results_->SetInteger("paint_op_count",
                        static_cast<int>(record_results_.paint_op_count));
-
-  for (int i = 0; i < RecordingSource::RECORDING_MODE_COUNT; i++) {
-    std::string name = base::StringPrintf("record_time%s_ms", kModeSuffixes[i]);
-    results_->SetDouble(name,
-                        record_results_.total_best_time[i].InMillisecondsF());
-  }
+  results_->SetDouble("record_time_ms", paint_benchmark_result.record_time_ms);
+  results_->SetDouble("record_time_caching_disabled_ms",
+                      paint_benchmark_result.record_time_caching_disabled_ms);
+  results_->SetDouble(
+      "record_time_subsequence_caching_disabled_ms",
+      paint_benchmark_result.record_time_subsequence_caching_disabled_ms);
+  results_->SetDouble(
+      "record_time_partial_invalidation_ms",
+      paint_benchmark_result.record_time_partial_invalidation_ms);
+  results_->SetDouble(
+      "raster_invalidation_and_convert_time_ms",
+      paint_benchmark_result.raster_invalidation_and_convert_time_ms);
+  results_->SetDouble(
+      "paint_artifact_compositor_update_time_ms",
+      paint_benchmark_result.paint_artifact_compositor_update_time_ms);
   main_thread_benchmark_done_ = true;
 }
 
@@ -134,64 +124,21 @@
   if (!layer->DrawsContent())
     return;
 
-  const int kTimeCheckInterval = 1;
-  const int kWarmupRuns = 0;
-  const int kTimeLimitMillis = 1;
   ContentLayerClient* painter = layer->client();
   RecordingSource recording_source;
 
-  for (int mode_index = 0; mode_index < RecordingSource::RECORDING_MODE_COUNT;
-       mode_index++) {
-    ContentLayerClient::PaintingControlSetting painting_control =
-        RecordingModeToPaintingControlSetting(
-            static_cast<RecordingSource::RecordingMode>(mode_index));
-    base::TimeDelta min_time = base::TimeDelta::Max();
-    size_t paint_op_memory_usage = 0;
-    size_t paint_op_count = 0;
+  scoped_refptr<DisplayItemList> display_list;
+  display_list = painter->PaintContentsToDisplayList();
+  recording_source.UpdateDisplayItemList(
+      display_list, painter->GetApproximateUnsharedMemoryUsage(),
+      layer_tree_host_->recording_scale_factor());
 
-    scoped_refptr<DisplayItemList> display_list;
-    for (int i = 0; i < record_repeat_count_; ++i) {
-      // Run for a minimum amount of time to avoid problems with timer
-      // quantization when the layer is very small.
-      base::LapTimer timer(kWarmupRuns,
-                           base::TimeDelta::FromMilliseconds(kTimeLimitMillis),
-                           kTimeCheckInterval);
-
-      do {
-        display_list = painter->PaintContentsToDisplayList(painting_control);
-        recording_source.UpdateDisplayItemList(
-            display_list, painter->GetApproximateUnsharedMemoryUsage(),
-            layer_tree_host_->recording_scale_factor());
-
-        if (paint_op_memory_usage) {
-          // Verify we are recording the same thing each time.
-          DCHECK_EQ(paint_op_memory_usage, display_list->BytesUsed());
-          DCHECK_EQ(paint_op_count, display_list->TotalOpCount());
-        } else {
-          paint_op_memory_usage = display_list->BytesUsed();
-          paint_op_count = display_list->TotalOpCount();
-        }
-
-        timer.NextLap();
-      } while (!timer.HasTimeLimitExpired());
-      base::TimeDelta duration = timer.TimePerLap();
-      if (duration < min_time)
-        min_time = duration;
-    }
-
-    if (mode_index == RecordingSource::RECORD_NORMALLY) {
-      record_results_.painter_memory_usage +=
-          painter->GetApproximateUnsharedMemoryUsage();
-      record_results_.paint_op_memory_usage += paint_op_memory_usage;
-      record_results_.paint_op_count += paint_op_count;
-      record_results_.pixels_recorded += painter->PaintableRegion().width() *
-                                         painter->PaintableRegion().height();
-    }
-    record_results_.total_best_time[mode_index] += min_time;
-  }
+  record_results_.painter_memory_usage +=
+      painter->GetApproximateUnsharedMemoryUsage();
+  record_results_.paint_op_memory_usage += display_list->BytesUsed();
+  record_results_.paint_op_count += display_list->TotalOpCount();
+  record_results_.pixels_recorded +=
+      painter->PaintableRegion().width() * painter->PaintableRegion().height();
 }
 
-RasterizeAndRecordBenchmark::RecordResults::RecordResults() = default;
-RasterizeAndRecordBenchmark::RecordResults::~RecordResults() = default;
-
 }  // namespace cc
diff --git a/cc/benchmarks/rasterize_and_record_benchmark.h b/cc/benchmarks/rasterize_and_record_benchmark.h
index 8ca7c588..f66314f3 100644
--- a/cc/benchmarks/rasterize_and_record_benchmark.h
+++ b/cc/benchmarks/rasterize_and_record_benchmark.h
@@ -8,6 +8,7 @@
 #include <stddef.h>
 
 #include <map>
+#include <memory>
 #include <utility>
 #include <vector>
 
@@ -42,14 +43,10 @@
   void RecordRasterResults(std::unique_ptr<base::Value> results);
 
   struct RecordResults {
-    RecordResults();
-    ~RecordResults();
-
     int pixels_recorded = 0;
     size_t painter_memory_usage = 0;
     size_t paint_op_memory_usage = 0;
     size_t paint_op_count = 0;
-    base::TimeDelta total_best_time[RecordingSource::RECORDING_MODE_COUNT];
   };
 
   RecordResults record_results_;
diff --git a/cc/layers/content_layer_client.h b/cc/layers/content_layer_client.h
index 285816c..e782c5b 100644
--- a/cc/layers/content_layer_client.h
+++ b/cc/layers/content_layer_client.h
@@ -18,14 +18,6 @@
 
 class CC_EXPORT ContentLayerClient {
  public:
-  enum PaintingControlSetting {
-    PAINTING_BEHAVIOR_NORMAL,
-    PAINTING_BEHAVIOR_NORMAL_FOR_TEST,
-    DISPLAY_LIST_CACHING_DISABLED,
-    SUBSEQUENCE_CACHING_DISABLED,
-    PARTIAL_INVALIDATION,
-  };
-
   // The paintable region is the rectangular region, within the bounds of the
   // layer this client paints, that the client is capable of painting via
   // paintContents(). Calling paintContents() will return a DisplayItemList
@@ -36,8 +28,7 @@
   // to the layer itself, into a DisplayItemList that it returns. The
   // PaintingControlSetting enum controls painting to isolate different
   // components in performance tests.
-  virtual scoped_refptr<DisplayItemList> PaintContentsToDisplayList(
-      PaintingControlSetting painting_control) = 0;
+  virtual scoped_refptr<DisplayItemList> PaintContentsToDisplayList() = 0;
 
   // If true the layer may skip clearing the background before rasterizing,
   // because it will cover any uncleared data with content.
diff --git a/cc/layers/picture_layer.cc b/cc/layers/picture_layer.cc
index 852c77d..e4a53b29 100644
--- a/cc/layers/picture_layer.cc
+++ b/cc/layers/picture_layer.cc
@@ -141,8 +141,7 @@
     {
       auto old_display_list = std::move(picture_layer_inputs_.display_list);
       picture_layer_inputs_.display_list =
-          picture_layer_inputs_.client->PaintContentsToDisplayList(
-              ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+          picture_layer_inputs_.client->PaintContentsToDisplayList();
       if (old_display_list &&
           picture_layer_inputs_.display_list
               ->NeedsAdditionalInvalidationForLCDText(*old_display_list)) {
@@ -190,8 +189,7 @@
     return nullptr;
 
   scoped_refptr<DisplayItemList> display_list =
-      picture_layer_inputs_.client->PaintContentsToDisplayList(
-          ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+      picture_layer_inputs_.client->PaintContentsToDisplayList();
   SkPictureRecorder recorder;
   SkCanvas* canvas =
       recorder.beginRecording(bounds().width(), bounds().height());
diff --git a/cc/layers/picture_layer_impl_unittest.cc b/cc/layers/picture_layer_impl_unittest.cc
index 627e929..3b000ca9 100644
--- a/cc/layers/picture_layer_impl_unittest.cc
+++ b/cc/layers/picture_layer_impl_unittest.cc
@@ -4724,8 +4724,7 @@
                                         gfx::Size layer_bounds) {
   gfx::Rect new_recorded_viewport = client->PaintableRegion();
   scoped_refptr<DisplayItemList> display_list =
-      client->PaintContentsToDisplayList(
-          ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+      client->PaintContentsToDisplayList();
   size_t painter_reported_memory_usage =
       client->GetApproximateUnsharedMemoryUsage();
 
diff --git a/cc/metrics/frame_sequence_metrics.cc b/cc/metrics/frame_sequence_metrics.cc
index d6154c69..dd025972 100644
--- a/cc/metrics/frame_sequence_metrics.cc
+++ b/cc/metrics/frame_sequence_metrics.cc
@@ -354,37 +354,13 @@
 
 void FrameSequenceMetrics::ComputeJank(
     FrameSequenceMetrics::ThreadType thread_type,
-    uint32_t frame_token,
     base::TimeTicks presentation_time,
     base::TimeDelta frame_interval) {
   if (!jank_reporter_)
     return;
 
   if (thread_type == jank_reporter_->thread_type())
-    jank_reporter_->AddPresentedFrame(frame_token, presentation_time,
-                                      frame_interval);
-}
-
-void FrameSequenceMetrics::NotifySubmitForJankReporter(
-    FrameSequenceMetrics::ThreadType thread_type,
-    uint32_t frame_token,
-    uint32_t sequence_number) {
-  if (!jank_reporter_)
-    return;
-
-  if (thread_type == jank_reporter_->thread_type())
-    jank_reporter_->AddSubmitFrame(frame_token, sequence_number);
-}
-
-void FrameSequenceMetrics::NotifyNoUpdateForJankReporter(
-    FrameSequenceMetrics::ThreadType thread_type,
-    uint32_t sequence_number,
-    base::TimeDelta frame_interval) {
-  if (!jank_reporter_)
-    return;
-
-  if (thread_type == jank_reporter_->thread_type())
-    jank_reporter_->AddFrameWithNoUpdate(sequence_number, frame_interval);
+    jank_reporter_->AddPresentedFrame(presentation_time, frame_interval);
 }
 
 bool FrameSequenceMetrics::ThroughputData::CanReportHistogram(
diff --git a/cc/metrics/frame_sequence_metrics.h b/cc/metrics/frame_sequence_metrics.h
index b7a3313..13ab2823 100644
--- a/cc/metrics/frame_sequence_metrics.h
+++ b/cc/metrics/frame_sequence_metrics.h
@@ -150,19 +150,9 @@
   void AdvanceTrace(base::TimeTicks timestamp);
 
   void ComputeJank(FrameSequenceMetrics::ThreadType thread_type,
-                   uint32_t frame_token,
                    base::TimeTicks presentation_time,
                    base::TimeDelta frame_interval);
 
-  void NotifySubmitForJankReporter(FrameSequenceMetrics::ThreadType thread_type,
-                                   uint32_t frame_token,
-                                   uint32_t sequence_number);
-
-  void NotifyNoUpdateForJankReporter(
-      FrameSequenceMetrics::ThreadType thread_type,
-      uint32_t sequence_number,
-      base::TimeDelta frame_interval);
-
  private:
   const FrameSequenceTrackerType type_;
 
diff --git a/cc/metrics/frame_sequence_tracker.cc b/cc/metrics/frame_sequence_tracker.cc
index 03b59ce..b05d5a2 100644
--- a/cc/metrics/frame_sequence_tracker.cc
+++ b/cc/metrics/frame_sequence_tracker.cc
@@ -276,9 +276,6 @@
 
   TRACKER_TRACE_STREAM << "s(" << frame_token % kDebugStrMod << ")";
   had_impl_frame_submitted_between_commits_ = true;
-  metrics()->NotifySubmitForJankReporter(
-      FrameSequenceMetrics::ThreadType::kCompositor, frame_token,
-      ack.frame_id.sequence_number);
 
   const bool main_changes_after_sequence_started =
       first_received_main_sequence_ &&
@@ -301,9 +298,6 @@
                            << origin_args.frame_id.sequence_number %
                                   kDebugStrMod
                            << ")";
-      metrics()->NotifySubmitForJankReporter(
-          FrameSequenceMetrics::ThreadType::kMain, frame_token,
-          origin_args.frame_id.sequence_number);
 
       last_submitted_main_sequence_ = origin_args.frame_id.sequence_number;
       main_frames_.push_back(frame_token);
@@ -364,9 +358,6 @@
               impl_throughput().frames_ontime)
         << TRACKER_DCHECK_MSG;
     --impl_throughput().frames_expected;
-    metrics()->NotifyNoUpdateForJankReporter(
-        FrameSequenceMetrics::ThreadType::kCompositor,
-        args.frame_id.sequence_number, args.interval);
 #if DCHECK_IS_ON()
     ++impl_throughput().frames_processed;
     // If these two are the same, it means that each impl frame is either
@@ -470,7 +461,7 @@
     }
 
     metrics()->ComputeJank(FrameSequenceMetrics::ThreadType::kCompositor,
-                           frame_token, feedback.timestamp, feedback.interval);
+                           feedback.timestamp, feedback.interval);
   }
 
   if (was_presented) {
@@ -492,8 +483,7 @@
       }
 
       metrics()->ComputeJank(FrameSequenceMetrics::ThreadType::kMain,
-                             frame_token, feedback.timestamp,
-                             feedback.interval);
+                             feedback.timestamp, feedback.interval);
     }
     if (main_frames_.size() < size_before_erase) {
       if (!last_frame_presentation_timestamp_.is_null() &&
@@ -608,10 +598,6 @@
       << TRACKER_DCHECK_MSG;
   last_no_main_damage_sequence_ = args.frame_id.sequence_number;
   --main_throughput().frames_expected;
-  metrics()->NotifyNoUpdateForJankReporter(
-      FrameSequenceMetrics::ThreadType::kMain, args.frame_id.sequence_number,
-      args.interval);
-
   DCHECK_GE(main_throughput().frames_expected, main_frames_.size())
       << TRACKER_DCHECK_MSG;
 
diff --git a/cc/metrics/jank_metrics.cc b/cc/metrics/jank_metrics.cc
index 02fb65d..1164269c 100644
--- a/cc/metrics/jank_metrics.cc
+++ b/cc/metrics/jank_metrics.cc
@@ -67,90 +67,19 @@
 }
 JankMetrics::~JankMetrics() = default;
 
-void JankMetrics::AddSubmitFrame(uint32_t frame_token,
-                                 uint32_t sequence_number) {
-  // When a frame is submitted, record its |frame_token| and its associated
-  // |sequence_number|. This pushed item will be removed when this frame is
-  // presented.
-  queue_frame_token_and_id_.push({frame_token, sequence_number});
-}
-
-void JankMetrics::AddFrameWithNoUpdate(uint32_t sequence_number,
-                                       base::TimeDelta frame_interval) {
-  // If a frame does not cause an increase in expected frames, it will be
-  // recorded here and later subtracted from the presentation interval that
-  // includes this frame.
-  queue_frame_id_and_interval_.push({sequence_number, frame_interval});
-}
-
 void JankMetrics::AddPresentedFrame(
-    uint32_t presented_frame_token,
     base::TimeTicks current_presentation_timestamp,
     base::TimeDelta frame_interval) {
-  uint32_t presented_frame_id = 0;
+  base::TimeDelta current_frame_delta =
+      current_presentation_timestamp - last_presentation_timestamp_;
 
-  // Find the main_sequence_number of the presented_frame_token
-  while (!queue_frame_token_and_id_.empty()) {
-    auto token_and_id = queue_frame_token_and_id_.front();
-
-    if (token_and_id.first > presented_frame_token) {
-      // The submitting of this presented frame was not recorded (e.g. the
-      // submitting might have occurred before JankMetrics starts recording).
-      // In that case, do not use this frame presentation for jank detection.
-      return;
-    }
-    queue_frame_token_and_id_.pop();
-
-    if (token_and_id.first == presented_frame_token) {
-      // Found information about the submit of this presented frame;
-      // retrieve the frame's sequence number.
-      presented_frame_id = token_and_id.second;
-      break;
-    }
-  }
-  // If for any reason the sequence number associated with the
-  // presented_frame_token cannot be identified, then ignore this frame
-  // presentation.
-  if (presented_frame_id == 0)
-    return;
-
-  base::TimeDelta no_update_time;  // The frame time spanned by the frames that
-                                   // have no updates
-
-  // Compute the presentation delay contributed by no-update frames that began
-  // BEFORE (i.e. have smaller sequence number than) the current presented
-  // frame.
-  while (!queue_frame_id_and_interval_.empty() &&
-         queue_frame_id_and_interval_.front().first < presented_frame_id) {
-    auto id_and_interval = queue_frame_id_and_interval_.front();
-    if (id_and_interval.first >= last_presentation_frame_id_) {
-      // Only count no-update frames that began SINCE (i.e. have a greater [or
-      // equal] sequence number than) the beginning of previous presented frame.
-      // If, in rare cases, there are still no-update frames that began BEFORE
-      // the beginning of previous presented frame left in the queue, those
-      // frames will simply be discarded and not counted into |no_update_time|.
-      no_update_time += id_and_interval.second;
-    }
-    queue_frame_id_and_interval_.pop();
-  }
-
-  // Exclude the presentation delay introduced by no-update frames. If this
-  // exclusion results in negative frame delta, treat the frame delta as 0.
-  base::TimeDelta current_frame_delta = current_presentation_timestamp -
-                                        last_presentation_timestamp_ -
-                                        no_update_time;
-  if (current_frame_delta < base::TimeDelta::FromMilliseconds(0))
-    current_frame_delta = base::TimeDelta::FromMilliseconds(0);
-
-  // Only start tracking jank if this function has already been
-  // called at least once (so that |last_presentation_timestamp_|
-  // and |prev_frame_delta_| have been set).
+  // Only start tracking jank if this function has been called (so that
+  // |last_presentation_timestamp_| and |prev_frame_delta_| have been set).
   //
-  // The presentation interval is typically a multiple of VSync
-  // intervals (i.e. 16.67ms, 33.33ms, 50ms ... on a 60Hz display)
-  // with small fluctuations. The 0.5 * |frame_interval| criterion
-  // is chosen so that the jank detection is robust to those
-  // fluctuations.
+  // The presentation interval is typically a multiple of VSync intervals (i.e.
+  // 16.67ms, 33.33ms, 50ms ... on a 60Hz display) with small fluctuations. The
+  // 0.5 * |frame_interval| criterion is chosen so that the jank detection is
+  // robust to those fluctuations.
   if (!last_presentation_timestamp_.is_null() && !prev_frame_delta_.is_zero() &&
       current_frame_delta > prev_frame_delta_ + 0.5 * frame_interval) {
     jank_count_++;
@@ -165,7 +94,7 @@
         FrameSequenceTracker::GetFrameSequenceTrackerTypeName(tracker_type_));
   }
   last_presentation_timestamp_ = current_presentation_timestamp;
-  last_presentation_frame_id_ = presented_frame_id;
+
   prev_frame_delta_ = current_frame_delta;
 }
 
diff --git a/cc/metrics/jank_metrics.h b/cc/metrics/jank_metrics.h
index 09c6cff..71aca6b 100644
--- a/cc/metrics/jank_metrics.h
+++ b/cc/metrics/jank_metrics.h
@@ -6,8 +6,6 @@
 #define CC_METRICS_JANK_METRICS_H_
 
 #include <memory>
-#include <queue>
-#include <utility>
 
 #include "cc/metrics/frame_sequence_metrics.h"
 
@@ -23,8 +21,7 @@
 
   // Check if a jank occurs based on the timestamps of recent presentations.
   // If there is a jank, increment |jank_count_| and log a trace event.
-  void AddPresentedFrame(uint32_t presented_frame_token,
-                         base::TimeTicks current_presentation_timestamp,
+  void AddPresentedFrame(base::TimeTicks current_presentation_timestamp,
                          base::TimeDelta frame_interval);
 
   // Report the occurrence rate of janks as a UMA metric.
@@ -33,10 +30,6 @@
   // Merge the current jank count with a previously unreported jank metrics.
   void Merge(std::unique_ptr<JankMetrics> jank_metrics);
 
-  void AddSubmitFrame(uint32_t frame_token, uint32_t sequence_number);
-
-  void AddFrameWithNoUpdate(uint32_t sequence_number,
-                            base::TimeDelta frame_interval);
   FrameSequenceMetrics::ThreadType thread_type() const {
     return effective_thread_;
   }
@@ -55,19 +48,8 @@
   // The time when the last presentation occurs
   base::TimeTicks last_presentation_timestamp_;
 
-  // The sequence number associated with the last presented frame
-  uint32_t last_presentation_frame_id_;
-
   // The interval before the previous frame presentation.
   base::TimeDelta prev_frame_delta_;
-
-  // A queue storing {frame token, sequence number} for all submitted
-  // frames, in ascending order of frame token.
-  std::queue<std::pair<uint32_t, uint32_t>> queue_frame_token_and_id_;
-
-  // A queue storing {sequence number, frame interval} of unprocessed no-update
-  // frames, in ascending order of sequence number.
-  std::queue<std::pair<uint32_t, base::TimeDelta>> queue_frame_id_and_interval_;
 };
 
 }  // namespace cc
diff --git a/cc/metrics/jank_metrics_unittest.cc b/cc/metrics/jank_metrics_unittest.cc
index 18c23ec..e96e7dd 100644
--- a/cc/metrics/jank_metrics_unittest.cc
+++ b/cc/metrics/jank_metrics_unittest.cc
@@ -6,7 +6,6 @@
 
 #include <memory>
 #include <string>
-#include <unordered_map>
 #include <utility>
 #include <vector>
 
@@ -20,16 +19,6 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-namespace {
-const base::TimeDelta kDefaultFrameInterval =
-    base::TimeDelta::FromMillisecondsD(16.67);
-
-// All sequence numbers for simulated frame events will start at this number.
-// This makes it easier to numerically distinguish sequence numbers versus
-// frame tokens, which always start at 1.
-const uint32_t kSequenceNumberStartsAt = 100u;
-}  // namespace
-
 namespace cc {
 
 class JankMetricsTest : public testing::Test {
@@ -37,99 +26,42 @@
   JankMetricsTest() = default;
   ~JankMetricsTest() override = default;
 
-  // Simulate a series of Submit, NoUpdate, and Presentation events and notify
-  // |jank_reporter|, as specified by |frame_sequences|. The exact presentation
-  // time of frames can be slightly manipulated by |presentation_time_shifts|.
-  void SimulateFrameSequence(
-      JankMetrics* jank_reporter,
-      const std::array<std::string, 3>& frame_sequences,
-      const std::unordered_map<char, double>& presentation_time_shifts = {}) {
-    // |frame_sequences| is an array of 3 strings of EQUAL LENGTH, representing
-    // the (S)UBMIT, (N)O-UPDATE, (P)RESENTATION events, respectively. In all 3
-    // strings:
-    //   any char == a vsync interval (16.67ms) with a unique sequence number.
-    //   '-' == no event in this vsync interval.
-    // In SUBMIT string:
-    //   [a-zA-Z] == A SUBMIT occurs at this vsync interval. Each symbol in this
-    //   string must be unique.
-    // In NO-UPDATE string:
-    //   Any non '-' letter == A NO-UPDATE frame is reported at this vsync
-    //   interval.
-    // In PRESENTATION string:
-    //   [a-zA-Z] == A PRESENTATION occurs at this vsync interval. Each
-    //   symbol must be unique and MUST HAVE APPEARED in the SUBMIT string.
-    //
-    // NOTE this test file stylistically denotes the frames that should jank
-    // with uppercases (although this is not a strict).
-    //
-    // Each item in |presentation_time_shifts| maps a presentation frame letter
-    // (must have appeared in string P) to how much time (in ms) this
-    // presentation deviates from expected. For example: {'a': -3.2} means frame
-    // 'a' is presented 3.2ms before expected.
-    //
-    // e.g.
-    // S = "a-b--c--D--"
-    // N = "---**------"
-    // P = "-a-b---c-D-"
-    // presentation_time_shifts = {'c':-8.4, 'D':8.4}
-    //
-    // means submit at vsync 0, 2, 5, 8, presentation at 1, 3, 7, 9. Due to the
-    // no-update frames 3 and 4, no janks will be reported for 'c'. However, the
-    // large fluctuation of presentation time of 'c' and 'D', there is a jank
-    // at 'D'.
-    //
-    // Without the no-update frames and presentation_time_shifts, one jank would
-    // have been reported at 'c'.
-    auto& submits = frame_sequences[0];
-    auto& ignores = frame_sequences[1];
-    auto& presnts = frame_sequences[2];
+  // Create a sequence of PresentationFeedback for testing based on the provided
+  // sequence of actual frame intervals and the expected frame interval. The
+  // size of the returned sequence is |actual_intervals_ms|.size() + 1
+  static std::vector<gfx::PresentationFeedback> CreateFeedbackSequence(
+      const std::vector<double>& actual_intervals_ms,
+      double expected_interval_ms) {
+    std::vector<gfx::PresentationFeedback> feedbacks;
 
-    // All three sequences must have the same size.
-    EXPECT_EQ(submits.size(), ignores.size());
-    EXPECT_EQ(submits.size(), presnts.size());
-
-    // Map submitted frame to their tokens
-    std::unordered_map<char, uint32_t> submit_to_token;
-
+    // The timestamp of the first presentation.
     base::TimeTicks start_time = base::TimeTicks::Now();
+    double accum_interval = 0.0;
+    base::TimeDelta expected_interval =
+        base::TimeDelta::FromMillisecondsD(expected_interval_ms);
 
-    // Scan S to collect all symbols
-    for (uint32_t frame_token = 1, i = 0; i < submits.size(); ++i) {
-      uint32_t sequence_number = kSequenceNumberStartsAt + i;
-      if (submits[i] != '-') {
-        submit_to_token[submits[i]] = frame_token;
-        jank_reporter->AddSubmitFrame(/*frame_token=*/frame_token,
-                                      /*sequence_number=*/sequence_number);
-        frame_token++;
-      }
+    feedbacks.emplace_back(
+        gfx::PresentationFeedback(start_time, expected_interval, 0));
+    for (auto interval : actual_intervals_ms) {
+      accum_interval += interval;
+      feedbacks.emplace_back(gfx::PresentationFeedback{
+          start_time + base::TimeDelta::FromMillisecondsD(accum_interval),
+          expected_interval, 0});
+    }
+    return feedbacks;
+  }
 
-      if (ignores[i] != '-') {
-        jank_reporter->AddFrameWithNoUpdate(
-            /*sequence_number=*/sequence_number,
-            /*frame_interval=*/kDefaultFrameInterval);
-      }
-
-      if (presnts[i] != '-') {
-        // The present frame must have been previously submitted
-        EXPECT_EQ(submit_to_token.count(presnts[i]), 1u);
-
-        double presentation_offset = 0.0;  // ms
-        if (presentation_time_shifts.count(presnts[i]))
-          presentation_offset = presentation_time_shifts.at(presnts[i]);
-
-        jank_reporter->AddPresentedFrame(
-            /*presented_frame_token=*/submit_to_token[presnts[i]],
-            /*current_presentation_timestamp=*/start_time +
-                i * kDefaultFrameInterval +
-                base::TimeDelta::FromMillisecondsD(presentation_offset),
-            /*frame_interval=*/kDefaultFrameInterval);
-        submit_to_token.erase(presnts[i]);
-      }
+  // Notify |jank_reporter| of all presentations in |feedbacks|.
+  void AddPresentedFramesToJankReporter(
+      JankMetrics* jank_reporter,
+      const std::vector<gfx::PresentationFeedback>& feedbacks) {
+    for (auto feedback : feedbacks) {
+      jank_reporter->AddPresentedFrame(feedback.timestamp, feedback.interval);
     }
   }
 };
 
-TEST_F(JankMetricsTest, CompositorAnimationOneJankWithMildFluctuation) {
+TEST_F(JankMetricsTest, CompositorAnimationMildFluctuationNoJank) {
   base::HistogramTester histogram_tester;
   FrameSequenceTrackerType tracker_type =
       FrameSequenceTrackerType::kCompositorAnimation;
@@ -137,18 +69,14 @@
       FrameSequenceMetrics::ThreadType::kCompositor;
   JankMetrics jank_reporter{tracker_type, thread_type};
 
-  // One Jank; there are no no-update frames. The fluctuation in presentation of
-  // 'd' is not big enough to cause another jank.
-  SimulateFrameSequence(&jank_reporter,
-                        {
-                            /*submit   */ "ab-C-d",
-                            /*noupdate */ "------",
-                            /*present  */ "ab-C-d",
-                        },
-                        {{'d', +8.0 /*ms*/}});
+  // No jank. Small upticks such as 15->17 or 14->18 do not qualify as janks.
+  auto feedbacks =
+      CreateFeedbackSequence({16.67, 16.67, 15, 17, 14, 18, 15, 16.67}, 16.67);
+
+  AddPresentedFramesToJankReporter(&jank_reporter, feedbacks);
   jank_reporter.ReportJankMetrics(100u);
 
-  // One sample of 1 janks reported for "Compositor".
+  // One sample of 0 janks reported for "Compositor".
   const char* metric =
       "Graphics.Smoothness.Jank.Compositor.CompositorAnimation";
   const char* invalid_metric =
@@ -156,13 +84,13 @@
 
   histogram_tester.ExpectTotalCount(metric, 1u);
   EXPECT_THAT(histogram_tester.GetAllSamples(metric),
-              testing::ElementsAre(base::Bucket(1, 1)));
+              testing::ElementsAre(base::Bucket(0, 1)));
 
   // No reporting for "Main".
   histogram_tester.ExpectTotalCount(invalid_metric, 0u);
 }
 
-TEST_F(JankMetricsTest, MainThreadAnimationOneJankWithNoUpdate) {
+TEST_F(JankMetricsTest, MainThreadAnimationOneJank) {
   base::HistogramTester histogram_tester;
   FrameSequenceTrackerType tracker_type =
       FrameSequenceTrackerType::kMainThreadAnimation;
@@ -170,13 +98,13 @@
       FrameSequenceMetrics::ThreadType::kMain;
   JankMetrics jank_reporter{tracker_type, thread_type};
 
-  // There are only 1 jank because of a no-update frame.
+  // One Main thread jank from 15 to 24, since 24 - 15 = 9, which is greater
+  // then 0.5 * frame_interval = 8.33. The jank occurrence is visually marked
+  // with a "+" sign.
+  auto feedbacks =
+      CreateFeedbackSequence({48, 15, +24, 14, 18, 15, 16.67}, 16.67);
 
-  SimulateFrameSequence(&jank_reporter, {
-                                            /*submit   */ "ab-c--D",
-                                            /*noupdate */ "--*----",
-                                            /*present  */ "ab-c--D",
-                                        });
+  AddPresentedFramesToJankReporter(&jank_reporter, feedbacks);
   jank_reporter.ReportJankMetrics(100u);
 
   // One jank is reported for "Main".
@@ -200,13 +128,10 @@
   JankMetrics jank_reporter{tracker_type, thread_type};
 
   // 7 janks.
-  SimulateFrameSequence(&jank_reporter,
-                        {
-                            /*submit   */ "ab-C--DeFGh-IJk---L---------",
-                            /*noupdate */ "----------------------------",
-                            /*present  */ "---ab-C--De-F--Gh-I---Jk---L",
-                        });
+  auto feedbacks = CreateFeedbackSequence(
+      {15, +33, +50, 33, 16, +33, +50, +100, +120, +180}, 16.67);
 
+  AddPresentedFramesToJankReporter(&jank_reporter, feedbacks);
   jank_reporter.ReportJankMetrics(300u);
 
   // Report in the 7/300 ~= 2% bucket for "Compositor"
@@ -221,20 +146,17 @@
   histogram_tester.ExpectTotalCount(invalid_metric, 0u);
 }
 
-TEST_F(JankMetricsTest, WheelScrollMainThreadNoJanksWithNoUpdates) {
+TEST_F(JankMetricsTest, WheelScrollMainThreadTwoJanks) {
   base::HistogramTester histogram_tester;
   FrameSequenceTrackerType tracker_type =
       FrameSequenceTrackerType::kWheelScroll;
   FrameSequenceMetrics::ThreadType thread_type =
       FrameSequenceMetrics::ThreadType::kMain;
+
   JankMetrics jank_reporter{tracker_type, thread_type};
 
-  SimulateFrameSequence(&jank_reporter,
-                        {
-                            /*submit   */ "ab-c--d------e---------f-",
-                            /*noupdate */ "--*-**-******-*********--",
-                            /*present  */ "---ab-c-d-----e---------f",
-                        });
+  auto feedbacks = CreateFeedbackSequence({33, 16, +33, +48, 33}, 16.67);
+  AddPresentedFramesToJankReporter(&jank_reporter, feedbacks);
   jank_reporter.ReportJankMetrics(100u);
 
   // Expect 2 janks for "Main" and no jank for "Compositor"
@@ -244,43 +166,12 @@
 
   histogram_tester.ExpectTotalCount(metric, 1u);
   EXPECT_THAT(histogram_tester.GetAllSamples(metric),
-              testing::ElementsAre(base::Bucket(0, 1)));
-
-  histogram_tester.ExpectTotalCount(invalid_metric, 0u);
-}
-
-TEST_F(JankMetricsTest, WheelScrollCompositorTwoJanksWithLargeFluctuation) {
-  base::HistogramTester histogram_tester;
-  FrameSequenceTrackerType tracker_type =
-      FrameSequenceTrackerType::kWheelScroll;
-  FrameSequenceMetrics::ThreadType thread_type =
-      FrameSequenceMetrics::ThreadType::kCompositor;
-  JankMetrics jank_reporter{tracker_type, thread_type};
-
-  // Two janks; there are no no-update frames. The fluctuations in presentation
-  // of 'C' and 'D' are just big enough to cause another jank.
-  SimulateFrameSequence(&jank_reporter,
-                        {
-                            /*submit   */ "ab-C-D",
-                            /*noupdate */ "------",
-                            /*present  */ "ab-C-D",
-                        },
-                        {{'C', -2.0 /*ms*/}, {'D', +7.0 /*ms*/}});
-  jank_reporter.ReportJankMetrics(100u);
-
-  // One sample of 2 janks reported for "Compositor".
-  const char* metric = "Graphics.Smoothness.Jank.Compositor.WheelScroll";
-  const char* invalid_metric = "Graphics.Smoothness.Jank.Main.WheelScroll";
-
-  histogram_tester.ExpectTotalCount(metric, 1u);
-  EXPECT_THAT(histogram_tester.GetAllSamples(metric),
               testing::ElementsAre(base::Bucket(2, 1)));
 
-  // No reporting for "Main".
   histogram_tester.ExpectTotalCount(invalid_metric, 0u);
 }
 
-TEST_F(JankMetricsTest, TouchScrollCompositorThreadManyJanksLongLatency) {
+TEST_F(JankMetricsTest, TouchScrollCompositorThreadManyJanks) {
   base::HistogramTester histogram_tester;
   FrameSequenceTrackerType tracker_type =
       FrameSequenceTrackerType::kTouchScroll;
@@ -289,17 +180,14 @@
 
   JankMetrics jank_reporter{tracker_type, thread_type};
 
-  // There are long delays from submit to presentations.
-  SimulateFrameSequence(
-      &jank_reporter, {
-                          /*submit   */ "abB-c--D--EFgH----------------------",
-                          /*noupdate */ "---*--------------------------------",
-                          /*present  */ "----------ab-B--c---D----E-----Fg--H",
-                      });
+  auto feedbacks =
+      CreateFeedbackSequence({33, 16, +33, +48, +100, 16, +48, +100}, 16.67);
+
+  AddPresentedFramesToJankReporter(&jank_reporter, feedbacks);
   jank_reporter.ReportJankMetrics(120u);
 
-  // Expect janks in the 5/120 ~= 4% bucket for "Compositor", and no jank
-  // for "Main"
+  // Expect janks in the 5/120 ~= 4% bucket for "Compositor", and no jank for
+  // "Main"
   const char* metric = "Graphics.Smoothness.Jank.Compositor.TouchScroll";
   const char* invalid_metric = "Graphics.Smoothness.Jank.Main.TouchScroll";
 
@@ -322,13 +210,9 @@
   std::unique_ptr<JankMetrics> other_reporter =
       std::make_unique<JankMetrics>(tracker_type, thread_type);
 
-  std::array<std::string, 3> seqs = {
-      /*submit   */ "a-b-Cd-e-F--D-",
-      /*noupdate */ "-*----*-------",
-      /*present  */ "-a-b-Cd-e-F--D",
-  };
-  SimulateFrameSequence(&jank_reporter, seqs);
-  SimulateFrameSequence(other_reporter.get(), seqs);
+  auto feedbacks = CreateFeedbackSequence({33, +50, 16, +33, 33, +48}, 16.67);
+  AddPresentedFramesToJankReporter(other_reporter.get(), feedbacks);
+  AddPresentedFramesToJankReporter(&jank_reporter, feedbacks);
 
   jank_reporter.Merge(std::move(other_reporter));
   jank_reporter.ReportJankMetrics(100u);
@@ -354,11 +238,9 @@
 
   // There should be 4 janks, but the jank reporter does not track or report
   // them.
-  SimulateFrameSequence(&jank_reporter, {
-                                            /*submit   */ "ab-C--D---E----F",
-                                            /*noupdate */ "----------------",
-                                            /*present  */ "ab-C--D---E----F",
-                                        });
+  auto feedbacks = CreateFeedbackSequence({16, +33, +48, 16, +33, +48}, 16.67);
+
+  AddPresentedFramesToJankReporter(&jank_reporter, feedbacks);
   jank_reporter.ReportJankMetrics(100u);
 
   // Expect no jank reports even though the sequence contains jank
diff --git a/cc/paint/discardable_image_map_unittest.cc b/cc/paint/discardable_image_map_unittest.cc
index a334098..4675f29 100644
--- a/cc/paint/discardable_image_map_unittest.cc
+++ b/cc/paint/discardable_image_map_unittest.cc
@@ -131,8 +131,7 @@
   }
 
   scoped_refptr<DisplayItemList> display_list =
-      content_layer_client.PaintContentsToDisplayList(
-          ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+      content_layer_client.PaintContentsToDisplayList();
   display_list->GenerateDiscardableImagesMetadata();
   const DiscardableImageMap& image_map = display_list->discardable_image_map();
 
@@ -204,8 +203,7 @@
   }
 
   scoped_refptr<DisplayItemList> display_list =
-      content_layer_client.PaintContentsToDisplayList(
-          ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+      content_layer_client.PaintContentsToDisplayList();
   display_list->GenerateDiscardableImagesMetadata();
   const DiscardableImageMap& image_map = display_list->discardable_image_map();
 
@@ -306,8 +304,7 @@
   }
 
   scoped_refptr<DisplayItemList> display_list =
-      content_layer_client.PaintContentsToDisplayList(
-          ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+      content_layer_client.PaintContentsToDisplayList();
   display_list->GenerateDiscardableImagesMetadata();
   const DiscardableImageMap& image_map = display_list->discardable_image_map();
 
@@ -342,8 +339,7 @@
                                       flags);
 
   scoped_refptr<DisplayItemList> display_list =
-      content_layer_client.PaintContentsToDisplayList(
-          ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+      content_layer_client.PaintContentsToDisplayList();
   display_list->GenerateDiscardableImagesMetadata();
   const DiscardableImageMap& image_map = display_list->discardable_image_map();
 
@@ -480,8 +476,7 @@
                                       flags);
 
   scoped_refptr<DisplayItemList> display_list =
-      content_layer_client.PaintContentsToDisplayList(
-          ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+      content_layer_client.PaintContentsToDisplayList();
   display_list->GenerateDiscardableImagesMetadata();
   const DiscardableImageMap& image_map = display_list->discardable_image_map();
 
@@ -525,8 +520,7 @@
                                       gfx::Point(-10000, 500), flags);
 
   scoped_refptr<DisplayItemList> display_list =
-      content_layer_client.PaintContentsToDisplayList(
-          ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+      content_layer_client.PaintContentsToDisplayList();
   display_list->GenerateDiscardableImagesMetadata();
   const DiscardableImageMap& image_map = display_list->discardable_image_map();
 
@@ -575,8 +569,7 @@
                                       gfx::Point(-100, 500), flags);
 
   scoped_refptr<DisplayItemList> display_list =
-      content_layer_client.PaintContentsToDisplayList(
-          ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+      content_layer_client.PaintContentsToDisplayList();
   display_list->GenerateDiscardableImagesMetadata();
   const DiscardableImageMap& image_map = display_list->discardable_image_map();
 
@@ -641,8 +634,7 @@
   }
 
   scoped_refptr<DisplayItemList> display_list =
-      content_layer_client.PaintContentsToDisplayList(
-          ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+      content_layer_client.PaintContentsToDisplayList();
   display_list->GenerateDiscardableImagesMetadata();
   const DiscardableImageMap& image_map = display_list->discardable_image_map();
 
@@ -766,8 +758,7 @@
                                       gfx::Point(200, 200), flags);
 
   scoped_refptr<DisplayItemList> display_list =
-      content_layer_client.PaintContentsToDisplayList(
-          ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+      content_layer_client.PaintContentsToDisplayList();
   display_list->GenerateDiscardableImagesMetadata();
   const auto& animated_images_metadata =
       display_list->discardable_image_map().animated_images_metadata();
@@ -811,8 +802,7 @@
                                       flags);
 
   scoped_refptr<DisplayItemList> display_list =
-      content_layer_client.PaintContentsToDisplayList(
-          ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+      content_layer_client.PaintContentsToDisplayList();
   display_list->GenerateDiscardableImagesMetadata();
 
   const auto& paint_worklet_inputs =
@@ -1010,8 +1000,7 @@
   content_layer_client.add_draw_image(sync_image, gfx::Point(20, 20),
                                       PaintFlags());
   scoped_refptr<DisplayItemList> display_list =
-      content_layer_client.PaintContentsToDisplayList(
-          ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+      content_layer_client.PaintContentsToDisplayList();
   display_list->GenerateDiscardableImagesMetadata();
   auto decode_hints = display_list->TakeDecodingModeMap();
   ASSERT_EQ(decode_hints.size(), 3u);
@@ -1082,8 +1071,7 @@
   content_layer_client.add_draw_image(sync_image3, gfx::Point(50, 50),
                                       PaintFlags());
   scoped_refptr<DisplayItemList> display_list =
-      content_layer_client.PaintContentsToDisplayList(
-          ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+      content_layer_client.PaintContentsToDisplayList();
   display_list->GenerateDiscardableImagesMetadata();
 
   auto decode_hints = display_list->TakeDecodingModeMap();
@@ -1117,8 +1105,7 @@
   content_layer_client.add_draw_image(image, gfx::Point(400, 400), flags);
 
   scoped_refptr<DisplayItemList> display_list =
-      content_layer_client.PaintContentsToDisplayList(
-          ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+      content_layer_client.PaintContentsToDisplayList();
   display_list->GenerateDiscardableImagesMetadata();
   const auto& image_map = display_list->discardable_image_map();
 
@@ -1154,16 +1141,14 @@
   content_layer_client.set_bounds(visible_rect.size());
 
   scoped_refptr<DisplayItemList> display_list =
-      content_layer_client.PaintContentsToDisplayList(
-          ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+      content_layer_client.PaintContentsToDisplayList();
   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 = content_layer_client.PaintContentsToDisplayList();
   display_list->GenerateDiscardableImagesMetadata();
   const DiscardableImageMap& image_map2 = display_list->discardable_image_map();
   EXPECT_TRUE(image_map2.contains_hbd_images());
@@ -1176,8 +1161,7 @@
   content_layer_client.set_bounds(kVisibleRect.size());
 
   // Empty map should report a color usage of SRGB.
-  auto display_list = content_layer_client.PaintContentsToDisplayList(
-      ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+  auto display_list = content_layer_client.PaintContentsToDisplayList();
   display_list->GenerateDiscardableImagesMetadata();
   EXPECT_EQ(display_list->discardable_image_map().content_color_usage(),
             gfx::ContentColorUsage::kSRGB);
@@ -1187,8 +1171,7 @@
       kSize, gfx::ColorSpace::CreateSRGB().ToSkColorSpace());
   content_layer_client.add_draw_image(discardable_image_srgb, gfx::Point(0, 0),
                                       PaintFlags());
-  display_list = content_layer_client.PaintContentsToDisplayList(
-      ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+  display_list = content_layer_client.PaintContentsToDisplayList();
   display_list->GenerateDiscardableImagesMetadata();
   EXPECT_EQ(display_list->discardable_image_map().content_color_usage(),
             gfx::ContentColorUsage::kSRGB);
@@ -1198,8 +1181,7 @@
       kSize, gfx::ColorSpace::CreateDisplayP3D65().ToSkColorSpace());
   content_layer_client.add_draw_image(discardable_image_wcg, gfx::Point(0, 0),
                                       PaintFlags());
-  display_list = content_layer_client.PaintContentsToDisplayList(
-      ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+  display_list = content_layer_client.PaintContentsToDisplayList();
   display_list->GenerateDiscardableImagesMetadata();
   EXPECT_EQ(display_list->discardable_image_map().content_color_usage(),
             gfx::ContentColorUsage::kWideColorGamut);
@@ -1209,8 +1191,7 @@
       kSize, gfx::ColorSpace::CreateHDR10().ToSkColorSpace());
   content_layer_client.add_draw_image(discardable_image_hdr, gfx::Point(0, 0),
                                       PaintFlags());
-  display_list = content_layer_client.PaintContentsToDisplayList(
-      ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+  display_list = content_layer_client.PaintContentsToDisplayList();
   display_list->GenerateDiscardableImagesMetadata();
   EXPECT_EQ(display_list->discardable_image_map().content_color_usage(),
             gfx::ContentColorUsage::kHDR);
@@ -1230,8 +1211,7 @@
   content_layer_client.set_bounds(visible_rect.size());
 
   scoped_refptr<DisplayItemList> display_list =
-      content_layer_client.PaintContentsToDisplayList(
-          ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+      content_layer_client.PaintContentsToDisplayList();
   display_list->GenerateDiscardableImagesMetadata();
   const DiscardableImageMap& image_map = display_list->discardable_image_map();
 
@@ -1240,8 +1220,7 @@
 
   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 = content_layer_client.PaintContentsToDisplayList();
   display_list->GenerateDiscardableImagesMetadata();
   const DiscardableImageMap& image_map2 = display_list->discardable_image_map();
 
diff --git a/cc/test/fake_content_layer_client.cc b/cc/test/fake_content_layer_client.cc
index 90f5339..f4fb8e4f 100644
--- a/cc/test/fake_content_layer_client.cc
+++ b/cc/test/fake_content_layer_client.cc
@@ -37,8 +37,7 @@
 }
 
 scoped_refptr<DisplayItemList>
-FakeContentLayerClient::PaintContentsToDisplayList(
-    PaintingControlSetting painting_control) {
+FakeContentLayerClient::PaintContentsToDisplayList() {
   auto display_list = base::MakeRefCounted<DisplayItemList>();
 
   for (RectPaintVector::const_iterator it = draw_rects_.begin();
diff --git a/cc/test/fake_content_layer_client.h b/cc/test/fake_content_layer_client.h
index 5105306..eaf1b12 100644
--- a/cc/test/fake_content_layer_client.h
+++ b/cc/test/fake_content_layer_client.h
@@ -43,8 +43,7 @@
   ~FakeContentLayerClient() override;
 
   gfx::Rect PaintableRegion() override;
-  scoped_refptr<DisplayItemList> PaintContentsToDisplayList(
-      PaintingControlSetting painting_control) override;
+  scoped_refptr<DisplayItemList> PaintContentsToDisplayList() override;
   bool FillsBoundsCompletely() const override;
   size_t GetApproximateUnsharedMemoryUsage() const override;
 
@@ -96,10 +95,6 @@
 
   SkCanvas* last_canvas() const { return last_canvas_; }
 
-  PaintingControlSetting last_painting_control() const {
-    return last_painting_control_;
-  }
-
   void set_reported_memory_usage(size_t reported_memory_usage) {
     reported_memory_usage_ = reported_memory_usage;
   }
@@ -117,7 +112,6 @@
   RectPaintVector draw_rects_;
   ImageVector draw_images_;
   SkCanvas* last_canvas_ = nullptr;
-  PaintingControlSetting last_painting_control_ = PAINTING_BEHAVIOR_NORMAL;
   size_t reported_memory_usage_ = 0;
   gfx::Size bounds_;
   bool bounds_set_ = false;
diff --git a/cc/test/fake_recording_source.h b/cc/test/fake_recording_source.h
index 5883215..dbf5ceae 100644
--- a/cc/test/fake_recording_source.h
+++ b/cc/test/fake_recording_source.h
@@ -81,8 +81,7 @@
     Region invalidation;
     gfx::Rect new_recorded_viewport = client_.PaintableRegion();
     scoped_refptr<DisplayItemList> display_list =
-        client_.PaintContentsToDisplayList(
-            ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+        client_.PaintContentsToDisplayList();
     size_t painter_reported_memory_usage =
         client_.GetApproximateUnsharedMemoryUsage();
     UpdateAndExpandInvalidation(&invalidation, size_, new_recorded_viewport);
diff --git a/cc/test/solid_color_content_layer_client.cc b/cc/test/solid_color_content_layer_client.cc
index 7ac7c96..0741e89 100644
--- a/cc/test/solid_color_content_layer_client.cc
+++ b/cc/test/solid_color_content_layer_client.cc
@@ -19,8 +19,7 @@
 }
 
 scoped_refptr<DisplayItemList>
-SolidColorContentLayerClient::PaintContentsToDisplayList(
-    PaintingControlSetting painting_control) {
+SolidColorContentLayerClient::PaintContentsToDisplayList() {
   auto display_list = base::MakeRefCounted<DisplayItemList>();
   display_list->StartPaint();
   display_list->push<SaveOp>();
diff --git a/cc/test/solid_color_content_layer_client.h b/cc/test/solid_color_content_layer_client.h
index d8fcd70c..e70d6ffc 100644
--- a/cc/test/solid_color_content_layer_client.h
+++ b/cc/test/solid_color_content_layer_client.h
@@ -29,8 +29,7 @@
   gfx::Rect PaintableRegion() override;
 
   // ContentLayerClient implementation.
-  scoped_refptr<DisplayItemList> PaintContentsToDisplayList(
-      PaintingControlSetting painting_control) override;
+  scoped_refptr<DisplayItemList> PaintContentsToDisplayList() override;
   bool FillsBoundsCompletely() const override;
   size_t GetApproximateUnsharedMemoryUsage() const override;
 
diff --git a/cc/trees/layer_tree_host_client.h b/cc/trees/layer_tree_host_client.h
index a97a408..ab13254 100644
--- a/cc/trees/layer_tree_host_client.h
+++ b/cc/trees/layer_tree_host_client.h
@@ -66,6 +66,15 @@
 constexpr ManipulationInfo kManipulationInfoPrecisionTouchPad = 1 << 2;
 constexpr ManipulationInfo kManipulationInfoPinchZoom = 1 << 3;
 
+struct PaintBenchmarkResult {
+  double record_time_ms = 0;
+  double record_time_caching_disabled_ms = 0;
+  double record_time_subsequence_caching_disabled_ms = 0;
+  double record_time_partial_invalidation_ms = 0;
+  double raster_invalidation_and_convert_time_ms = 0;
+  double paint_artifact_compositor_update_time_ms = 0;
+};
+
 // A LayerTreeHost is bound to a LayerTreeHostClient. The main rendering
 // loop (in ProxyMain or SingleThreadProxy) calls methods on the
 // LayerTreeHost, which then handles them and also calls into the equivalent
@@ -172,6 +181,9 @@
   virtual std::unique_ptr<BeginMainFrameMetrics> GetBeginMainFrameMetrics() = 0;
   virtual void NotifyThroughputTrackerResults(CustomTrackerResults results) = 0;
 
+  virtual void RunPaintBenchmark(int repeat_count,
+                                 PaintBenchmarkResult& result) {}
+
  protected:
   virtual ~LayerTreeHostClient() = default;
 };
diff --git a/cc/trees/layer_tree_host_pixeltest_masks.cc b/cc/trees/layer_tree_host_pixeltest_masks.cc
index 95c904c..39e253292 100644
--- a/cc/trees/layer_tree_host_pixeltest_masks.cc
+++ b/cc/trees/layer_tree_host_pixeltest_masks.cc
@@ -62,8 +62,7 @@
 
   gfx::Rect PaintableRegion() override { return gfx::Rect(bounds_); }
 
-  scoped_refptr<DisplayItemList> PaintContentsToDisplayList(
-      PaintingControlSetting picture_control) override {
+  scoped_refptr<DisplayItemList> PaintContentsToDisplayList() override {
     auto display_list = base::MakeRefCounted<DisplayItemList>();
     display_list->StartPaint();
 
@@ -197,8 +196,7 @@
 
   gfx::Rect PaintableRegion() override { return gfx::Rect(bounds_); }
 
-  scoped_refptr<DisplayItemList> PaintContentsToDisplayList(
-      PaintingControlSetting picture_control) override {
+  scoped_refptr<DisplayItemList> PaintContentsToDisplayList() override {
     // Intentionally return a solid color, empty mask display list. This
     // is a situation where all content should be masked out.
     auto display_list = base::MakeRefCounted<DisplayItemList>();
@@ -332,8 +330,7 @@
   sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(50, 50);
   SkCanvas* canvas = surface->getCanvas();
   scoped_refptr<DisplayItemList> mask_display_list =
-      mask_client.PaintContentsToDisplayList(
-          ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+      mask_client.PaintContentsToDisplayList();
   mask_display_list->Raster(canvas);
 
   FakeContentLayerClient layer_client;
@@ -361,8 +358,7 @@
   SkCanvas* canvas = surface->getCanvas();
   MaskContentLayerClient client(mask_bounds);
   scoped_refptr<DisplayItemList> mask_display_list =
-      client.PaintContentsToDisplayList(
-          ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+      client.PaintContentsToDisplayList();
   mask_display_list->Raster(canvas);
 
   FakeContentLayerClient mask_client;
@@ -449,8 +445,7 @@
   bool FillsBoundsCompletely() const override { return false; }
   size_t GetApproximateUnsharedMemoryUsage() const override { return 0; }
   gfx::Rect PaintableRegion() override { return gfx::Rect(bounds_); }
-  scoped_refptr<DisplayItemList> PaintContentsToDisplayList(
-      PaintingControlSetting picture_control) override {
+  scoped_refptr<DisplayItemList> PaintContentsToDisplayList() override {
     auto display_list = base::MakeRefCounted<DisplayItemList>();
     display_list->StartPaint();
 
@@ -498,8 +493,7 @@
   bool FillsBoundsCompletely() const override { return false; }
   size_t GetApproximateUnsharedMemoryUsage() const override { return 0; }
   gfx::Rect PaintableRegion() override { return gfx::Rect(bounds_); }
-  scoped_refptr<DisplayItemList> PaintContentsToDisplayList(
-      PaintingControlSetting picture_control) override {
+  scoped_refptr<DisplayItemList> PaintContentsToDisplayList() override {
     auto display_list = base::MakeRefCounted<DisplayItemList>();
     display_list->StartPaint();
 
@@ -724,8 +718,7 @@
   }
 
   gfx::Rect PaintableRegion() override { return gfx::Rect(bounds()); }
-  scoped_refptr<DisplayItemList> PaintContentsToDisplayList(
-      PaintingControlSetting) override {
+  scoped_refptr<DisplayItemList> PaintContentsToDisplayList() override {
     return display_list_;
   }
   bool FillsBoundsCompletely() const override { return false; }
diff --git a/cc/trees/layer_tree_host_pixeltest_tiles.cc b/cc/trees/layer_tree_host_pixeltest_tiles.cc
index 0cc4f38..c6a7abf 100644
--- a/cc/trees/layer_tree_host_pixeltest_tiles.cc
+++ b/cc/trees/layer_tree_host_pixeltest_tiles.cc
@@ -59,8 +59,7 @@
       : size_(size), blue_top_(true) {}
 
   gfx::Rect PaintableRegion() override { return gfx::Rect(size_); }
-  scoped_refptr<DisplayItemList> PaintContentsToDisplayList(
-      PaintingControlSetting painting_status) override {
+  scoped_refptr<DisplayItemList> PaintContentsToDisplayList() override {
     auto display_list = base::MakeRefCounted<DisplayItemList>();
 
     display_list->StartPaint();
diff --git a/cc/trees/layer_tree_host_unittest_capture_content.cc b/cc/trees/layer_tree_host_unittest_capture_content.cc
index 785d826..c7bf7b5b 100644
--- a/cc/trees/layer_tree_host_unittest_capture_content.cc
+++ b/cc/trees/layer_tree_host_unittest_capture_content.cc
@@ -32,8 +32,7 @@
     holders_.push_back(holder);
   }
 
-  scoped_refptr<DisplayItemList> PaintContentsToDisplayList(
-      PaintingControlSetting painting_control) override {
+  scoped_refptr<DisplayItemList> PaintContentsToDisplayList() override {
     auto display_list = base::MakeRefCounted<DisplayItemList>();
     for (auto& holder : holders_) {
       display_list->StartPaint();
diff --git a/chrome/MAJOR_BRANCH_DATE b/chrome/MAJOR_BRANCH_DATE
index 895cc24..38106ca 100644
--- a/chrome/MAJOR_BRANCH_DATE
+++ b/chrome/MAJOR_BRANCH_DATE
@@ -1 +1 @@
-MAJOR_BRANCH_DATE=2020-08-21
+MAJOR_BRANCH_DATE=2020-10-02
diff --git a/chrome/VERSION b/chrome/VERSION
index 174b420..cf24f5c 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
-MAJOR=87
+MAJOR=88
 MINOR=0
-BUILD=4280
+BUILD=4281
 PATCH=0
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 9caba09..0df0263 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -1183,6 +1183,7 @@
   "java/src/org/chromium/chrome/browser/omnibox/suggestions/header/HeaderView.java",
   "java/src/org/chromium/chrome/browser/omnibox/suggestions/header/HeaderViewBinder.java",
   "java/src/org/chromium/chrome/browser/omnibox/suggestions/header/HeaderViewProperties.java",
+  "java/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesProcessor.java",
   "java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/AlignmentManager.java",
   "java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/TailSuggestionProcessor.java",
   "java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/TailSuggestionView.java",
@@ -1584,6 +1585,7 @@
   "java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonMediator.java",
   "java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonProperties.java",
   "java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonViewBinder.java",
+  "java/src/org/chromium/chrome/browser/toolbar/menu_button/PropertyModelAnimatorFactory.java",
   "java/src/org/chromium/chrome/browser/toolbar/top/ActionModeController.java",
   "java/src/org/chromium/chrome/browser/toolbar/top/IncognitoSwitchCoordinator.java",
   "java/src/org/chromium/chrome/browser/toolbar/top/IncognitoSwitchProperties.java",
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedListContentManager.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedListContentManager.java
index 743d750..6c51e57 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedListContentManager.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedListContentManager.java
@@ -119,13 +119,19 @@
             UiUtils.removeViewFromParent(mNativeView);
 
             FrameLayout enclosingLayout = new FrameLayout(parent.getContext());
-            // Set the left and right margins.
             FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
                     new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
-            layoutParams.leftMargin = context.getResources().getDimensionPixelSize(
-                    R.dimen.ntp_header_lateral_margins_v2);
-            layoutParams.rightMargin = layoutParams.leftMargin;
             enclosingLayout.setLayoutParams(layoutParams);
+
+            // Set the left and right paddings.
+            int horizontalPadding = context.getResources().getDimensionPixelSize(
+                    R.dimen.ntp_header_lateral_margins_v2);
+            enclosingLayout.setPadding(/* left */ horizontalPadding, /* top */ 0,
+                    /* right */ horizontalPadding, /* bottom */ 0);
+            // Do not clip children. This ensures that the negative margin use in the feed header
+            // does not subsequently cause the IPH bubble to be clipped.
+            enclosingLayout.setClipToPadding(false);
+            enclosingLayout.setClipChildren(false);
             enclosingLayout.addView(mNativeView);
             return enclosingLayout;
         }
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 d23a583a..8ab3c16 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
@@ -770,6 +770,11 @@
 
     @Override
     public void processThereAndBackAgainData(byte[] data) {
+        processThereAndBackAgainData(data, null);
+    }
+
+    @Override
+    public void processThereAndBackAgainData(byte[] data, @Nullable View actionSourceView) {
         assert ThreadUtils.runningOnUiThread();
         FeedStreamSurfaceJni.get().processThereAndBackAgain(
                 mNativeFeedStreamSurface, FeedStreamSurface.this, data);
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml
index d0b63b15..1e60d80 100644
--- a/chrome/android/java/res/values/dimens.xml
+++ b/chrome/android/java/res/values/dimens.xml
@@ -225,6 +225,7 @@
     <dimen name="toolbar_button_width">48dp</dimen>
     <dimen name="toolbar_identity_disc_size">24dp</dimen>
     <dimen name="toolbar_identity_disc_size_duet">32dp</dimen>
+    <dimen name="toolbar_url_focus_translation_x">10dp</dimen>
 
     <!-- Bottom Toolbar -->
     <dimen name="split_toolbar_button_height">56dp</dimen>
@@ -317,6 +318,7 @@
     <dimen name="tile_view_bg_corner_radius">2dp</dimen>
     <dimen name="tile_view_width">80dp</dimen>
     <dimen name="tile_view_width_condensed">64dp</dimen>
+    <dimen name="tile_view_min_height">86dp</dimen>
     <dimen name="tile_view_icon_size">48dp</dimen>
     <dimen name="tile_view_icon_size_modern">24dp</dimen>
     <!-- TODO(crbug.com/900912): Fix and remove lint ignore -->
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
index bed1fbc..f838fa5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
@@ -49,6 +49,7 @@
 import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.WindowDelegate;
 import org.chromium.chrome.browser.app.ChromeActivity;
+import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
 import org.chromium.chrome.browser.native_page.NativePageFactory;
 import org.chromium.chrome.browser.ntp.NewTabPage;
 import org.chromium.chrome.browser.offlinepages.OfflinePageUtils;
@@ -829,6 +830,9 @@
         @Override
         public void setUnfocusedWidth(int unfocusedWidth) {}
 
+        @Override
+        public void setOverviewModeBehavior(OverviewModeBehavior overviewModeBehavior) {}
+
         // Implements FakeBoxDelegate.
         @Override
         public boolean isUrlBarFocused() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBar.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBar.java
index 83ce837d..2550d83 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBar.java
@@ -15,6 +15,7 @@
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.WindowDelegate;
+import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
 import org.chromium.chrome.browser.ntp.FakeboxDelegate;
 import org.chromium.chrome.browser.ntp.NewTabPage;
 import org.chromium.chrome.browser.omnibox.UrlBar.UrlBarDelegate;
@@ -129,6 +130,11 @@
     void setToolbarDataProvider(ToolbarDataProvider model);
 
     /**
+     * Sets the {@link OverviewModeBehavior}.
+     */
+    void setOverviewModeBehavior(OverviewModeBehavior overviewModeBehavior);
+
+    /**
      * Gets the {@link ToolbarDataProvider} to be used for accessing {@link Toolbar} state.
      */
     ToolbarDataProvider getToolbarDataProvider();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
index 05676d0b..e44f1cc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
@@ -43,6 +43,7 @@
 import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.AppHooks;
 import org.chromium.chrome.browser.WindowDelegate;
+import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.gsa.GSAState;
 import org.chromium.chrome.browser.locale.LocaleManager;
@@ -356,6 +357,11 @@
     }
 
     @Override
+    public void setOverviewModeBehavior(OverviewModeBehavior overviewModeBehavior) {
+        mAutocompleteCoordinator.setOverviewModeBehavior(overviewModeBehavior);
+    }
+
+    @Override
     public void onDeferredStartup() {
         mAutocompleteCoordinator.prefetchZeroSuggestResults();
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarPhone.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarPhone.java
index dbba147..2ce8fd7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarPhone.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarPhone.java
@@ -15,7 +15,6 @@
 
 import org.chromium.base.TraceEvent;
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
 import org.chromium.chrome.browser.ntp.NewTabPage;
 import org.chromium.ui.interpolators.BakedBezierInterpolator;
 
@@ -329,10 +328,6 @@
         }
     }
 
-    public void setOverviewModeBehavior(OverviewModeBehavior overviewModeBehavior) {
-        mAutocompleteCoordinator.setOverviewModeBehavior(overviewModeBehavior);
-    }
-
     /** Update the status visibility according to the current state held in LocationBar. */
     private void updateStatusVisibility() {
         boolean incognito = getToolbarDataProvider().isIncognito();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinatorImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinatorImpl.java
index a7ca8e2..b0d88cf 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinatorImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinatorImpl.java
@@ -28,11 +28,13 @@
 import org.chromium.chrome.browser.omnibox.suggestions.base.BaseSuggestionView;
 import org.chromium.chrome.browser.omnibox.suggestions.base.BaseSuggestionViewBinder;
 import org.chromium.chrome.browser.omnibox.suggestions.basic.SuggestionViewViewBinder;
+import org.chromium.chrome.browser.omnibox.suggestions.carousel.BaseCarouselSuggestionViewBinder;
 import org.chromium.chrome.browser.omnibox.suggestions.editurl.EditUrlSuggestionView;
 import org.chromium.chrome.browser.omnibox.suggestions.editurl.EditUrlSuggestionViewBinder;
 import org.chromium.chrome.browser.omnibox.suggestions.entity.EntitySuggestionViewBinder;
 import org.chromium.chrome.browser.omnibox.suggestions.header.HeaderView;
 import org.chromium.chrome.browser.omnibox.suggestions.header.HeaderViewBinder;
+import org.chromium.chrome.browser.omnibox.suggestions.mostvisited.MostVisitedTilesProcessor;
 import org.chromium.chrome.browser.omnibox.suggestions.tail.TailSuggestionView;
 import org.chromium.chrome.browser.omnibox.suggestions.tail.TailSuggestionViewBinder;
 import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler;
@@ -173,6 +175,11 @@
                         mQueryTileCoordinator::bind);
 
                 adapter.registerType(
+                        OmniboxSuggestionUiType.TILE_NAVSUGGEST,
+                        MostVisitedTilesProcessor::createView,
+                        BaseCarouselSuggestionViewBinder::bind);
+
+                adapter.registerType(
                         OmniboxSuggestionUiType.HEADER,
                         parent -> new HeaderView(parent.getContext()),
                         HeaderViewBinder::bind);
@@ -287,12 +294,11 @@
     public boolean handleKeyEvent(int keyCode, KeyEvent event) {
         boolean isShowingList = mDropdown != null && mDropdown.getViewGroup().isShown();
 
-        boolean isUpOrDown = KeyNavigationUtil.isGoUpOrDown(event);
-        if (isShowingList && mMediator.getSuggestionCount() > 0 && isUpOrDown) {
+        boolean isAnyDirection = KeyNavigationUtil.isGoAnyDirection(event);
+        if (isShowingList && mMediator.getSuggestionCount() > 0 && isAnyDirection) {
             mMediator.allowPendingItemSelection();
         }
-        boolean isValidListKey = isUpOrDown || KeyNavigationUtil.isGoRight(event)
-                || KeyNavigationUtil.isGoLeft(event) || KeyNavigationUtil.isEnter(event);
+        boolean isValidListKey = isAnyDirection || KeyNavigationUtil.isEnter(event);
         if (isShowingList && isValidListKey && mDropdown.getViewGroup().onKeyDown(keyCode, event)) {
             return true;
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
index a719c8b..1188743 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
@@ -479,13 +479,6 @@
             PropertyModel model, OmniboxSuggestion suggestion, int position) {
         return new SuggestionViewDelegate() {
             @Override
-            public void onSetUrlToSuggestion() {
-                if (mIgnoreOmniboxItemSelection) return;
-                mIgnoreOmniboxItemSelection = true;
-                AutocompleteMediator.this.onSetUrlToSuggestion(suggestion);
-            }
-
-            @Override
             public void onSelection() {
                 processor.recordItemUsed(model);
                 AutocompleteMediator.this.onSelection(suggestion, position);
@@ -662,10 +655,13 @@
 
     /**
      * Triggered when the user navigates to one of the suggestions without clicking on it.
-     * @param suggestion The suggestion that was selected.
+     * @param text The text to be displayed in the Omnibox.
      */
-    void onSetUrlToSuggestion(OmniboxSuggestion suggestion) {
-        mDelegate.setOmniboxEditingText(suggestion.getFillIntoEdit());
+    @Override
+    public void setOmniboxEditingText(String text) {
+        if (mIgnoreOmniboxItemSelection) return;
+        mIgnoreOmniboxItemSelection = true;
+        mDelegate.setOmniboxEditingText(text);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListBuilder.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListBuilder.java
index 5623cc5..b5b0cf9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListBuilder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListBuilder.java
@@ -27,6 +27,7 @@
 import org.chromium.chrome.browser.omnibox.suggestions.editurl.EditUrlSuggestionProcessor;
 import org.chromium.chrome.browser.omnibox.suggestions.entity.EntitySuggestionProcessor;
 import org.chromium.chrome.browser.omnibox.suggestions.header.HeaderProcessor;
+import org.chromium.chrome.browser.omnibox.suggestions.mostvisited.MostVisitedTilesProcessor;
 import org.chromium.chrome.browser.omnibox.suggestions.tail.TailSuggestionProcessor;
 import org.chromium.chrome.browser.omnibox.suggestions.tiles.TileSuggestionProcessor;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -101,6 +102,8 @@
         registerSuggestionProcessor(
                 new TileSuggestionProcessor(context, queryTileSuggestionCallback));
         registerSuggestionProcessor(
+                new MostVisitedTilesProcessor(context, host, iconBridgeSupplier));
+        registerSuggestionProcessor(
                 new BasicSuggestionProcessor(context, host, textProvider, iconBridgeSupplier));
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionUiType.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionUiType.java
index 8ba5857..77db27b0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionUiType.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionUiType.java
@@ -13,7 +13,7 @@
 @IntDef({OmniboxSuggestionUiType.DEFAULT, OmniboxSuggestionUiType.EDIT_URL_SUGGESTION,
         OmniboxSuggestionUiType.ANSWER_SUGGESTION, OmniboxSuggestionUiType.ENTITY_SUGGESTION,
         OmniboxSuggestionUiType.TAIL_SUGGESTION, OmniboxSuggestionUiType.CLIPBOARD_SUGGESTION,
-        OmniboxSuggestionUiType.TILE_SUGGESTION})
+        OmniboxSuggestionUiType.TILE_SUGGESTION, OmniboxSuggestionUiType.TILE_NAVSUGGEST})
 @Retention(RetentionPolicy.SOURCE)
 public @interface OmniboxSuggestionUiType {
     int DEFAULT = 0;
@@ -24,4 +24,5 @@
     int CLIPBOARD_SUGGESTION = 5;
     int TILE_SUGGESTION = 6;
     int HEADER = 7;
+    int TILE_NAVSUGGEST = 8;
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionHost.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionHost.java
index 3bc4d8d..28c804c9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionHost.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionHost.java
@@ -40,4 +40,11 @@
      * @param isCollapsed True if group should appear collapsed, otherwise false.
      */
     void setGroupCollapsedState(int groupId, boolean isCollapsed);
+
+    /**
+     * Update the content of the Omnibox without triggering the Navigation.
+     *
+     * @param text The text to be displayed in the Omnibox.
+     */
+    void setOmniboxEditingText(String text);
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionViewDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionViewDelegate.java
index d0247ed0..d0f3dca93 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionViewDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionViewDelegate.java
@@ -13,7 +13,4 @@
 
     /** Triggered when the user long presses the omnibox suggestion. */
     void onLongPress();
-
-    /** Triggered when the user navigates to one of the suggestions without clicking on it. */
-    void onSetUrlToSuggestion();
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionView.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionView.java
index 1544751..ab50838 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionView.java
@@ -11,6 +11,7 @@
 import android.widget.ImageView;
 
 import androidx.annotation.LayoutRes;
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.appcompat.widget.AppCompatImageView;
 
@@ -32,6 +33,7 @@
     private final List<ImageView> mActionButtons;
     private final DecoratedSuggestionView<T> mDecoratedView;
     private SuggestionViewDelegate mDelegate;
+    private @Nullable Runnable mOnFocusViaSelectionListener;
 
     /**
      * Constructs a new suggestion view.
@@ -136,8 +138,8 @@
     @Override
     public void setSelected(boolean selected) {
         mDecoratedView.setSelected(selected);
-        if (selected) {
-            mDelegate.onSetUrlToSuggestion();
+        if (selected && mOnFocusViaSelectionListener != null) {
+            mOnFocusViaSelectionListener.run();
         }
     }
 
@@ -170,6 +172,15 @@
         mDelegate = delegate;
     }
 
+    /**
+     * Specify the listener receiving a call when the user highlights this Suggestion.
+     *
+     * @param listener The listener to be notified about selection.
+     */
+    void setOnFocusViaSelectionListener(@Nullable Runnable listener) {
+        mOnFocusViaSelectionListener = listener;
+    }
+
     /** @return Widget holding suggestion decoration icon. */
     RoundedCornerImageView getSuggestionImageView() {
         return mDecoratedView.getImageView();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewBinder.java
index 7143f15..deeaca4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewBinder.java
@@ -63,6 +63,9 @@
             updateColorScheme(model, view);
         } else if (BaseSuggestionViewProperties.ACTIONS == propertyKey) {
             bindActionButtons(model, view, model.get(BaseSuggestionViewProperties.ACTIONS));
+        } else if (BaseSuggestionViewProperties.ON_FOCUS_VIA_SELECTION == propertyKey) {
+            view.setOnFocusViaSelectionListener(
+                    model.get(BaseSuggestionViewProperties.ON_FOCUS_VIA_SELECTION));
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewProcessor.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewProcessor.java
index 676ef82..510db42 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewProcessor.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewProcessor.java
@@ -152,6 +152,8 @@
                 mSuggestionHost.createSuggestionViewDelegate(this, model, suggestion, position);
 
         model.set(BaseSuggestionViewProperties.SUGGESTION_DELEGATE, delegate);
+        model.set(BaseSuggestionViewProperties.ON_FOCUS_VIA_SELECTION,
+                () -> { mSuggestionHost.setOmniboxEditingText(suggestion.getFillIntoEdit()); });
         model.set(BaseSuggestionViewProperties.DENSITY, mDensity);
         setCustomActions(model, null);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewProperties.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewProperties.java
index c0d3f7e..40b86b1b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewProperties.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewProperties.java
@@ -74,6 +74,10 @@
     public static final WritableObjectPropertyKey<List<Action>> ACTIONS =
             new WritableObjectPropertyKey();
 
+    /** Callback invoked when the Suggestion view is highlighted. */
+    public static final WritableObjectPropertyKey<Runnable> ON_FOCUS_VIA_SELECTION =
+            new WritableObjectPropertyKey<>();
+
     /** Delegate receiving user events. */
     public static final WritableObjectPropertyKey<SuggestionViewDelegate> SUGGESTION_DELEGATE =
             new WritableObjectPropertyKey<>();
@@ -82,7 +86,7 @@
     public static final WritableIntPropertyKey DENSITY = new WritableIntPropertyKey();
 
     public static final PropertyKey[] ALL_UNIQUE_KEYS =
-            new PropertyKey[] {ACTIONS, ICON, DENSITY, SUGGESTION_DELEGATE};
+            new PropertyKey[] {ACTIONS, ICON, DENSITY, SUGGESTION_DELEGATE, ON_FOCUS_VIA_SELECTION};
 
     public static final PropertyKey[] ALL_KEYS =
             PropertyModel.concatKeys(ALL_UNIQUE_KEYS, SuggestionCommonProperties.ALL_KEYS);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesProcessor.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesProcessor.java
new file mode 100644
index 0000000..47ba247a
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesProcessor.java
@@ -0,0 +1,149 @@
+// 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.omnibox.suggestions.mostvisited;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+
+import org.chromium.base.supplier.Supplier;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.omnibox.OmniboxSuggestionType;
+import org.chromium.chrome.browser.omnibox.styles.OmniboxResourceProvider;
+import org.chromium.chrome.browser.omnibox.styles.OmniboxTheme;
+import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestion;
+import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionUiType;
+import org.chromium.chrome.browser.omnibox.suggestions.SuggestionHost;
+import org.chromium.chrome.browser.omnibox.suggestions.carousel.BaseCarouselSuggestionProcessor;
+import org.chromium.chrome.browser.omnibox.suggestions.carousel.BaseCarouselSuggestionView;
+import org.chromium.chrome.browser.omnibox.suggestions.carousel.BaseCarouselSuggestionViewProperties;
+import org.chromium.chrome.browser.suggestions.tile.TileView;
+import org.chromium.chrome.browser.suggestions.tile.TileViewBinder;
+import org.chromium.chrome.browser.suggestions.tile.TileViewProperties;
+import org.chromium.chrome.browser.ui.favicon.LargeIconBridge;
+import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
+import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.SimpleRecyclerViewAdapter;
+import org.chromium.url.GURL;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * SuggestionProcessor for Most Visited URL tiles.
+ * TODO(crbug.com/1106109): Write integration tests.
+ */
+public class MostVisitedTilesProcessor extends BaseCarouselSuggestionProcessor {
+    private final @NonNull Context mContext;
+    private final @NonNull SuggestionHost mSuggestionHost;
+    private final @NonNull Supplier<LargeIconBridge> mIconBridgeSupplier;
+    private final int mMinCarouselItemViewHeight;
+    private static final int DEFAULT_TILE_TYPE = 0;
+
+    /**
+     * Constructor.
+     *
+     * @param context An Android context.
+     * @param host SuggestionHost receiving notifications about user actions.
+     * @param iconBridgeSupplier Supplier of the LargeIconBridge used to fetch site favicons.
+     */
+    public MostVisitedTilesProcessor(@NonNull Context context, @NonNull SuggestionHost host,
+            @NonNull Supplier<LargeIconBridge> iconBridgeSupplier) {
+        super(context);
+        mContext = context;
+        mSuggestionHost = host;
+        mIconBridgeSupplier = iconBridgeSupplier;
+        mMinCarouselItemViewHeight =
+                context.getResources().getDimensionPixelSize(R.dimen.tile_view_min_height);
+    }
+
+    @Override
+    public boolean doesProcessSuggestion(OmniboxSuggestion suggestion, int position) {
+        return suggestion.getType() == OmniboxSuggestionType.TILE_NAVSUGGEST;
+    }
+
+    @Override
+    public int getViewTypeId() {
+        return OmniboxSuggestionUiType.TILE_NAVSUGGEST;
+    }
+
+    @Override
+    public PropertyModel createModel() {
+        return new PropertyModel(BaseCarouselSuggestionViewProperties.ALL_KEYS);
+    }
+
+    @Override
+    public int getMinimumCarouselItemViewHeight() {
+        return mMinCarouselItemViewHeight;
+    }
+
+    @Override
+    public void populateModel(OmniboxSuggestion suggestion, PropertyModel model, int position) {
+        final List<OmniboxSuggestion.NavsuggestTile> tiles = suggestion.getNavsuggestTiles();
+        final int tilesCount = tiles.size();
+        final List<ListItem> tileList = new ArrayList<>(tilesCount);
+        final LargeIconBridge iconBridge = mIconBridgeSupplier.get();
+
+        for (int index = 0; index < tilesCount; index++) {
+            final PropertyModel tileModel = new PropertyModel(TileViewProperties.ALL_KEYS);
+            final GURL url = tiles.get(index).url;
+            tileModel.set(TileViewProperties.TITLE, tiles.get(index).title);
+            tileModel.set(TileViewProperties.TITLE_LINES, 1);
+            tileModel.set(TileViewProperties.ON_FOCUS_VIA_SELECTION,
+                    () -> { mSuggestionHost.setOmniboxEditingText(url.getSpec()); });
+
+            if (iconBridge != null) {
+                iconBridge.getLargeIconForUrl(tiles.get(index).url, /* size */ 32,
+                        (Bitmap icon, int fallbackColor, boolean isFallbackColorDefault,
+                                int iconType) -> {
+                            if (icon == null) return;
+                            tileModel.set(TileViewProperties.ICON, new BitmapDrawable(icon));
+                        });
+            }
+
+            tileList.add(new ListItem(DEFAULT_TILE_TYPE, tileModel));
+        }
+
+        model.set(BaseCarouselSuggestionViewProperties.TILES, tileList);
+        model.set(BaseCarouselSuggestionViewProperties.TITLE,
+                mContext.getResources().getString(R.string.most_visited_tiles_header));
+    }
+
+    /**
+     * Create Carousel Suggestion View presenting the Most Visited URL tiles.
+     *
+     * @param parent ViewGroup that will host the Carousel view.
+     * @return BaseCarouselSuggestionView for the Most Visited URL suggestions.
+     */
+    public static BaseCarouselSuggestionView createView(ViewGroup parent) {
+        SimpleRecyclerViewAdapter adapter = new SimpleRecyclerViewAdapter(new ModelList());
+        adapter.registerType(
+                DEFAULT_TILE_TYPE, MostVisitedTilesProcessor::buildTile, TileViewBinder::bind);
+        return new BaseCarouselSuggestionView(parent.getContext(), adapter);
+    }
+
+    /**
+     * Create a Tile element for the Most Visited URL suggestions.
+     *
+     * @param parent ViewGroup that will host the Tile.
+     * @return A TileView element for the individual URL suggestion.
+     */
+    private static TileView buildTile(ViewGroup parent) {
+        TileView tile = (TileView) LayoutInflater.from(parent.getContext())
+                                .inflate(R.layout.suggestions_tile_view, parent, false);
+        tile.setClickable(true);
+
+        Drawable background = OmniboxResourceProvider.resolveAttributeToDrawable(
+                parent.getContext(), OmniboxTheme.LIGHT_THEME, R.attr.selectableItemBackground);
+        tile.setBackgroundDrawable(background);
+        return tile;
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonCoordinator.java
index de113bf..1e30186e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonCoordinator.java
@@ -4,7 +4,12 @@
 
 package org.chromium.chrome.browser.toolbar.menu_button;
 
+import static android.view.View.LAYOUT_DIRECTION_RTL;
+
+import android.animation.Animator;
 import android.app.Activity;
+import android.graphics.Canvas;
+import android.view.View;
 import android.view.View.OnKeyListener;
 
 import androidx.annotation.IdRes;
@@ -20,6 +25,7 @@
 import org.chromium.chrome.browser.ui.appmenu.AppMenuButtonHelper;
 import org.chromium.chrome.browser.ui.appmenu.AppMenuCoordinator;
 import org.chromium.ui.UiUtils;
+import org.chromium.ui.base.ViewUtils;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
 
@@ -201,9 +207,34 @@
      * Set the visibility of the MenuButton controlled by this coordinator.
      * @param visible Visibility state, true for visible and false for hidden.
      */
-
     public void setVisibility(boolean visible) {
         if (mMediator == null) return;
         mMediator.setVisibility(visible);
     }
+
+    /**
+     * Draws the current visual state of this component for the purposes of rendering the tab
+     * switcher animation, setting the alpha to fade the view by the appropriate amount.
+     * @param root Root view for the menu button; used to position the canvas that's drawn on.
+     * @param canvas Canvas to draw to.
+     * @param alpha Integer (0-255) alpha level to draw at.
+     */
+    public void drawTabSwitcherAnimationOverlay(View root, Canvas canvas, int alpha) {
+        canvas.save();
+        ViewUtils.translateCanvasToView(root, mMenuButton, canvas);
+        mMenuButton.drawTabSwitcherAnimationOverlay(canvas, alpha);
+        canvas.restore();
+    }
+
+    /**
+     * Creates an animator for the MenuButton during the process offocusing or unfocusing the
+     * UrlBar. The animation translate and fades the button into/out of view.
+     * @return The Animator object for the MenuButton.
+     * @param isFocusingUrl Whether the animation is for focusing the URL, meaning the button is
+     *         fading out of view, or un-focusing, meaning it's fading into view.
+     */
+    public Animator getUrlFocusingAnimator(boolean isFocusingUrl) {
+        return mMediator.getUrlFocusingAnimator(isFocusingUrl,
+                mMenuButton != null && mMenuButton.getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonMediator.java
index 40f8ceb..d2928ae 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonMediator.java
@@ -4,17 +4,21 @@
 
 package org.chromium.chrome.browser.toolbar.menu_button;
 
+import android.animation.Animator;
+import android.animation.AnimatorSet;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 
 import androidx.annotation.Nullable;
 
 import org.chromium.base.Callback;
+import org.chromium.base.MathUtils;
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.base.supplier.Supplier;
+import org.chromium.chrome.R;
 import org.chromium.chrome.browser.browser_controls.BrowserStateBrowserControlsVisibilityDelegate;
 import org.chromium.chrome.browser.omaha.UpdateMenuItemHelper;
 import org.chromium.chrome.browser.omaha.UpdateMenuItemHelper.MenuButtonState;
@@ -36,7 +40,6 @@
  * changes to the property model that backs the MenuButton view.
  */
 class MenuButtonMediator implements AppMenuObserver {
-    private OneshotSupplier<AppMenuCoordinator> mAppMenuCoordinatorSupplier;
     private Callback<AppMenuCoordinator> mAppMenuCoordinatorSupplierObserver;
     private @Nullable AppMenuPropertiesDelegate mAppMenuPropertiesDelegate;
     private AppMenuButtonHelper mAppMenuButtonHelper;
@@ -55,6 +58,9 @@
     private Supplier<Boolean> mIsInOverviewModeSupplier;
     private boolean mSuppressAppMenuUpdateBadge;
     private Resources mResources;
+    private OneshotSupplier<AppMenuCoordinator> mAppMenuCoordinatorSupplier;
+
+    private final int mUrlFocusTranslationX;
 
     /**
      *  @param appMenuCoordinatorSupplier Supplier for the AppMenuCoordinator, which owns all other
@@ -94,6 +100,9 @@
         mAppMenuCoordinatorSupplier.onAvailable(mAppMenuCoordinatorSupplierObserver);
         mResources = resources;
         mAppMenuButtonHelperSupplier = new ObservableSupplierImpl<>();
+
+        mUrlFocusTranslationX =
+                mResources.getDimensionPixelSize(R.dimen.toolbar_url_focus_translation_x);
     }
 
     @Override
@@ -269,4 +278,25 @@
     private boolean isUpdateAvailable() {
         return UpdateMenuItemHelper.getInstance().getUiState().buttonState != null;
     }
+
+    public Animator getUrlFocusingAnimator(boolean isFocusingUrl, boolean isRtl) {
+        float translationX;
+        float alpha;
+        if (isFocusingUrl) {
+            float density = mResources.getDisplayMetrics().density;
+            translationX = MathUtils.flipSignIf(mUrlFocusTranslationX, isRtl) * density;
+            alpha = 0.0f;
+        } else {
+            translationX = 0.0f;
+            alpha = 1.0f;
+        }
+
+        AnimatorSet animatorSet = new AnimatorSet();
+        Animator translationAnimator = PropertyModelAnimatorFactory.ofFloat(
+                mPropertyModel, MenuButtonProperties.TRANSLATION_X, translationX);
+        Animator alphaAnimator = PropertyModelAnimatorFactory.ofFloat(
+                mPropertyModel, MenuButtonProperties.ALPHA, alpha);
+        animatorSet.playTogether(translationAnimator, alphaAnimator);
+        return animatorSet;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonProperties.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonProperties.java
index b03b11d..8d37e9f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonProperties.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonProperties.java
@@ -11,6 +11,7 @@
 import org.chromium.chrome.browser.ui.appmenu.AppMenuButtonHelper;
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel.WritableBooleanPropertyKey;
+import org.chromium.ui.modelutil.PropertyModel.WritableFloatPropertyKey;
 import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
 
 class MenuButtonProperties {
@@ -35,6 +36,7 @@
         }
     }
 
+    public static final WritableFloatPropertyKey ALPHA = new WritableFloatPropertyKey();
     public static final WritableObjectPropertyKey<AppMenuButtonHelper> APP_MENU_BUTTON_HELPER =
             new WritableObjectPropertyKey<>();
     public static final WritableObjectPropertyKey<String> CONTENT_DESCRIPTION =
@@ -47,8 +49,9 @@
             new WritableObjectPropertyKey(true);
     public static final WritableObjectPropertyKey<ThemeProperty> THEME =
             new WritableObjectPropertyKey<>(true);
+    public static final WritableFloatPropertyKey TRANSLATION_X = new WritableFloatPropertyKey();
 
     public static final PropertyKey[] ALL_KEYS =
-            new PropertyKey[] {APP_MENU_BUTTON_HELPER, CONTENT_DESCRIPTION, IS_CLICKABLE,
-                    IS_HIGHLIGHTING, IS_VISIBLE, SHOW_UPDATE_BADGE, THEME};
+            new PropertyKey[] {ALPHA, APP_MENU_BUTTON_HELPER, CONTENT_DESCRIPTION, IS_CLICKABLE,
+                    IS_HIGHLIGHTING, IS_VISIBLE, SHOW_UPDATE_BADGE, THEME, TRANSLATION_X};
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonViewBinder.java
index 0693f206..eb423b57 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonViewBinder.java
@@ -15,7 +15,9 @@
 class MenuButtonViewBinder implements ViewBinder<PropertyModel, MenuButton, PropertyKey> {
     @Override
     public void bind(PropertyModel model, MenuButton view, PropertyKey propertyKey) {
-        if (propertyKey == MenuButtonProperties.APP_MENU_BUTTON_HELPER) {
+        if (propertyKey == MenuButtonProperties.ALPHA) {
+            view.setAlpha(model.get(MenuButtonProperties.ALPHA));
+        } else if (propertyKey == MenuButtonProperties.APP_MENU_BUTTON_HELPER) {
             view.setAppMenuButtonHelper(model.get(MenuButtonProperties.APP_MENU_BUTTON_HELPER));
         } else if (propertyKey == MenuButtonProperties.CONTENT_DESCRIPTION) {
             view.updateContentDescription(model.get(MenuButtonProperties.CONTENT_DESCRIPTION));
@@ -36,6 +38,8 @@
         } else if (propertyKey == MenuButtonProperties.THEME) {
             ThemeProperty themeProperty = model.get(MenuButtonProperties.THEME);
             view.onTintChanged(themeProperty.mColorStateList, themeProperty.mUseLightColors);
+        } else if (propertyKey == MenuButtonProperties.TRANSLATION_X) {
+            view.setTranslationX(model.get(MenuButtonProperties.TRANSLATION_X));
         }
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/menu_button/PropertyModelAnimatorFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/menu_button/PropertyModelAnimatorFactory.java
new file mode 100644
index 0000000..093b96b
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/menu_button/PropertyModelAnimatorFactory.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.chrome.browser.toolbar.menu_button;
+
+import android.animation.ObjectAnimator;
+import android.util.Property;
+
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.PropertyModel.WritableFloatPropertyKey;
+
+/**
+ * Static factory class that creates Animators for MVC properties by providing implementations of
+ * android.util.Property that mutate a given property in a given model.
+ */
+class PropertyModelAnimatorFactory {
+    /**
+     * Builds an Animator for the given model, key, and target value.
+     * @param model PropertyModel object to write changes to the given key to.
+     * @param key Key of the property to change.
+     * @param targetValue Target end value of the property.
+     * @return An Animator that when run, will animate the property from its current value to the
+     *         given target.
+     */
+    static ObjectAnimator ofFloat(
+            PropertyModel model, WritableFloatPropertyKey key, float targetValue) {
+        PropertyModelFloatProp customProperty = new PropertyModelFloatProp(key);
+        return ObjectAnimator.ofFloat(model, customProperty, targetValue);
+    }
+
+    private static class PropertyModelFloatProp extends Property<PropertyModel, Float> {
+        final WritableFloatPropertyKey mKey;
+
+        public PropertyModelFloatProp(WritableFloatPropertyKey key) {
+            super(Float.class, key.toString());
+            mKey = key;
+        }
+
+        @Override
+        public Float get(PropertyModel model) {
+            return model.get(mKey);
+        }
+
+        @Override
+        public void set(PropertyModel model, Float value) {
+            model.set(mKey, value);
+        }
+    }
+
+    // TODO(https://crbug.com/1086676, pnoland): Extract this from toolbar.menu_button and implement
+    // factory methods for other types, e.g. int and aRGB.
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
index 2611d14..a8c1682 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
@@ -68,7 +68,6 @@
 import org.chromium.chrome.browser.toolbar.TabCountProvider.TabCountObserver;
 import org.chromium.chrome.browser.toolbar.TabSwitcherDrawable;
 import org.chromium.chrome.browser.toolbar.ToolbarColors;
-import org.chromium.chrome.browser.toolbar.menu_button.MenuButton;
 import org.chromium.chrome.browser.toolbar.menu_button.MenuButtonCoordinator;
 import org.chromium.chrome.browser.toolbar.top.TopToolbarCoordinator.UrlExpansionObserver;
 import org.chromium.components.browser_ui.styles.ChromeColors;
@@ -94,7 +93,6 @@
     public static final long THEME_COLOR_TRANSITION_DURATION = 250;
 
     public static final int URL_FOCUS_CHANGE_ANIMATION_DURATION_MS = 225;
-    private static final int URL_FOCUS_TOOLBAR_BUTTONS_TRANSLATION_X_DP = 10;
     private static final int URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS = 100;
     private static final int URL_CLEAR_FOCUS_EXPERIMENTAL_BUTTON_DELAY_MS = 150;
     private static final int URL_CLEAR_FOCUS_TABSTACK_DELAY_MS = 200;
@@ -265,6 +263,7 @@
     private AnimatorSet mOptionalButtonAnimator;
     private boolean mOptionalButtonAnimationRunning;
     private int mOptionalButtonTranslation;
+    private int mUrlFocusTranslationX;
 
     /**
      * The progress fraction for the location bar width change animation that is run when the
@@ -363,6 +362,8 @@
             inflateTabSwitchingResources();
 
             setWillNotDraw(false);
+            mUrlFocusTranslationX =
+                    getResources().getDimensionPixelSize(R.dimen.toolbar_url_focus_translation_x);
         }
     }
 
@@ -1242,14 +1243,14 @@
         mLocationBar.setAlpha(previousAlpha);
 
         // Translate to draw end toolbar buttons.
-        translateCanvasToView(this, mToolbarButtonsContainer, canvas);
+        ViewUtils.translateCanvasToView(this, mToolbarButtonsContainer, canvas);
 
         // Draw the optional button if necessary.
         if (mOptionalButton != null && mOptionalButton.getVisibility() != View.GONE) {
             canvas.save();
             Drawable optionalButtonDrawable = mOptionalButton.getDrawable();
 
-            translateCanvasToView(mToolbarButtonsContainer, mOptionalButton, canvas);
+            ViewUtils.translateCanvasToView(mToolbarButtonsContainer, mOptionalButton, canvas);
 
             int backgroundWidth = mOptionalButton.getDrawable().getIntrinsicWidth();
             int backgroundHeight = mOptionalButton.getDrawable().getIntrinsicHeight();
@@ -1274,7 +1275,8 @@
                 && mUrlExpansionFraction != 1f) {
             // Draw the tab stack button image.
             canvas.save();
-            translateCanvasToView(mToolbarButtonsContainer, mToggleTabStackButton, canvas);
+            ViewUtils.translateCanvasToView(
+                    mToolbarButtonsContainer, mToggleTabStackButton, canvas);
 
             int backgroundWidth = mToggleTabStackButton.getDrawable().getIntrinsicWidth();
             int backgroundHeight = mToggleTabStackButton.getDrawable().getIntrinsicHeight();
@@ -1298,12 +1300,10 @@
         }
 
         // Draw the menu button if necessary.
-        final MenuButton menuButton = getMenuButtonCoordinator().getMenuButton();
-        if (menuButton != null) {
-            canvas.save();
-            translateCanvasToView(mToolbarButtonsContainer, menuButton, canvas);
-            menuButton.drawTabSwitcherAnimationOverlay(canvas, rgbAlpha);
-            canvas.restore();
+        final MenuButtonCoordinator menuButtonCoordinator = getMenuButtonCoordinator();
+        if (menuButtonCoordinator != null) {
+            menuButtonCoordinator.drawTabSwitcherAnimationOverlay(
+                    mToolbarButtonsContainer, canvas, rgbAlpha);
         }
 
         mLightDrawablesUsedForLastTextureCapture = useLight();
@@ -1315,28 +1315,6 @@
         canvas.restore();
     }
 
-    /**
-     * Translates the canvas to ensure the specified view's coordinates are at 0, 0.
-     *
-     * @param from The view the canvas is currently translated to.
-     * @param to The view to translate to.
-     * @param canvas The canvas to be translated.
-     *
-     * @throws IllegalArgumentException if {@code from} is not an ancestor of {@code to}.
-     */
-    private static void translateCanvasToView(View from, View to, Canvas canvas)
-            throws IllegalArgumentException {
-        assert from != null;
-        assert to != null;
-        while (to != from) {
-            canvas.translate(to.getLeft(), to.getTop());
-            if (!(to.getParent() instanceof View)) {
-                throw new IllegalArgumentException("View 'to' was not a desendent of 'from'.");
-            }
-            to = (View) to.getParent();
-        }
-    }
-
     @Override
     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
         if (child == mLocationBar) return drawLocationBar(canvas, drawingTime);
@@ -1984,21 +1962,12 @@
         float density = getContext().getResources().getDisplayMetrics().density;
         boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
         float toolbarButtonTranslationX =
-                MathUtils.flipSignIf(URL_FOCUS_TOOLBAR_BUTTONS_TRANSLATION_X_DP, isRtl) * density;
+                MathUtils.flipSignIf(mUrlFocusTranslationX, isRtl) * density;
 
-        final View menuButtonWrapper = getMenuButtonCoordinator().getMenuButton();
-        if (menuButtonWrapper != null) {
-            animator = ObjectAnimator.ofFloat(
-                    menuButtonWrapper, TRANSLATION_X, toolbarButtonTranslationX);
-            animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS);
-            animator.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE);
-            animators.add(animator);
-
-            animator = ObjectAnimator.ofFloat(menuButtonWrapper, ALPHA, 0);
-            animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS);
-            animator.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE);
-            animators.add(animator);
-        }
+        animator = getMenuButtonCoordinator().getUrlFocusingAnimator(true);
+        animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS);
+        animator.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE);
+        animators.add(animator);
 
         if (mToggleTabStackButton != null) {
             animator = ObjectAnimator.ofFloat(
@@ -2041,20 +2010,10 @@
         animator.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE);
         animators.add(animator);
 
-        final View menuButtonWrapper = getMenuButtonCoordinator().getMenuButton();
-        if (menuButtonWrapper != null) {
-            animator = ObjectAnimator.ofFloat(menuButtonWrapper, TRANSLATION_X, 0);
-            animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS);
-            animator.setStartDelay(URL_CLEAR_FOCUS_MENU_DELAY_MS);
-            animator.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE);
-            animators.add(animator);
-
-            animator = ObjectAnimator.ofFloat(menuButtonWrapper, ALPHA, 1);
-            animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS);
-            animator.setStartDelay(URL_CLEAR_FOCUS_MENU_DELAY_MS);
-            animator.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE);
-            animators.add(animator);
-        }
+        animator = getMenuButtonCoordinator().getUrlFocusingAnimator(false);
+        animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS);
+        animator.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE);
+        animators.add(animator);
 
         if (mToggleTabStackButton != null) {
             animator = ObjectAnimator.ofFloat(mToggleTabStackButton, TRANSLATION_X, 0);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tasks/ReturnToChromeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tasks/ReturnToChromeTest.java
index 5f735c9..f3797a6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tasks/ReturnToChromeTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tasks/ReturnToChromeTest.java
@@ -413,6 +413,8 @@
     // clang-format off
     @CommandLineFlags.Add({BASE_PARAMS + "/" + TAB_SWITCHER_ON_RETURN_MS_PARAM + "/0"
             + "/start_surface_variation/omniboxonly"})
+    @DisableIf.Build(sdk_is_less_than = VERSION_CODES.Q, sdk_is_greater_than = VERSION_CODES.O,
+            message = "crbug.com/1134361")
     public void testTabSwitcherModeTriggeredBeyondThreshold_WarmStart() throws Exception {
         // clang-format on
         testTabSwitcherModeTriggeredBeyondThreshold();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhoneTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhoneTest.java
index cdd9aa723..73bd82af 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhoneTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhoneTest.java
@@ -4,12 +4,19 @@
 
 package org.chromium.chrome.browser.toolbar.top;
 
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+
+import static org.hamcrest.Matchers.allOf;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.graphics.Canvas;
+import android.view.View;
 
+import androidx.test.espresso.matcher.ViewMatchers.Visibility;
 import androidx.test.filters.MediumTest;
 
 import org.junit.Before;
@@ -46,6 +53,7 @@
 
     private Canvas mCanvas = new Canvas();
     private ToolbarPhone mToolbar;
+    private View mToolbarButtonsContainer;
     private MenuButton mMenuButton;
 
     @Before
@@ -54,22 +62,37 @@
 
         mActivityTestRule.startMainActivityOnBlankPage();
         mToolbar = mActivityTestRule.getActivity().findViewById(R.id.toolbar);
-        mMenuButton = Mockito.spy(mToolbar.findViewById(R.id.menu_button_wrapper));
-        mToolbar.setMenuButtonCoordinatorForTesting(mMenuButtonCoordinator);
-        doReturn(mMenuButton).when(mMenuButtonCoordinator).getMenuButton();
+        mToolbarButtonsContainer = mToolbar.findViewById(R.id.toolbar_buttons);
     }
 
     @Test
     @MediumTest
     public void testDrawTabSwitcherAnimation_menuButtonDrawn() {
+        mMenuButton = Mockito.spy(mToolbar.findViewById(R.id.menu_button_wrapper));
+        mToolbar.setMenuButtonCoordinatorForTesting(mMenuButtonCoordinator);
+        doReturn(mMenuButton).when(mMenuButtonCoordinator).getMenuButton();
+
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             mToolbar.drawTabSwitcherAnimationOverlay(mCanvas, 0);
-            verify(mMenuButton).drawTabSwitcherAnimationOverlay(mCanvas, 255);
+            verify(mMenuButtonCoordinator)
+                    .drawTabSwitcherAnimationOverlay(mToolbarButtonsContainer, mCanvas, 255);
 
             mToolbar.setTextureCaptureMode(true);
             mToolbar.draw(mCanvas);
-            verify(mMenuButton, times(2)).drawTabSwitcherAnimationOverlay(mCanvas, 255);
+            verify(mMenuButtonCoordinator, times(2))
+                    .drawTabSwitcherAnimationOverlay(mToolbarButtonsContainer, mCanvas, 255);
             mToolbar.setTextureCaptureMode(false);
         });
     }
+
+    @Test
+    @MediumTest
+    public void testFocusAnimation_menuButtonHidesAndShows() {
+        TestThreadUtils.runOnUiThreadBlocking(() -> { mToolbar.onUrlFocusChange(true); });
+        onView(allOf(withId(R.id.menu_button_wrapper), withEffectiveVisibility(Visibility.GONE)));
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> { mToolbar.onUrlFocusChange(false); });
+        onView(allOf(
+                withId(R.id.menu_button_wrapper), withEffectiveVisibility(Visibility.VISIBLE)));
+    }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewTest.java
index a2a1ee05..1712d1fd 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewTest.java
@@ -41,6 +41,9 @@
     private View mContentView;
 
     @Mock
+    private Runnable mOnFocusListener;
+
+    @Mock
     SuggestionViewDelegate mMockDelegate;
 
     // IMPORTANT: We need to extend the tested class here to support functionality currently
@@ -87,6 +90,7 @@
         mContentView = new View(mActivity);
         mView = new BaseSuggestionViewForTest(mContentView);
         mView.setDelegate(mMockDelegate);
+        mView.setOnFocusViaSelectionListener(mOnFocusListener);
 
         mActionIconWidthPx = mActivity.getResources().getDimensionPixelSize(
                 R.dimen.omnibox_suggestion_action_icon_width);
@@ -333,12 +337,12 @@
     @Test
     public void setSelected_emitsOmniboxUpdateWhenSelected() {
         mView.setSelected(true);
-        verify(mMockDelegate, times(1)).onSetUrlToSuggestion();
+        verify(mOnFocusListener, times(1)).run();
     }
 
     @Test
     public void setSelected_noOmniboxUpdateWhenDeselected() {
         mView.setSelected(false);
-        verify(mMockDelegate, never()).onSetUrlToSuggestion();
+        verify(mOnFocusListener, never()).run();
     }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonCoordinatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonCoordinatorTest.java
index 826e2b9..e1d47669 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonCoordinatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonCoordinatorTest.java
@@ -9,6 +9,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.app.Activity;
+import android.content.res.Resources;
 import android.widget.ImageButton;
 
 import org.junit.Before;
@@ -56,6 +57,8 @@
     private Runnable mRequestRenderRunnable;
     @Mock
     ThemeColorProvider mThemeColorProvider;
+    @Mock
+    Resources mResources;
 
     private UpdateMenuItemHelper.MenuUiState mMenuUiState;
     private OneshotSupplierImpl<AppMenuCoordinator> mAppMenuSupplier;
@@ -77,6 +80,10 @@
                 .when(mActivity)
                 .findViewById(org.chromium.chrome.R.id.menu_button_wrapper);
         doReturn(mImageButton).when(mMenuButton).getImageButton();
+        doReturn(mResources).when(mActivity).getResources();
+        doReturn(10)
+                .when(mResources)
+                .getDimensionPixelSize(org.chromium.chrome.R.dimen.toolbar_url_focus_translation_x);
 
         mMenuButtonCoordinator = new MenuButtonCoordinator(mAppMenuSupplier,
                 mControlsVisibilityDelegate, mActivity, mFocusFunction, mRequestRenderRunnable,
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index fc23709..9a1ee42e 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -1637,6 +1637,12 @@
   <message name="IDS_SETTINGS_MOUSE_SWAP_BUTTONS_LABEL" desc="In Device Settings, the text next to the checkbox to set the primary mouse button to the right button instead of the left button.">
     Swap primary mouse button
   </message>
+  <message name="IDS_SETTINGS_PRIMARY_MOUSE_BUTTON_LEFT_LABEL" desc="In Device Settings, the text labelling the dropdown menu item for the left mouse button.">
+    Left button
+  </message>
+  <message name="IDS_SETTINGS_PRIMARY_MOUSE_BUTTON_RIGHT_LABEL" desc="In Device Settings, the text labelling the dropdown menu item for the right mouse button.">
+    Right button
+  </message>
   <message name="IDS_SETTINGS_MOUSE_REVERSE_SCROLL_LABEL" desc="In Device Settings, the text next to the checkbox to set reverse scrolling.">
     Enable reverse scrolling. <ph name="LINK_BEGIN">&lt;a&gt;</ph>Learn more<ph name="LINK_END">&lt;/a&gt;</ph>
   </message>
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_PRIMARY_MOUSE_BUTTON_LEFT_LABEL.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_PRIMARY_MOUSE_BUTTON_LEFT_LABEL.png.sha1
new file mode 100644
index 0000000..843d8b2
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_PRIMARY_MOUSE_BUTTON_LEFT_LABEL.png.sha1
@@ -0,0 +1 @@
+0de32bdc0a9e5c95680629877c323c11d242cf03
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_PRIMARY_MOUSE_BUTTON_RIGHT_LABEL.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_PRIMARY_MOUSE_BUTTON_RIGHT_LABEL.png.sha1
new file mode 100644
index 0000000..d0f4c974
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_PRIMARY_MOUSE_BUTTON_RIGHT_LABEL.png.sha1
@@ -0,0 +1 @@
+037c4cf7a5c655f6e15a4ca3c906230359440d50
\ No newline at end of file
diff --git a/chrome/browser/android/vr/BUILD.gn b/chrome/browser/android/vr/BUILD.gn
index 4292683..90ca4dfa 100644
--- a/chrome/browser/android/vr/BUILD.gn
+++ b/chrome/browser/android/vr/BUILD.gn
@@ -40,8 +40,6 @@
     "gvr_scheduler_delegate.h",
     "gvr_util.cc",
     "gvr_util.h",
-    "mailbox_to_surface_bridge.cc",
-    "mailbox_to_surface_bridge.h",
     "metrics_util_android.cc",
     "metrics_util_android.h",
     "register_gvr_jni.cc",
@@ -63,29 +61,16 @@
     "vr_shell_delegate.h",
     "vrcore_install_helper.cc",
     "vrcore_install_helper.h",
-    "web_xr_presentation_state.cc",
-    "web_xr_presentation_state.h",
   ]
 
   if (enable_arcore) {
     sources += [
-      "arcore_device/ar_image_transport.cc",
-      "arcore_device/ar_image_transport.h",
-      "arcore_device/ar_renderer.cc",
-      "arcore_device/ar_renderer.h",
-      "arcore_device/arcore_device.cc",
-      "arcore_device/arcore_device.h",
       "arcore_device/arcore_device_provider.cc",
       "arcore_device/arcore_device_provider.h",
-      "arcore_device/arcore_gl.cc",
-      "arcore_device/arcore_gl.h",
-      "arcore_device/arcore_gl_thread.cc",
-      "arcore_device/arcore_gl_thread.h",
       "arcore_device/arcore_install_helper.cc",
       "arcore_device/arcore_install_helper.h",
       "arcore_device/arcore_java_utils.cc",
       "arcore_device/arcore_java_utils.h",
-      "arcore_device/arcore_session_utils.h",
     ]
   }
 
@@ -106,10 +91,12 @@
     "//components/permissions",
     "//components/rappor",
     "//components/search_engines:search_engines",
+    "//components/webxr:webxr",
     "//content/public/browser",
     "//content/public/common",
     "//device/vr",
     "//device/vr:vr_base",
+    "//device/vr/android:vr_android",
     "//device/vr/buildflags:buildflags",
     "//services/device/public/mojom",
     "//services/metrics/public/cpp:ukm_builders",
@@ -238,6 +225,7 @@
     "//base/test:test_support",
     "//chrome/browser",
     "//components/translate/core/language_detection:language_detection",
+    "//components/webxr:webxr",
     "//device/vr:vr_fakes",
     "//device/vr/android/arcore",
     "//device/vr/public/mojom",
diff --git a/chrome/browser/android/vr/DEPS b/chrome/browser/android/vr/DEPS
index 36776e6..eb5f1416 100644
--- a/chrome/browser/android/vr/DEPS
+++ b/chrome/browser/android/vr/DEPS
@@ -3,6 +3,7 @@
   "+cc/layers",
   "+cc/test",
   "+chrome/android/features/vr/jni_headers",
+  "+components/webxr",
   "+device/vr",
   "+services/metrics/public/cpp/ukm_builders.h",
   "+third_party/gvr-android-keyboard/src",
diff --git a/chrome/browser/android/vr/arcore_device/arcore_device_provider.cc b/chrome/browser/android/vr/arcore_device/arcore_device_provider.cc
index 9c559d1..c553e99 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_device_provider.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_device_provider.cc
@@ -4,7 +4,11 @@
 
 #include "chrome/browser/android/vr/arcore_device/arcore_device_provider.h"
 
-#include "chrome/browser/android/vr/arcore_device/arcore_device.h"
+#include "chrome/browser/android/vr/arcore_device/arcore_java_utils.h"
+#include "components/webxr/mailbox_to_surface_bridge_impl.h"
+#include "device/vr/android/arcore/ar_image_transport.h"
+#include "device/vr/android/arcore/arcore_device.h"
+#include "device/vr/android/arcore/arcore_impl.h"
 #include "device/vr/android/arcore/arcore_shim.h"
 
 namespace device {
@@ -23,7 +27,11 @@
     base::OnceClosure initialization_complete) {
   if (vr::IsArCoreSupported()) {
     DVLOG(2) << __func__ << ": ARCore is supported, creating device";
-    arcore_device_ = std::make_unique<ArCoreDevice>();
+    arcore_device_ = std::make_unique<ArCoreDevice>(
+        std::make_unique<ArCoreImplFactory>(),
+        std::make_unique<ArImageTransportFactory>(),
+        std::make_unique<webxr::MailboxToSurfaceBridgeFactoryImpl>(),
+        std::make_unique<vr::ArCoreJavaUtils>());
 
     add_device_callback.Run(
         arcore_device_->GetId(), arcore_device_->GetVRDisplayInfo(),
diff --git a/chrome/browser/android/vr/arcore_device/arcore_device_unittest.cc b/chrome/browser/android/vr/arcore_device/arcore_device_unittest.cc
index efab547..4837ebc 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_device_unittest.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_device_unittest.cc
@@ -2,21 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/android/vr/arcore_device/arcore_device.h"
+#include "device/vr/android/arcore/arcore_device.h"
 
 #include <memory>
 
 #include "base/bind.h"
 #include "base/memory/ptr_util.h"
 #include "base/run_loop.h"
-#include "chrome/browser/android/vr/arcore_device/ar_image_transport.h"
-#include "chrome/browser/android/vr/arcore_device/arcore_gl.h"
-#include "chrome/browser/android/vr/arcore_device/arcore_session_utils.h"
 #include "chrome/browser/android/vr/arcore_device/fake_arcore.h"
-#include "chrome/browser/android/vr/mailbox_to_surface_bridge.h"
+#include "components/webxr/mailbox_to_surface_bridge_impl.h"
+#include "device/vr/android/arcore/ar_image_transport.h"
+#include "device/vr/android/arcore/arcore_gl.h"
+#include "device/vr/android/arcore/arcore_session_utils.h"
 #include "device/vr/public/mojom/vr_service.mojom.h"
-#include "device/vr/test/fake_vr_device.h"
-#include "device/vr/test/fake_vr_service_client.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
@@ -28,7 +26,7 @@
 class StubArImageTransport : public ArImageTransport {
  public:
   explicit StubArImageTransport(
-      std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_bridge)
+      std::unique_ptr<MailboxToSurfaceBridge> mailbox_bridge)
       : ArImageTransport(std::move(mailbox_bridge)) {}
 
   void Initialize(vr::WebXrPresentationState*,
@@ -49,7 +47,7 @@
     return gpu::MailboxHolder();
   }
 
-  std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_bridge_;
+  std::unique_ptr<MailboxToSurfaceBridge> mailbox_bridge_;
   const GLuint CAMERA_TEXTURE_ID = 10;
 };
 
@@ -58,12 +56,12 @@
   ~StubArImageTransportFactory() override = default;
 
   std::unique_ptr<ArImageTransport> Create(
-      std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_bridge) override {
+      std::unique_ptr<MailboxToSurfaceBridge> mailbox_bridge) override {
     return std::make_unique<StubArImageTransport>(std::move(mailbox_bridge));
   }
 };
 
-class StubMailboxToSurfaceBridge : public vr::MailboxToSurfaceBridge {
+class StubMailboxToSurfaceBridge : public webxr::MailboxToSurfaceBridgeImpl {
  public:
   StubMailboxToSurfaceBridge() = default;
 
@@ -73,14 +71,19 @@
 
   bool IsConnected() override { return true; }
 
-  void CallCallback() { std::move(callback_).Run(); }
-
   const uint32_t TEXTURE_ID = 1;
 
  private:
   base::OnceClosure callback_;
 };
 
+class StubMailboxToSurfaceBridgeFactory : public MailboxToSurfaceBridgeFactory {
+ public:
+  std::unique_ptr<device::MailboxToSurfaceBridge> Create() const override {
+    return std::make_unique<StubMailboxToSurfaceBridge>();
+  }
+};
+
 class StubArCoreSessionUtils : public vr::ArCoreSessionUtils {
  public:
   StubArCoreSessionUtils() = default;
@@ -140,7 +143,6 @@
     std::move(quit_closure).Run();
   }
 
-  StubMailboxToSurfaceBridge* bridge;
   StubArCoreSessionUtils* session_utils;
   mojo::Remote<mojom::XRFrameDataProvider> frame_provider;
   mojo::AssociatedRemote<mojom::XREnvironmentIntegrationProvider>
@@ -150,15 +152,13 @@
 
  protected:
   void SetUp() override {
-    std::unique_ptr<StubMailboxToSurfaceBridge> bridge_ptr =
-        std::make_unique<StubMailboxToSurfaceBridge>();
-    bridge = bridge_ptr.get();
     std::unique_ptr<StubArCoreSessionUtils> session_utils_ptr =
         std::make_unique<StubArCoreSessionUtils>();
     session_utils = session_utils_ptr.get();
     device_ = std::make_unique<ArCoreDevice>(
         std::make_unique<FakeArCoreFactory>(),
-        std::make_unique<StubArImageTransportFactory>(), std::move(bridge_ptr),
+        std::make_unique<StubArImageTransportFactory>(),
+        std::make_unique<StubMailboxToSurfaceBridgeFactory>(),
         std::move(session_utils_ptr));
   }
 
diff --git a/chrome/browser/android/vr/arcore_device/arcore_java_utils.h b/chrome/browser/android/vr/arcore_device/arcore_java_utils.h
index bfc2fb4a..f5ccf57 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_java_utils.h
+++ b/chrome/browser/android/vr/arcore_device/arcore_java_utils.h
@@ -11,7 +11,7 @@
 #include "base/android/scoped_java_ref.h"
 #include "base/callback.h"
 #include "base/memory/weak_ptr.h"
-#include "chrome/browser/android/vr/arcore_device/arcore_session_utils.h"
+#include "device/vr/android/arcore/arcore_session_utils.h"
 
 namespace vr {
 
diff --git a/chrome/browser/android/vr/arcore_device/fake_arcore.cc b/chrome/browser/android/vr/arcore_device/fake_arcore.cc
index fff7829..d6476c13 100644
--- a/chrome/browser/android/vr/arcore_device/fake_arcore.cc
+++ b/chrome/browser/android/vr/arcore_device/fake_arcore.cc
@@ -7,9 +7,6 @@
 #include "base/android/android_hardware_buffer_compat.h"
 #include "base/numerics/math_constants.h"
 #include "base/single_thread_task_runner.h"
-#include "ui/display/display.h"
-#include "ui/gfx/buffer_types.h"
-#include "ui/gl/gl_image_ahardwarebuffer.h"
 
 namespace {}
 
diff --git a/chrome/browser/android/vr/gvr_graphics_delegate.h b/chrome/browser/android/vr/gvr_graphics_delegate.h
index 243ba4b..9f26abb 100644
--- a/chrome/browser/android/vr/gvr_graphics_delegate.h
+++ b/chrome/browser/android/vr/gvr_graphics_delegate.h
@@ -14,9 +14,9 @@
 #include "base/containers/queue.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
-#include "chrome/browser/android/vr/web_xr_presentation_state.h"
 #include "chrome/browser/vr/base_graphics_delegate.h"
 #include "chrome/browser/vr/render_info.h"
+#include "device/vr/android/web_xr_presentation_state.h"
 #include "device/vr/util/sliding_average.h"
 #include "third_party/gvr-android-sdk/src/libraries/headers/vr/gvr/capi/include/gvr.h"
 #include "third_party/gvr-android-sdk/src/libraries/headers/vr/gvr/capi/include/gvr_types.h"
diff --git a/chrome/browser/android/vr/gvr_install_helper.cc b/chrome/browser/android/vr/gvr_install_helper.cc
index 2cb22a21..943ee51 100644
--- a/chrome/browser/android/vr/gvr_install_helper.cc
+++ b/chrome/browser/android/vr/gvr_install_helper.cc
@@ -8,7 +8,7 @@
 
 #include "base/bind.h"
 #include "base/logging.h"
-#include "chrome/browser/android/vr/android_vr_utils.h"
+#include "chrome/browser/android/vr/vr_module_provider.h"
 #include "chrome/browser/android/vr/vrcore_install_helper.h"
 
 using base::android::AttachCurrentThread;
diff --git a/chrome/browser/android/vr/gvr_scheduler_delegate.cc b/chrome/browser/android/vr/gvr_scheduler_delegate.cc
index 1f911cf6..dd2f613 100644
--- a/chrome/browser/android/vr/gvr_scheduler_delegate.cc
+++ b/chrome/browser/android/vr/gvr_scheduler_delegate.cc
@@ -14,11 +14,11 @@
 #include "base/trace_event/trace_event.h"
 #include "base/trace_event/traced_value.h"
 #include "chrome/browser/android/vr/gl_browser_interface.h"
-#include "chrome/browser/android/vr/mailbox_to_surface_bridge.h"
 #include "chrome/browser/android/vr/metrics_util_android.h"
 #include "chrome/browser/android/vr/scoped_gpu_trace.h"
 #include "chrome/browser/vr/scheduler_browser_renderer_interface.h"
 #include "chrome/browser/vr/scheduler_ui_interface.h"
+#include "components/webxr/mailbox_to_surface_bridge_impl.h"
 #include "content/public/common/content_features.h"
 #include "device/vr/android/gvr/gvr_delegate.h"
 #include "gpu/command_buffer/common/shared_image_usage.h"
@@ -304,7 +304,7 @@
     gl::SurfaceTexture* surface_texture) {
   DCHECK(!mailbox_bridge_);
   DCHECK(!webxr_.mailbox_bridge_ready());
-  mailbox_bridge_ = std::make_unique<MailboxToSurfaceBridge>();
+  mailbox_bridge_ = std::make_unique<webxr::MailboxToSurfaceBridgeImpl>();
   if (surface_texture)
     mailbox_bridge_->CreateSurface(surface_texture);
   mailbox_bridge_->CreateAndBindContextProvider(
diff --git a/chrome/browser/android/vr/gvr_scheduler_delegate.h b/chrome/browser/android/vr/gvr_scheduler_delegate.h
index 4313f84..7fde4a6 100644
--- a/chrome/browser/android/vr/gvr_scheduler_delegate.h
+++ b/chrome/browser/android/vr/gvr_scheduler_delegate.h
@@ -16,8 +16,8 @@
 #include "base/memory/ref_counted.h"
 #include "chrome/browser/android/vr/android_vsync_helper.h"
 #include "chrome/browser/android/vr/gvr_graphics_delegate.h"
-#include "chrome/browser/android/vr/web_xr_presentation_state.h"
 #include "chrome/browser/vr/base_scheduler_delegate.h"
+#include "device/vr/android/web_xr_presentation_state.h"
 #include "device/vr/public/mojom/vr_service.mojom.h"
 #include "device/vr/util/sliding_average.h"
 #include "mojo/public/cpp/bindings/pending_associated_receiver.h"
@@ -40,10 +40,13 @@
 class GvrApi;
 }
 
+namespace device {
+class MailboxToSurfaceBridge;
+}
+
 namespace vr {
 
 class GlBrowserInterface;
-class MailboxToSurfaceBridge;
 class SchedulerUiInterface;
 class ScopedGpuTrace;
 class SlidingTimeDeltaAverage;
@@ -235,7 +238,7 @@
       void(FrameType, const gfx::Transform&, std::unique_ptr<gl::GLFenceEGL>)>
       webxr_delayed_gvr_submit_;
 
-  std::unique_ptr<MailboxToSurfaceBridge> mailbox_bridge_;
+  std::unique_ptr<device::MailboxToSurfaceBridge> mailbox_bridge_;
   std::unique_ptr<ScopedGpuTrace> gpu_trace_;
 
   device::FPSMeter vr_ui_fps_meter_;
diff --git a/chrome/browser/android/vr/mailbox_to_surface_bridge.h b/chrome/browser/android/vr/mailbox_to_surface_bridge.h
deleted file mode 100644
index 6b58838..0000000
--- a/chrome/browser/android/vr/mailbox_to_surface_bridge.h
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_ANDROID_VR_MAILBOX_TO_SURFACE_BRIDGE_H_
-#define CHROME_BROWSER_ANDROID_VR_MAILBOX_TO_SURFACE_BRIDGE_H_
-
-#include "base/callback.h"
-#include "base/macros.h"
-#include "base/memory/weak_ptr.h"
-#include "base/single_thread_task_runner.h"
-#include "gpu/command_buffer/common/sync_token.h"
-#include "gpu/ipc/common/surface_handle.h"
-#include "ui/gfx/buffer_format_util.h"
-#include "ui/gfx/gpu_fence.h"
-#include "ui/gl/android/scoped_java_surface.h"
-
-namespace gl {
-class SurfaceTexture;
-}  // namespace gl
-
-namespace gfx {
-class ColorSpace;
-}
-
-namespace gpu {
-class ContextSupport;
-class GpuMemoryBufferImplAndroidHardwareBuffer;
-struct MailboxHolder;
-struct SyncToken;
-namespace gles2 {
-class GLES2Interface;
-}
-}  // namespace gpu
-
-namespace viz {
-class ContextProvider;
-}
-
-namespace vr {
-
-class MailboxToSurfaceBridge {
- public:
-  // It's OK to create an object instance and pass it to a different thread,
-  // i.e. to enable dependency injection for a unit test, but all methods on it
-  // must be called consistently on a single GL thread. This is verified by
-  // DCHECKs.
-  MailboxToSurfaceBridge();
-  virtual ~MailboxToSurfaceBridge();
-
-  // Returns true if the GPU process connection is established and ready to use.
-  // Equivalent to waiting for on_initialized to be called.
-  virtual bool IsConnected();
-
-  // Checks if a workaround from "gpu/config/gpu_driver_bug_workaround_type.h"
-  // is active. Requires initialization to be complete.
-  bool IsGpuWorkaroundEnabled(int32_t workaround);
-
-  // This call is needed for Surface transport, in that case it must be called
-  // on the GL thread with a valid local native GL context. If it's not used,
-  // only the SharedBuffer transport methods are available.
-  void CreateSurface(gl::SurfaceTexture*);
-
-  // Asynchronously create the context using the surface provided by an earlier
-  // CreateSurface call, or an offscreen context if that wasn't called. Also
-  // binds the context provider to the current thread (making it the GL thread),
-  // and calls the callback on the GL thread.
-  virtual void CreateAndBindContextProvider(base::OnceClosure callback);
-
-  // All other public methods below must be called on the GL thread
-  // (except when marked otherwise).
-
-  void ResizeSurface(int width, int height);
-
-  // Returns true if swapped successfully. This can fail if the GL
-  // context isn't ready for use yet, in that case the caller
-  // won't get a new frame on the SurfaceTexture.
-  bool CopyMailboxToSurfaceAndSwap(const gpu::MailboxHolder& mailbox);
-
-  void GenSyncToken(gpu::SyncToken* out_sync_token);
-
-  void WaitSyncToken(const gpu::SyncToken& sync_token);
-
-  // Copies a GpuFence from the local context to the GPU process,
-  // and issues a server wait for it.
-  void WaitForClientGpuFence(gfx::GpuFence*);
-
-  // Creates a GpuFence in the GPU process after the supplied sync_token
-  // completes, and copies it for use in the local context. This is
-  // asynchronous, the callback receives the GpuFence once it's available.
-  void CreateGpuFence(
-      const gpu::SyncToken& sync_token,
-      base::OnceCallback<void(std::unique_ptr<gfx::GpuFence>)> callback);
-
-  // Creates a shared image bound to |buffer|. Returns a mailbox holder that
-  // references the shared image with a sync token representing a point after
-  // the creation. Caller must call DestroySharedImage to free the shared image.
-  // Does not take ownership of |buffer| or retain any references to it.
-  gpu::MailboxHolder CreateSharedImage(
-      gpu::GpuMemoryBufferImplAndroidHardwareBuffer* buffer,
-      const gfx::ColorSpace& color_space,
-      uint32_t usage);
-
-  // Destroys a shared image created by CreateSharedImage. The mailbox_holder's
-  // sync_token must have been updated to a sync token after the last use of the
-  // shared image.
-  void DestroySharedImage(const gpu::MailboxHolder& mailbox_holder);
-
- private:
-  void BindContextProviderToCurrentThread();
-  void OnContextAvailableOnUiThread(
-      scoped_refptr<viz::ContextProvider> provider);
-  void InitializeRenderer();
-  void DestroyContext();
-  void DrawQuad(unsigned int textureHandle);
-
-  scoped_refptr<viz::ContextProvider> context_provider_;
-  std::unique_ptr<gl::ScopedJavaSurface> surface_;
-  gpu::gles2::GLES2Interface* gl_ = nullptr;
-  gpu::ContextSupport* context_support_ = nullptr;
-  int surface_handle_ = gpu::kNullSurfaceHandle;
-  // TODO(https://crbug.com/836524): shouldn't have both of these closures
-  // in the same class like this.
-  base::OnceClosure on_context_bound_;
-
-  int surface_width_ = 0;
-  int surface_height_ = 0;
-
-  // If true, surface width/height is the intended size that should be applied
-  // to the surface once it's ready for use.
-  bool needs_resize_ = false;
-
-  // A swap ID which is passed to GL swap. Incremented each call.
-  uint64_t swap_id_ = 0;
-
-  // A task runner for the GL thread
-  scoped_refptr<base::SingleThreadTaskRunner> gl_thread_task_runner_;
-
-  // Must be last.
-  base::WeakPtrFactory<MailboxToSurfaceBridge> weak_ptr_factory_{this};
-
-  DISALLOW_COPY_AND_ASSIGN(MailboxToSurfaceBridge);
-};
-
-}  // namespace vr
-
-#endif  // CHROME_BROWSER_ANDROID_VR_MAILBOX_TO_SURFACE_BRIDGE_H_
diff --git a/chrome/browser/android/vr/vr_shell_delegate.cc b/chrome/browser/android/vr/vr_shell_delegate.cc
index 3caee0e..f2904d8 100644
--- a/chrome/browser/android/vr/vr_shell_delegate.cc
+++ b/chrome/browser/android/vr/vr_shell_delegate.cc
@@ -9,7 +9,6 @@
 #include "base/android/jni_android.h"
 #include "base/bind.h"
 #include "chrome/android/features/vr/jni_headers/VrShellDelegate_jni.h"
-#include "chrome/browser/android/vr/arcore_device/arcore_device_provider.h"
 #include "chrome/browser/android/vr/vr_shell.h"
 #include "chrome/browser/android/vr/vrcore_install_helper.h"
 #include "chrome/browser/browser_process.h"
diff --git a/chrome/browser/apps/app_service/app_icon_factory_unittest.cc b/chrome/browser/apps/app_service/app_icon_factory_unittest.cc
index c37eea8..318aaac 100644
--- a/chrome/browser/apps/app_service/app_icon_factory_unittest.cc
+++ b/chrome/browser/apps/app_service/app_icon_factory_unittest.cc
@@ -44,6 +44,7 @@
 #if defined(OS_CHROMEOS)
 #include "chrome/browser/chromeos/arc/icon_decode_request.h"
 #include "chrome/browser/ui/app_list/icon_standardizer.h"
+#include "chrome/browser/ui/app_list/md_icon_normalizer.h"
 #include "chrome/grit/chrome_unscaled_resources.h"
 #include "components/arc/mojom/intent_helper.mojom.h"
 #endif
@@ -612,6 +613,7 @@
             run_loop.QuitClosure()));
     run_loop.Run();
 
+    extensions::ChromeAppIcon::ResizeFunction resize_function;
 #if defined(OS_CHROMEOS)
     if (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon)) {
       if (purpose == IconPurpose::ANY) {
@@ -621,13 +623,16 @@
       if (purpose == IconPurpose::MASKABLE) {
         output_image_skia = apps::ApplyBackgroundAndMask(output_image_skia);
       }
+    } else {
+      resize_function =
+          base::BindRepeating(&app_list::MaybeResizeAndPadIconForMd);
     }
 #endif
 
     extensions::ChromeAppIcon::ApplyEffects(
-        kSizeInDip, extensions::ChromeAppIcon::ResizeFunction(),
-        true /* app_launchable */, true /* from_bookmark */,
-        extensions::ChromeAppIcon::Badge::kNone, &output_image_skia);
+        kSizeInDip, resize_function, true /* app_launchable */,
+        true /* from_bookmark */, extensions::ChromeAppIcon::Badge::kNone,
+        &output_image_skia);
 
     EnsureRepresentationsLoaded(output_image_skia);
   }
@@ -729,10 +734,17 @@
                      {{1.0, kIconSize1}, {2.0, kIconSize2}}, src_image_skia);
 
   gfx::ImageSkia dst_image_skia;
-  LoadIconFromWebApp(
-      app_id,
-      apps::IconEffects::kRoundCorners | apps::IconEffects::kCrOsStandardIcon,
-      dst_image_skia);
+  apps::IconEffects icon_effect = apps::IconEffects::kRoundCorners;
+
+#if defined(OS_CHROMEOS)
+  if (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon)) {
+    icon_effect |= apps::IconEffects::kCrOsStandardIcon;
+  } else {
+    icon_effect |= apps::IconEffects::kResizeAndPad;
+  }
+#endif
+
+  LoadIconFromWebApp(app_id, icon_effect, dst_image_skia);
 
   VerifyIcon(src_image_skia, dst_image_skia);
 }
@@ -758,10 +770,17 @@
                                src_data);
 
   apps::mojom::IconValuePtr icon;
-  LoadCompressedIconBlockingFromWebApp(
-      app_id,
-      apps::IconEffects::kRoundCorners | apps::IconEffects::kCrOsStandardIcon,
-      icon);
+  apps::IconEffects icon_effect = apps::IconEffects::kRoundCorners;
+
+#if defined(OS_CHROMEOS)
+  if (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon)) {
+    icon_effect |= apps::IconEffects::kCrOsStandardIcon;
+  } else {
+    icon_effect |= apps::IconEffects::kResizeAndPad;
+  }
+#endif
+
+  LoadCompressedIconBlockingFromWebApp(app_id, icon_effect, icon);
 
   VerifyCompressedIcon(src_data, icon);
 }
@@ -783,20 +802,25 @@
   RegisterApp(std::move(web_app));
 
 #if defined(OS_CHROMEOS)
-  ASSERT_TRUE(
-      icon_manager().HasIcons(app_id, IconPurpose::MASKABLE, {kIconSize2}));
+  if (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon)) {
+    ASSERT_TRUE(
+        icon_manager().HasIcons(app_id, IconPurpose::MASKABLE, {kIconSize2}));
 
-  gfx::ImageSkia src_image_skia;
-  GenerateWebAppIcon(app_id, IconPurpose::MASKABLE, {kIconSize2},
-                     {{1.0, kIconSize2}, {2.0, kIconSize2}}, src_image_skia);
+    gfx::ImageSkia src_image_skia;
+    GenerateWebAppIcon(app_id, IconPurpose::MASKABLE, {kIconSize2},
+                       {{1.0, kIconSize2}, {2.0, kIconSize2}}, src_image_skia);
 
-  gfx::ImageSkia dst_image_skia;
-  LoadIconFromWebApp(app_id,
-                     apps::IconEffects::kRoundCorners |
-                         apps::IconEffects::kCrOsStandardBackground |
-                         apps::IconEffects::kCrOsStandardMask,
-                     dst_image_skia);
-#else
+    gfx::ImageSkia dst_image_skia;
+    LoadIconFromWebApp(app_id,
+                       apps::IconEffects::kRoundCorners |
+                           apps::IconEffects::kCrOsStandardBackground |
+                           apps::IconEffects::kCrOsStandardMask,
+                       dst_image_skia);
+    VerifyIcon(src_image_skia, dst_image_skia);
+    return;
+  }
+#endif
+
   ASSERT_TRUE(icon_manager().HasIcons(app_id, IconPurpose::ANY, {kIconSize1}));
 
   gfx::ImageSkia src_image_skia;
@@ -805,7 +829,6 @@
 
   gfx::ImageSkia dst_image_skia;
   LoadIconFromWebApp(app_id, apps::IconEffects::kRoundCorners, dst_image_skia);
-#endif
 
   VerifyIcon(src_image_skia, dst_image_skia);
 }
@@ -828,30 +851,33 @@
 
   std::vector<uint8_t> src_data;
   apps::mojom::IconValuePtr icon;
+  apps::IconEffects icon_effect = apps::IconEffects::kRoundCorners;
 #if defined(OS_CHROMEOS)
-  ASSERT_TRUE(
-      icon_manager().HasIcons(app_id, IconPurpose::MASKABLE, {kIconSize2}));
+  if (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon)) {
+    icon_effect |= apps::IconEffects::kCrOsStandardBackground |
+                   apps::IconEffects::kCrOsStandardMask;
+    ASSERT_TRUE(
+        icon_manager().HasIcons(app_id, IconPurpose::MASKABLE, {kIconSize2}));
 
-  GenerateWebAppCompressedIcon(app_id, IconPurpose::MASKABLE, {kIconSize2},
-                               {{1.0, kIconSize2}, {2.0, kIconSize2}},
-                               src_data);
+    GenerateWebAppCompressedIcon(app_id, IconPurpose::MASKABLE, {kIconSize2},
+                                 {{1.0, kIconSize2}, {2.0, kIconSize2}},
+                                 src_data);
 
-  LoadCompressedIconBlockingFromWebApp(
-      app_id,
-      apps::IconEffects::kRoundCorners |
-          apps::IconEffects::kCrOsStandardBackground |
-          apps::IconEffects::kCrOsStandardMask,
-      icon);
-#else
+    LoadCompressedIconBlockingFromWebApp(app_id, icon_effect, icon);
+    VerifyCompressedIcon(src_data, icon);
+    return;
+  }
+
+  icon_effect |= apps::IconEffects::kResizeAndPad;
+#endif
+
   ASSERT_TRUE(icon_manager().HasIcons(app_id, IconPurpose::ANY, {kIconSize1}));
 
   GenerateWebAppCompressedIcon(app_id, IconPurpose::ANY, {kIconSize1},
                                {{1.0, kIconSize1}, {2.0, kIconSize1}},
                                src_data);
 
-  LoadCompressedIconBlockingFromWebApp(app_id, apps::IconEffects::kRoundCorners,
-                                       icon);
-#endif
+  LoadCompressedIconBlockingFromWebApp(app_id, icon_effect, icon);
 
   VerifyCompressedIcon(src_data, icon);
 }
@@ -879,10 +905,17 @@
                      {{1.0, kIconSize2}, {2.0, kIconSize2}}, src_image_skia);
 
   gfx::ImageSkia dst_image_skia;
-  LoadIconFromWebApp(
-      app_id,
-      apps::IconEffects::kRoundCorners | apps::IconEffects::kCrOsStandardIcon,
-      dst_image_skia);
+  apps::IconEffects icon_effect = apps::IconEffects::kRoundCorners;
+
+#if defined(OS_CHROMEOS)
+  if (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon)) {
+    icon_effect |= apps::IconEffects::kCrOsStandardIcon;
+  } else {
+    icon_effect |= apps::IconEffects::kResizeAndPad;
+  }
+#endif
+
+  LoadIconFromWebApp(app_id, icon_effect, dst_image_skia);
 
   VerifyIcon(src_image_skia, dst_image_skia);
 }
@@ -944,10 +977,17 @@
                      {{1.0, kIconSize2}, {2.0, kIconSize4}}, src_image_skia);
 
   gfx::ImageSkia dst_image_skia;
-  LoadIconFromWebApp(
-      app_id,
-      apps::IconEffects::kRoundCorners | apps::IconEffects::kCrOsStandardIcon,
-      dst_image_skia);
+  apps::IconEffects icon_effect = apps::IconEffects::kRoundCorners;
+
+#if defined(OS_CHROMEOS)
+  if (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon)) {
+    icon_effect |= apps::IconEffects::kCrOsStandardIcon;
+  } else {
+    icon_effect |= apps::IconEffects::kResizeAndPad;
+  }
+#endif
+
+  LoadIconFromWebApp(app_id, icon_effect, dst_image_skia);
 
   VerifyIcon(src_image_skia, dst_image_skia);
 }
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index c5c7a66..26a0847 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -3373,22 +3373,6 @@
     web_prefs->immersive_mode_enabled = vr::VrTabHelper::IsInVr(contents);
   }
 
-  if (base::FeatureList::IsEnabled(features::kLowPriorityIframes)) {
-    // Obtain the maximum effective connection type at which the feature is
-    // enabled.
-    std::string effective_connection_type_param =
-        base::GetFieldTrialParamValueByFeature(
-            features::kLowPriorityIframes,
-            "max_effective_connection_type_threshold");
-
-    base::Optional<net::EffectiveConnectionType> effective_connection_type =
-        net::GetEffectiveConnectionTypeForName(effective_connection_type_param);
-    if (effective_connection_type) {
-      web_prefs->low_priority_iframes_threshold =
-          effective_connection_type.value();
-    }
-  }
-
   web_prefs->lazy_load_enabled = !contents || !contents->GetDelegate() ||
                                  contents->GetDelegate()->ShouldAllowLazyLoad();
 
diff --git a/chrome/browser/chromeos/accessibility/accessibility_manager.cc b/chrome/browser/chromeos/accessibility/accessibility_manager.cc
index b2be0a10..79adcbd6 100644
--- a/chrome/browser/chromeos/accessibility/accessibility_manager.cc
+++ b/chrome/browser/chromeos/accessibility/accessibility_manager.cc
@@ -47,6 +47,7 @@
 #include "chrome/browser/extensions/api/braille_display_private/stub_braille_controller.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
 #include "chrome/browser/ui/singleton_tabs.h"
 #include "chrome/common/chrome_paths.h"
@@ -1436,6 +1437,12 @@
 
 void AccessibilityManager::PostLoadSwitchAccess() {
   InitializeFocusRings(extension_misc::kSwitchAccessExtensionId);
+
+  was_vk_enabled_before_switch_access_ =
+      ChromeKeyboardControllerClient::Get()->IsEnableFlagSet(
+          keyboard::KeyboardEnableFlag::kExtensionEnabled);
+  ChromeKeyboardControllerClient::Get()->SetEnableFlag(
+      keyboard::KeyboardEnableFlag::kExtensionEnabled);
 }
 
 void AccessibilityManager::PostUnloadSwitchAccess() {
@@ -1444,6 +1451,13 @@
 
   // Clear the accessibility focus ring.
   RemoveFocusRings(extension_misc::kSwitchAccessExtensionId);
+
+  if (!was_vk_enabled_before_switch_access_) {
+    ChromeKeyboardControllerClient::Get()->ClearEnableFlag(
+        keyboard::KeyboardEnableFlag::kExtensionEnabled);
+  } else {
+    was_vk_enabled_before_switch_access_ = false;
+  }
 }
 
 void AccessibilityManager::PostLoadAccessibilityCommon() {
diff --git a/chrome/browser/chromeos/accessibility/accessibility_manager.h b/chrome/browser/chromeos/accessibility/accessibility_manager.h
index 5772f19f..45432f3 100644
--- a/chrome/browser/chromeos/accessibility/accessibility_manager.h
+++ b/chrome/browser/chromeos/accessibility/accessibility_manager.h
@@ -489,10 +489,14 @@
   // Used to set the audio focus enforcement type for ChromeVox.
   mojo::Remote<media_session::mojom::AudioFocusManager> audio_focus_manager_;
 
+  // Whether the virtual keyboard was enabled before Switch Access loaded.
+  bool was_vk_enabled_before_switch_access_ = false;
+
   base::WeakPtrFactory<AccessibilityManager> weak_ptr_factory_{this};
 
   friend class DictationTest;
   friend class SwitchAccessTest;
+
   DISALLOW_COPY_AND_ASSIGN(AccessibilityManager);
 };
 
diff --git a/chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_bridge.cc b/chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_bridge.cc
index 771a0cf1..68c52b28 100644
--- a/chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_bridge.cc
+++ b/chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_bridge.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_bridge.h"
 
 #include <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.h>
 #include <bluetooth/rfcomm.h>
 #include <fcntl.h>
 #include <stddef.h>
@@ -1916,8 +1917,8 @@
   }
 }
 
-void ArcBluetoothBridge::OpenBluetoothSocket(
-    OpenBluetoothSocketCallback callback) {
+void ArcBluetoothBridge::OpenBluetoothSocketDeprecated(
+    OpenBluetoothSocketDeprecatedCallback callback) {
   base::ScopedFD sock(socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM));
   if (!sock.is_valid()) {
     LOG(ERROR) << "Failed to open socket.";
@@ -2930,17 +2931,79 @@
 
 namespace {
 
-constexpr int kMinRfcommChannelNum = 1;
-constexpr int kMaxRfcommChannelNum = 30;
+constexpr int kAutoSockPort = 0;
+constexpr int kMinRfcommChannel = 1;
+constexpr int kMaxRfcommChannel = 30;
 
-base::ScopedFD OpenRfcommSocket(int32_t optval) {
-  base::ScopedFD sock(socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM));
+// Copied from the values of L2CAP_PSM_LE_DYN_START and L2CAP_PSM_LE_DYN_END
+// in /include/net/bluetooth/l2cap.h
+constexpr int kMinL2capLePsm = 0x0080;
+constexpr int kMaxL2capLePsm = 0x00FF;
+
+union BluetoothSocketAddress {
+  sockaddr sock;
+  sockaddr_rc rfcomm;
+  sockaddr_l2 l2cap;
+};
+
+bool IsValidPort(mojom::BluetoothSocketType sock_type, int port) {
+  switch (sock_type) {
+    case mojom::BluetoothSocketType::TYPE_RFCOMM:
+      return port <= kMaxRfcommChannel && port >= kMinRfcommChannel;
+    case mojom::BluetoothSocketType::TYPE_L2CAP_LE:
+      return port <= kMaxL2capLePsm && port >= kMinL2capLePsm;
+  }
+}
+
+int32_t GetSockOptvalFromFlags(mojom::BluetoothSocketType sock_type,
+                               mojom::BluetoothSocketFlagsPtr sock_flags) {
+  int optval = 0;
+  switch (sock_type) {
+    case mojom::BluetoothSocketType::TYPE_RFCOMM:
+      optval |= sock_flags->encrypt ? RFCOMM_LM_ENCRYPT : 0;
+      optval |= sock_flags->auth ? RFCOMM_LM_AUTH : 0;
+      optval |= sock_flags->auth_mitm ? RFCOMM_LM_SECURE : 0;
+      optval |= sock_flags->auth_16_digit ? RFCOMM_LM_SECURE : 0;
+      return optval;
+    case mojom::BluetoothSocketType::TYPE_L2CAP_LE:
+      optval |= sock_flags->encrypt ? L2CAP_LM_ENCRYPT : 0;
+      optval |= sock_flags->auth ? L2CAP_LM_AUTH : 0;
+      optval |= sock_flags->auth_mitm ? L2CAP_LM_SECURE : 0;
+      optval |= sock_flags->auth_16_digit ? L2CAP_LM_SECURE : 0;
+      return optval;
+  }
+}
+
+// Opens an AF_BLUETOOTH socket with |sock_type|, sets L2CAP_LM or RFCOMM_LM
+// with |optval|, and binds the socket to address with |port|.
+base::ScopedFD OpenBluetoothSocketImpl(mojom::BluetoothSocketType sock_type,
+                                       int32_t optval,
+                                       uint16_t port) {
+  int protocol;
+  int level;
+  int optname;
+  switch (sock_type) {
+    case mojom::BluetoothSocketType::TYPE_RFCOMM:
+      protocol = BTPROTO_RFCOMM;
+      level = SOL_RFCOMM;
+      optname = RFCOMM_LM;
+      break;
+    case mojom::BluetoothSocketType::TYPE_L2CAP_LE:
+      protocol = BTPROTO_L2CAP;
+      level = SOL_L2CAP;
+      optname = L2CAP_LM;
+      break;
+    default:
+      LOG(ERROR) << "Unknown socket type " << sock_type;
+      return {};
+  }
+
+  base::ScopedFD sock(socket(AF_BLUETOOTH, SOCK_STREAM, protocol));
   if (!sock.is_valid()) {
     PLOG(ERROR) << "Failed to open bluetooth socket.";
     return {};
   }
-  if (setsockopt(sock.get(), SOL_RFCOMM, RFCOMM_LM, &optval, sizeof(optval)) ==
-      -1) {
+  if (setsockopt(sock.get(), level, optname, &optval, sizeof(optval)) == -1) {
     PLOG(ERROR) << "Failed to setopt() on socket.";
     return {};
   }
@@ -2949,127 +3012,240 @@
     PLOG(ERROR) << "Failed to fcntl() on socket.";
     return {};
   }
+
+  BluetoothSocketAddress sa = {};
+  switch (sock_type) {
+    case mojom::BluetoothSocketType::TYPE_RFCOMM:
+      sa.rfcomm.rc_family = AF_BLUETOOTH;
+      sa.rfcomm.rc_channel = port;
+      break;
+    case mojom::BluetoothSocketType::TYPE_L2CAP_LE:
+      sa.l2cap.l2_family = AF_BLUETOOTH;
+      sa.l2cap.l2_psm = htobs(port);
+      sa.l2cap.l2_bdaddr_type = BDADDR_LE_PUBLIC;
+      break;
+    default:
+      LOG(ERROR) << "Unknown socket type " << sock_type;
+      return {};
+  }
+
+  if (bind(sock.get(), &sa.sock, sizeof(sa)) == -1) {
+    PLOG(ERROR) << "Failed to bind()";
+    return {};
+  }
+
   return sock;
 }
 
 }  // namespace
 
-void ArcBluetoothBridge::RfcommListen(int32_t channel,
-                                      int32_t optval,
-                                      RfcommListenCallback callback) {
-  // |channel|=0 means selecting a available channel automatically.
-  if (channel != 0 &&
-      (channel < kMinRfcommChannelNum || channel > kMaxRfcommChannelNum)) {
+void ArcBluetoothBridge::RfcommListenDeprecated(
+    int32_t channel,
+    int32_t optval,
+    RfcommListenDeprecatedCallback callback) {
+  if (channel != kAutoSockPort &&
+      !IsValidPort(mojom::BluetoothSocketType::TYPE_RFCOMM, channel)) {
     LOG(ERROR) << "Invalid channel number";
     std::move(callback).Run(
         mojom::BluetoothStatus::FAIL, /*channel=*/0,
         mojo::PendingReceiver<mojom::RfcommListeningSocketClient>());
     return;
   }
-  uint8_t listen_channel = static_cast<uint8_t>(channel);
-  auto sock_wrapper = RfcommCreateListenSocket(optval, &listen_channel);
+
+  uint16_t listen_channel = static_cast<uint16_t>(channel);
+  auto sock_wrapper = CreateBluetoothListenSocket(
+      mojom::BluetoothSocketType::TYPE_RFCOMM, optval, &listen_channel);
   if (!sock_wrapper) {
     std::move(callback).Run(
         mojom::BluetoothStatus::FAIL, /*channel=*/0,
         mojo::PendingReceiver<mojom::RfcommListeningSocketClient>());
     return;
   }
-  std::move(callback).Run(mojom::BluetoothStatus::SUCCESS, listen_channel,
-                          sock_wrapper->remote.BindNewPipeAndPassReceiver());
 
-  sock_wrapper->remote.set_disconnect_handler(
-      base::BindOnce(&ArcBluetoothBridge::RfcommCloseListeningSocket,
+  sock_wrapper->created_by_deprecated_method = true;
+  std::move(callback).Run(
+      mojom::BluetoothStatus::SUCCESS, listen_channel,
+      sock_wrapper->deprecated_remote.BindNewPipeAndPassReceiver());
+
+  sock_wrapper->deprecated_remote.set_disconnect_handler(
+      base::BindOnce(&ArcBluetoothBridge::CloseBluetoothListeningSocket,
                      weak_factory_.GetWeakPtr(), sock_wrapper.get()));
   listening_sockets_.insert(std::move(sock_wrapper));
 }
 
-void ArcBluetoothBridge::RfcommCloseListeningSocket(
-    RfcommListeningSocket* ptr) {
+void ArcBluetoothBridge::BluetoothSocketListen(
+    mojom::BluetoothSocketType sock_type,
+    mojom::BluetoothSocketFlagsPtr sock_flags,
+    int32_t port,
+    BluetoothSocketListenCallback callback) {
+  if (!mojom::IsKnownEnumValue(sock_type)) {
+    LOG(ERROR) << "Unsupported sock type " << sock_type;
+    std::move(callback).Run(
+        mojom::BluetoothStatus::UNSUPPORTED, /*port=*/0,
+        mojo::PendingReceiver<mojom::BluetoothListenSocketClient>());
+    return;
+  }
+
+  if (port != kAutoSockPort && !IsValidPort(sock_type, port)) {
+    LOG(ERROR) << "Invalid port number " << port;
+    std::move(callback).Run(
+        mojom::BluetoothStatus::FAIL, /*port=*/0,
+        mojo::PendingReceiver<mojom::BluetoothListenSocketClient>());
+    return;
+  }
+
+  int32_t optval = GetSockOptvalFromFlags(sock_type, std::move(sock_flags));
+  uint16_t listen_port = static_cast<uint16_t>(port);
+  auto sock_wrapper =
+      CreateBluetoothListenSocket(sock_type, optval, &listen_port);
+  if (!sock_wrapper) {
+    std::move(callback).Run(
+        mojom::BluetoothStatus::FAIL, /*port=*/0,
+        mojo::PendingReceiver<mojom::BluetoothListenSocketClient>());
+    return;
+  }
+
+  std::move(callback).Run(mojom::BluetoothStatus::SUCCESS, listen_port,
+                          sock_wrapper->remote.BindNewPipeAndPassReceiver());
+  sock_wrapper->remote.set_disconnect_handler(
+      base::BindOnce(&ArcBluetoothBridge::CloseBluetoothListeningSocket,
+                     weak_factory_.GetWeakPtr(), sock_wrapper.get()));
+  listening_sockets_.insert(std::move(sock_wrapper));
+}
+
+void ArcBluetoothBridge::CloseBluetoothListeningSocket(
+    BluetoothListeningSocket* ptr) {
   auto itr = listening_sockets_.find(ptr);
   listening_sockets_.erase(itr);
 }
 
-void ArcBluetoothBridge::RfcommConnect(mojom::BluetoothAddressPtr remote_addr,
-                                       int32_t channel,
-                                       int32_t optval,
-                                       RfcommConnectCallback callback) {
-  if (channel < kMinRfcommChannelNum || channel > kMaxRfcommChannelNum) {
-    LOG(ERROR) << "Invalid channel number";
+void ArcBluetoothBridge::RfcommConnectDeprecated(
+    mojom::BluetoothAddressPtr remote_addr,
+    int32_t channel,
+    int32_t optval,
+    RfcommConnectDeprecatedCallback callback) {
+  if (!IsValidPort(mojom::BluetoothSocketType::TYPE_RFCOMM, channel)) {
+    LOG(ERROR) << "Invalid channel number " << channel;
     std::move(callback).Run(mojom::BluetoothStatus::FAIL,
                             mojom::RfcommConnectingSocketClientRequest());
     return;
   }
 
-  auto sock_wrapper = RfcommCreateConnectSocket(
-      std::move(remote_addr), static_cast<uint8_t>(channel), optval);
+  auto sock_wrapper = CreateBluetoothConnectSocket(
+      mojom::BluetoothSocketType::TYPE_RFCOMM, optval, std::move(remote_addr),
+      static_cast<uint16_t>(channel));
   if (!sock_wrapper) {
     std::move(callback).Run(mojom::BluetoothStatus::FAIL,
                             mojom::RfcommConnectingSocketClientRequest());
     return;
   }
 
+  sock_wrapper->created_by_deprecated_method = true;
+  std::move(callback).Run(
+      mojom::BluetoothStatus::SUCCESS,
+      sock_wrapper->deprecated_remote.BindNewPipeAndPassReceiver());
+  sock_wrapper->deprecated_remote.set_disconnect_handler(
+      base::BindOnce(&ArcBluetoothBridge::CloseBluetoothConnectingSocket,
+                     weak_factory_.GetWeakPtr(), sock_wrapper.get()));
+  connecting_sockets_.insert(std::move(sock_wrapper));
+}
+
+void ArcBluetoothBridge::BluetoothSocketConnect(
+    mojom::BluetoothSocketType sock_type,
+    mojom::BluetoothSocketFlagsPtr sock_flags,
+    mojom::BluetoothAddressPtr remote_addr,
+    int32_t port,
+    BluetoothSocketConnectCallback callback) {
+  if (!mojom::IsKnownEnumValue(sock_type)) {
+    LOG(ERROR) << "Unsupported sock type " << sock_type;
+    std::move(callback).Run(mojom::BluetoothStatus::UNSUPPORTED,
+                            mojom::BluetoothConnectSocketClientRequest());
+    return;
+  }
+
+  if (!IsValidPort(sock_type, port)) {
+    LOG(ERROR) << "Invalid port number " << port;
+    std::move(callback).Run(mojom::BluetoothStatus::FAIL,
+                            mojom::BluetoothConnectSocketClientRequest());
+    return;
+  }
+
+  int32_t optval = GetSockOptvalFromFlags(sock_type, std::move(sock_flags));
+  auto sock_wrapper = CreateBluetoothConnectSocket(
+      sock_type, optval, std::move(remote_addr), static_cast<uint16_t>(port));
+  if (!sock_wrapper) {
+    std::move(callback).Run(mojom::BluetoothStatus::FAIL,
+                            mojom::BluetoothConnectSocketClientRequest());
+    return;
+  }
+
   std::move(callback).Run(mojom::BluetoothStatus::SUCCESS,
                           sock_wrapper->remote.BindNewPipeAndPassReceiver());
   sock_wrapper->remote.set_disconnect_handler(
-      base::BindOnce(&ArcBluetoothBridge::RfcommCloseConnectingSocket,
+      base::BindOnce(&ArcBluetoothBridge::CloseBluetoothConnectingSocket,
                      weak_factory_.GetWeakPtr(), sock_wrapper.get()));
   connecting_sockets_.insert(std::move(sock_wrapper));
 }
 
-void ArcBluetoothBridge::RfcommCloseConnectingSocket(
-    RfcommConnectingSocket* ptr) {
+void ArcBluetoothBridge::CloseBluetoothConnectingSocket(
+    BluetoothConnectingSocket* ptr) {
   auto itr = connecting_sockets_.find(ptr);
   connecting_sockets_.erase(itr);
 }
 
-std::unique_ptr<ArcBluetoothBridge::RfcommListeningSocket>
-ArcBluetoothBridge::RfcommCreateListenSocket(int32_t optval, uint8_t* channel) {
-  base::ScopedFD sock = OpenRfcommSocket(optval);
+std::unique_ptr<ArcBluetoothBridge::BluetoothListeningSocket>
+ArcBluetoothBridge::CreateBluetoothListenSocket(
+    mojom::BluetoothSocketType sock_type,
+    int32_t optval,
+    uint16_t* port) {
+  DCHECK(port);
+  base::ScopedFD sock = OpenBluetoothSocketImpl(sock_type, optval, *port);
   if (!sock.is_valid()) {
     LOG(ERROR) << "Failed to open listen socket.";
     return nullptr;
   }
 
-  DCHECK(channel);
-  struct sockaddr_rc my_addr = {};
-  my_addr.rc_family = AF_BLUETOOTH;
-  my_addr.rc_channel = *channel;
-
-  if (bind(sock.get(), reinterpret_cast<const struct sockaddr*>(&my_addr),
-           sizeof(my_addr)) == -1) {
-    PLOG(ERROR) << "Failed to bind()";
-    return nullptr;
-  }
   if (listen(sock.get(), /*backlog=*/1) == -1) {
     PLOG(ERROR) << "Failed to listen()";
     return nullptr;
   }
 
-  socklen_t addr_len = sizeof(my_addr);
-  if (getsockname(sock.get(), reinterpret_cast<struct sockaddr*>(&my_addr),
-                  &addr_len) == -1) {
+  BluetoothSocketAddress local_addr;
+  socklen_t addr_len = sizeof(local_addr);
+  if (getsockname(sock.get(), &local_addr.sock, &addr_len) == -1) {
     PLOG(ERROR) << "Failed to getsockname()";
     return nullptr;
   }
 
-  auto sock_wrapper = std::make_unique<RfcommListeningSocket>();
+  auto sock_wrapper = std::make_unique<BluetoothListeningSocket>();
+  sock_wrapper->sock_type = sock_type;
   sock_wrapper->controller = base::FileDescriptorWatcher::WatchReadable(
       sock.get(),
-      base::BindRepeating(&ArcBluetoothBridge::OnRfcommListeningSocketReady,
+      base::BindRepeating(&ArcBluetoothBridge::OnBluetoothListeningSocketReady,
                           weak_factory_.GetWeakPtr(), sock_wrapper.get()));
   sock_wrapper->file = std::move(sock);
 
-  *channel = my_addr.rc_channel;
+  switch (sock_type) {
+    case mojom::BluetoothSocketType::TYPE_RFCOMM:
+      *port = local_addr.rfcomm.rc_channel;
+      break;
+    case mojom::BluetoothSocketType::TYPE_L2CAP_LE:
+      *port = btohs(local_addr.l2cap.l2_psm);
+      break;
+    default:
+      LOG(ERROR) << "Unknown socket type " << sock_type;
+      return nullptr;
+  }
+
   return sock_wrapper;
 }
 
-void ArcBluetoothBridge::OnRfcommListeningSocketReady(
-    ArcBluetoothBridge::RfcommListeningSocket* sock_wrapper) {
-  struct sockaddr_rc sa;
+void ArcBluetoothBridge::OnBluetoothListeningSocketReady(
+    ArcBluetoothBridge::BluetoothListeningSocket* sock_wrapper) {
+  BluetoothSocketAddress sa;
   socklen_t addr_len = sizeof(sa);
-  base::ScopedFD accept_fd(accept(sock_wrapper->file.get(),
-                                  reinterpret_cast<struct sockaddr*>(&sa),
-                                  &addr_len));
+  base::ScopedFD accept_fd(
+      accept(sock_wrapper->file.get(), &sa.sock, &addr_len));
   if (!accept_fd.is_valid()) {
     PLOG(ERROR) << "Failed to accept()";
     return;
@@ -3084,38 +3260,91 @@
       mojo::WrapPlatformHandle(mojo::PlatformHandle(std::move(accept_fd)));
 
   // Tells Android we successfully accept() a new connection.
-  auto connection = mojom::BluetoothRfcommConnection::New();
-  connection->sock = std::move(handle);
-  connection->addr = mojom::BluetoothAddress::From<bdaddr_t>(sa.rc_bdaddr);
-  connection->channel = sa.rc_channel;
-  sock_wrapper->remote->OnAccepted(std::move(connection));
+  if (sock_wrapper->created_by_deprecated_method) {
+    auto connection = mojom::BluetoothRfcommConnection::New();
+    connection->sock = std::move(handle);
+    connection->addr =
+        mojom::BluetoothAddress::From<bdaddr_t>(sa.rfcomm.rc_bdaddr);
+    connection->channel = sa.rfcomm.rc_channel;
+    sock_wrapper->deprecated_remote->OnAccepted(std::move(connection));
+  } else {
+    auto connection = mojom::BluetoothSocketConnection::New();
+    connection->sock = std::move(handle);
+    switch (sock_wrapper->sock_type) {
+      case mojom::BluetoothSocketType::TYPE_RFCOMM:
+        connection->addr =
+            mojom::BluetoothAddress::From<bdaddr_t>(sa.rfcomm.rc_bdaddr);
+        connection->port = sa.rfcomm.rc_channel;
+        break;
+      case mojom::BluetoothSocketType::TYPE_L2CAP_LE:
+        connection->addr =
+            mojom::BluetoothAddress::From<bdaddr_t>(sa.l2cap.l2_bdaddr);
+        connection->port = btohs(sa.l2cap.l2_psm);
+        break;
+      default:
+        LOG(ERROR) << "Unknown socket type " << sock_wrapper->sock_type;
+        return;
+    }
+    sock_wrapper->remote->OnAccepted(std::move(connection));
+  }
 }
 
-std::unique_ptr<ArcBluetoothBridge::RfcommConnectingSocket>
-ArcBluetoothBridge::RfcommCreateConnectSocket(mojom::BluetoothAddressPtr addr,
-                                              uint8_t channel,
-                                              int32_t optval) {
-  base::ScopedFD sock = OpenRfcommSocket(optval);
+std::unique_ptr<ArcBluetoothBridge::BluetoothConnectingSocket>
+ArcBluetoothBridge::CreateBluetoothConnectSocket(
+    mojom::BluetoothSocketType sock_type,
+    int32_t optval,
+    mojom::BluetoothAddressPtr addr,
+    uint16_t port) {
+  base::ScopedFD sock =
+      OpenBluetoothSocketImpl(sock_type, optval, kAutoSockPort);
   if (!sock.is_valid()) {
     LOG(ERROR) << "Failed to open connect socket.";
     return nullptr;
   }
 
-  struct sockaddr_rc sa = {};
-  sa.rc_family = AF_BLUETOOTH;
-  sa.rc_bdaddr = addr->To<bdaddr_t>();
-  sa.rc_channel = channel;
+  std::string addr_str = addr->To<std::string>();
+  BluetoothDevice* device = bluetooth_adapter_->GetDevice(addr_str);
+  if (!device)
+    return nullptr;
+
+  const auto addr_type = device->GetAddressType();
+  if (addr_type == BluetoothDevice::ADDR_TYPE_UNKNOWN) {
+    LOG(ERROR) << "Unknown address type.";
+    return nullptr;
+  }
+
+  BluetoothSocketAddress sa = {};
+  switch (sock_type) {
+    case mojom::BluetoothSocketType::TYPE_RFCOMM:
+      sa.rfcomm.rc_family = AF_BLUETOOTH;
+      sa.rfcomm.rc_bdaddr = addr->To<bdaddr_t>();
+      sa.rfcomm.rc_channel = static_cast<uint8_t>(port);
+      break;
+    case mojom::BluetoothSocketType::TYPE_L2CAP_LE:
+      sa.l2cap.l2_family = AF_BLUETOOTH;
+      sa.l2cap.l2_bdaddr = addr->To<bdaddr_t>();
+      sa.l2cap.l2_psm = htobs(port);
+      sa.l2cap.l2_bdaddr_type = addr_type == BluetoothDevice::ADDR_TYPE_PUBLIC
+                                    ? BDADDR_LE_PUBLIC
+                                    : BDADDR_LE_RANDOM;
+      break;
+    default:
+      LOG(ERROR) << "Unknown socket type " << sock_type;
+      return nullptr;
+  }
+
   int ret = HANDLE_EINTR(connect(
       sock.get(), reinterpret_cast<const struct sockaddr*>(&sa), sizeof(sa)));
 
   auto sock_wrapper =
-      std::make_unique<ArcBluetoothBridge::RfcommConnectingSocket>();
+      std::make_unique<ArcBluetoothBridge::BluetoothConnectingSocket>();
+  sock_wrapper->sock_type = sock_type;
   if (ret == 0) {
     // connect() returns success immediately.
     sock_wrapper->file = std::move(sock);
     base::ThreadPool::PostTask(
         FROM_HERE,
-        base::BindOnce(&ArcBluetoothBridge::OnRfcommConnectingSocketReady,
+        base::BindOnce(&ArcBluetoothBridge::OnBluetoothConnectingSocketReady,
                        weak_factory_.GetWeakPtr(), sock_wrapper.get()));
     return sock_wrapper;
   }
@@ -3126,14 +3355,14 @@
 
   sock_wrapper->controller = base::FileDescriptorWatcher::WatchWritable(
       sock.get(),
-      base::BindRepeating(&ArcBluetoothBridge::OnRfcommConnectingSocketReady,
+      base::BindRepeating(&ArcBluetoothBridge::OnBluetoothConnectingSocketReady,
                           weak_factory_.GetWeakPtr(), sock_wrapper.get()));
   sock_wrapper->file = std::move(sock);
   return sock_wrapper;
 }
 
-void ArcBluetoothBridge::OnRfcommConnectingSocketReady(
-    ArcBluetoothBridge::RfcommConnectingSocket* sock_wrapper) {
+void ArcBluetoothBridge::OnBluetoothConnectingSocketReady(
+    ArcBluetoothBridge::BluetoothConnectingSocket* sock_wrapper) {
   // When connect() is ready, we will transfer this fd to Android, and Android
   // is responsible for closing it.
   base::ScopedFD fd = std::move(sock_wrapper->file);
@@ -3143,27 +3372,35 @@
   socklen_t len = sizeof(err);
   int ret = getsockopt(fd.get(), SOL_SOCKET, SO_ERROR, &err, &len);
   if (ret != 0 || err != 0) {
-    PLOG(ERROR) << "Failed to connect. err=" << err;
-    sock_wrapper->remote->OnConnectFailed();
+    LOG(ERROR) << "Failed to connect. err=" << err;
+    if (sock_wrapper->created_by_deprecated_method)
+      sock_wrapper->deprecated_remote->OnConnectFailed();
+    else
+      sock_wrapper->remote->OnConnectFailed();
     return;
   }
 
   // Gets peer address.
-  struct sockaddr_rc sa;
-  socklen_t sa_len = sizeof(sa);
-  if (getpeername(fd.get(), reinterpret_cast<sockaddr*>(&sa), &sa_len) == -1) {
+  BluetoothSocketAddress peer_sa;
+  socklen_t peer_sa_len = sizeof(peer_sa);
+  if (getpeername(fd.get(), &peer_sa.sock, &peer_sa_len) == -1) {
     PLOG(ERROR) << "Failed to getpeername().";
-    sock_wrapper->remote->OnConnectFailed();
+    if (sock_wrapper->created_by_deprecated_method)
+      sock_wrapper->deprecated_remote->OnConnectFailed();
+    else
+      sock_wrapper->remote->OnConnectFailed();
     return;
   }
 
-  // Gets our channel.
-  struct sockaddr_rc our_sa;
-  socklen_t our_sa_len = sizeof(sa);
-  if (getsockname(fd.get(), reinterpret_cast<sockaddr*>(&our_sa),
-                  &our_sa_len) == -1) {
+  // Gets our port.
+  BluetoothSocketAddress local_sa;
+  socklen_t local_sa_len = sizeof(local_sa);
+  if (getsockname(fd.get(), &local_sa.sock, &local_sa_len) == -1) {
     PLOG(ERROR) << "Failed to getsockname()";
-    sock_wrapper->remote->OnConnectFailed();
+    if (sock_wrapper->created_by_deprecated_method)
+      sock_wrapper->deprecated_remote->OnConnectFailed();
+    else
+      sock_wrapper->remote->OnConnectFailed();
     return;
   }
 
@@ -3171,17 +3408,43 @@
       mojo::WrapPlatformHandle(mojo::PlatformHandle(std::move(fd)));
 
   // Notifies Android.
-  auto connection = mojom::BluetoothRfcommConnection::New();
-  connection->sock = std::move(handle);
-  connection->addr = mojom::BluetoothAddress::From<bdaddr_t>(sa.rc_bdaddr);
-  connection->channel = our_sa.rc_channel;
-  sock_wrapper->remote->OnConnected(std::move(connection));
+  if (sock_wrapper->created_by_deprecated_method) {
+    auto connection = mojom::BluetoothRfcommConnection::New();
+    connection->sock = std::move(handle);
+    connection->addr =
+        mojom::BluetoothAddress::From<bdaddr_t>(peer_sa.rfcomm.rc_bdaddr);
+    connection->channel = local_sa.rfcomm.rc_channel;
+    sock_wrapper->deprecated_remote->OnConnected(std::move(connection));
+  } else {
+    auto connection = mojom::BluetoothSocketConnection::New();
+    connection->sock = std::move(handle);
+    switch (sock_wrapper->sock_type) {
+      case mojom::BluetoothSocketType::TYPE_RFCOMM:
+        connection->addr =
+            mojom::BluetoothAddress::From<bdaddr_t>(peer_sa.rfcomm.rc_bdaddr);
+        connection->port = local_sa.rfcomm.rc_channel;
+        break;
+      case mojom::BluetoothSocketType::TYPE_L2CAP_LE:
+        connection->addr =
+            mojom::BluetoothAddress::From<bdaddr_t>(peer_sa.l2cap.l2_bdaddr);
+        connection->port = btohs(local_sa.l2cap.l2_psm);
+        break;
+      default:
+        LOG(ERROR) << "Unknown socket type " << sock_wrapper->sock_type;
+        return;
+    }
+    sock_wrapper->remote->OnConnected(std::move(connection));
+  }
 }
 
-ArcBluetoothBridge::RfcommListeningSocket::RfcommListeningSocket() = default;
-ArcBluetoothBridge::RfcommListeningSocket::~RfcommListeningSocket() = default;
-ArcBluetoothBridge::RfcommConnectingSocket::RfcommConnectingSocket() = default;
-ArcBluetoothBridge::RfcommConnectingSocket::~RfcommConnectingSocket() = default;
+ArcBluetoothBridge::BluetoothListeningSocket::BluetoothListeningSocket() =
+    default;
+ArcBluetoothBridge::BluetoothListeningSocket::~BluetoothListeningSocket() =
+    default;
+ArcBluetoothBridge::BluetoothConnectingSocket::BluetoothConnectingSocket() =
+    default;
+ArcBluetoothBridge::BluetoothConnectingSocket::~BluetoothConnectingSocket() =
+    default;
 
 ArcBluetoothBridge::BluetoothArcConnectionObserver::
     BluetoothArcConnectionObserver(ArcBluetoothBridge* arc_bluetooth_bridge)
diff --git a/chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_bridge.h b/chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_bridge.h
index f7cf99d7..ce80fec 100644
--- a/chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_bridge.h
+++ b/chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_bridge.h
@@ -267,7 +267,8 @@
   void ReadRemoteRssi(mojom::BluetoothAddressPtr remote_addr,
                       ReadRemoteRssiCallback callback) override;
 
-  void OpenBluetoothSocket(OpenBluetoothSocketCallback callback) override;
+  void OpenBluetoothSocketDeprecated(
+      OpenBluetoothSocketDeprecatedCallback callback) override;
 
   // Bluetooth Mojo host interface - Bluetooth Gatt Server functions
   // Android counterpart link:
@@ -316,13 +317,25 @@
                        RemoveSdpRecordCallback callback) override;
 
   // Bluetooth Mojo host interface - Bluetooth RFCOMM functions
-  void RfcommListen(int32_t channel,
-                    int32_t optval,
-                    RfcommListenCallback callback) override;
-  void RfcommConnect(mojom::BluetoothAddressPtr remote_addr,
-                     int32_t channel,
-                     int32_t optval,
-                     RfcommConnectCallback callback) override;
+  void RfcommListenDeprecated(int32_t channel,
+                              int32_t optval,
+                              RfcommListenDeprecatedCallback callback) override;
+  void RfcommConnectDeprecated(
+      mojom::BluetoothAddressPtr remote_addr,
+      int32_t channel,
+      int32_t optval,
+      RfcommConnectDeprecatedCallback callback) override;
+
+  // Bluetooth Mojo host interface - Bluetooth socket functions
+  void BluetoothSocketListen(mojom::BluetoothSocketType sock_type,
+                             mojom::BluetoothSocketFlagsPtr sock_flags,
+                             int32_t port,
+                             BluetoothSocketListenCallback callback) override;
+  void BluetoothSocketConnect(mojom::BluetoothSocketType sock_type,
+                              mojom::BluetoothSocketFlagsPtr sock_flags,
+                              mojom::BluetoothAddressPtr remote_addr,
+                              int32_t port,
+                              BluetoothSocketConnectCallback callback) override;
 
   // Set up or disable multiple advertising.
   void ReserveAdvertisementHandle(
@@ -548,48 +561,60 @@
   // DevicePairedChange() but not in this function.
   void TrackPairingState(const device::BluetoothDevice* device);
 
-  // Data structures for RFCOMM listening/connecting sockets that live in
+  // Data structures for Bluetooth listening/connecting sockets that live in
   // Chrome.
-  struct RfcommListeningSocket {
-    mojo::Remote<mojom::RfcommListeningSocketClient> remote;
+  struct BluetoothListeningSocket {
+    mojom::BluetoothSocketType sock_type;
+    // TODO(b/163099156): Remove the following two fields when
+    // RfcommListenDeprecated()/RfcommConnectDeprecated() are removed.
+    bool created_by_deprecated_method = false;
+    mojo::Remote<mojom::RfcommListeningSocketClient> deprecated_remote;
+    mojo::Remote<mojom::BluetoothListenSocketClient> remote;
     base::ScopedFD file;
     std::unique_ptr<base::FileDescriptorWatcher::Controller> controller;
-    RfcommListeningSocket();
-    ~RfcommListeningSocket();
+    BluetoothListeningSocket();
+    ~BluetoothListeningSocket();
   };
-  struct RfcommConnectingSocket {
-    mojo::Remote<mojom::RfcommConnectingSocketClient> remote;
+  struct BluetoothConnectingSocket {
+    mojom::BluetoothSocketType sock_type;
+    // TODO(b/163099156): Remove the following two fields when
+    // RfcommListenDeprecated()/RfcommConnectDeprecated() are removed.
+    bool created_by_deprecated_method = false;
+    mojo::Remote<mojom::RfcommConnectingSocketClient> deprecated_remote;
+    mojo::Remote<mojom::BluetoothConnectSocketClient> remote;
     base::ScopedFD file;
     std::unique_ptr<base::FileDescriptorWatcher::Controller> controller;
-    RfcommConnectingSocket();
-    ~RfcommConnectingSocket();
+    BluetoothConnectingSocket();
+    ~BluetoothConnectingSocket();
   };
 
-  // Creates a bluetooth socket with socket option |optval|, and then bind()
-  // and listen() with requested RFCOMM |channel| number. The actual channel
-  // number will be filled in |channel| as the return value. Returns a
-  // RfcommListeningSocket that holds the socket.
-  std::unique_ptr<RfcommListeningSocket> RfcommCreateListenSocket(
+  // Creates a Bluetooth socket with socket option |optval|, and then bind() and
+  // listen() with requested |port| number. The actual port number will be
+  // filled in |port| as the return value. Returns a BluetoothListeningSocket
+  // that holds the socket.
+  std::unique_ptr<BluetoothListeningSocket> CreateBluetoothListenSocket(
+      mojom::BluetoothSocketType type,
       int32_t optval,
-      uint8_t* channel);
-  // Creates a bluetooth socket with socket option |optval|, and then calls
-  // connect() to (|addr|, |channel|). This connect() call is non-blocking.
-  // Returns a RfcommConnectingSocket that holds the socket.
-  std::unique_ptr<RfcommConnectingSocket> RfcommCreateConnectSocket(
+      uint16_t* port);
+  // Creates a Bluetooth socket with socket option |optval|, and then calls
+  // connect() to (|addr|, |port|). This connect() call is non-blocking.
+  // Returns a BluetoothConnectingSocket that holds the socket.
+  std::unique_ptr<BluetoothConnectingSocket> CreateBluetoothConnectSocket(
+      mojom::BluetoothSocketType type,
+      int32_t optval,
       mojom::BluetoothAddressPtr addr,
-      uint8_t channel,
-      int32_t optval);
+      uint16_t port);
 
-  // Closes RFCOMM sockets. Releases the corresponding resources.
-  void RfcommCloseListeningSocket(RfcommListeningSocket* socket);
-  void RfcommCloseConnectingSocket(RfcommConnectingSocket* socket);
+  // Closes Bluetooth sockets. Releases the corresponding resources.
+  void CloseBluetoothListeningSocket(BluetoothListeningSocket* socket);
+  void CloseBluetoothConnectingSocket(BluetoothConnectingSocket* socket);
 
   // Called when the listening socket is ready to accept().
-  void OnRfcommListeningSocketReady(
-      ArcBluetoothBridge::RfcommListeningSocket* socket);
+  void OnBluetoothListeningSocketReady(
+      ArcBluetoothBridge::BluetoothListeningSocket* socket);
   // Called when the connecting socket is ready.
-  void OnRfcommConnectingSocketReady(
-      ArcBluetoothBridge::RfcommConnectingSocket* socket);
+  void OnBluetoothConnectingSocketReady(
+      ArcBluetoothBridge::BluetoothConnectingSocket* socket);
 
   ArcBridgeService* const arc_bridge_service_;  // Owned by ArcServiceManager.
 
@@ -699,10 +724,11 @@
   // Start/StopLEScan().
   ArcBluetoothTaskQueue discovery_queue_;
 
-  // Rfcomm sockets that live in Chrome.
-  std::set<std::unique_ptr<RfcommListeningSocket>, base::UniquePtrComparator>
+  // Bluetooth sockets that live in Chrome.
+  std::set<std::unique_ptr<BluetoothListeningSocket>, base::UniquePtrComparator>
       listening_sockets_;
-  std::set<std::unique_ptr<RfcommConnectingSocket>, base::UniquePtrComparator>
+  std::set<std::unique_ptr<BluetoothConnectingSocket>,
+           base::UniquePtrComparator>
       connecting_sockets_;
 
   // Observes the ARC connection to Bluetooth service in Android. We need to do
diff --git a/chrome/browser/chromeos/arc/tracing/arc_app_performance_tracing.cc b/chrome/browser/chromeos/arc/tracing/arc_app_performance_tracing.cc
index 4a12721..d5f4a3d 100644
--- a/chrome/browser/chromeos/arc/tracing/arc_app_performance_tracing.cc
+++ b/chrome/browser/chromeos/arc/tracing/arc_app_performance_tracing.cc
@@ -5,15 +5,19 @@
 #include "chrome/browser/chromeos/arc/tracing/arc_app_performance_tracing.h"
 
 #include "base/memory/singleton.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/no_destructor.h"
 #include "base/strings/string_util.h"
+#include "base/timer/timer.h"
 #include "chrome/browser/chromeos/arc/tracing/arc_app_performance_tracing_custom_session.h"
 #include "chrome/browser/chromeos/arc/tracing/arc_app_performance_tracing_session.h"
 #include "chrome/browser/chromeos/arc/tracing/arc_app_performance_tracing_uma_session.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_list_prefs_factory.h"
 #include "components/arc/arc_browser_context_keyed_service_factory_base.h"
 #include "components/arc/arc_features.h"
+#include "components/arc/arc_service_manager.h"
 #include "components/arc/arc_util.h"
+#include "components/arc/session/arc_bridge_service.h"
 #include "components/exo/wm_helper.h"
 #include "ui/aura/window.h"
 
@@ -21,6 +25,13 @@
 
 namespace {
 
+// Tracing delay for jankinees.
+constexpr base::TimeDelta kJankinessTracingTime =
+    base::TimeDelta::FromMinutes(5);
+
+// Minimum number of frames for a jankiness tracing result to be valid.
+constexpr int kMinTotalFramesJankiness = 1000;
+
 // Singleton factory for ArcAppPerformanceTracing.
 class ArcAppPerformanceTracingFactory
     : public internal::ArcBrowserContextKeyedServiceFactoryBase<
@@ -124,6 +135,8 @@
 }
 
 void ArcAppPerformanceTracing::Shutdown() {
+  CancelJankinessTracing();
+
   MaybeStopTracing();
 
   // |session_|. Make sure that |arc_active_window_| is detached.
@@ -160,6 +173,9 @@
   // Discard any active tracing if any.
   MaybeStopTracing();
 
+  // Stop and report previous active window's jankiness tracing so far.
+  FinalizeJankinessTracing(true /* stopped_early */);
+
   // Detach previous active window if it is set.
   DetachActiveWindow();
 
@@ -170,6 +186,8 @@
   // Observe active ARC++ window.
   AttachActiveWindow(gained_active);
 
+  StartJankinessTracing();
+
   MaybeStartTracing();
 }
 
@@ -177,6 +195,8 @@
   // ARC++ window will be destroyed.
   DCHECK_EQ(arc_active_window_, window);
 
+  CancelJankinessTracing();
+
   MaybeStopTracing();
 
   DetachActiveWindow();
@@ -187,7 +207,7 @@
                                              const std::string& activity,
                                              const std::string& intent) {
   const std::string app_id = ArcAppListPrefs::GetAppId(package_name, activity);
-  task_id_to_app_id_[task_id] = app_id;
+  task_id_to_app_id_[task_id] = std::make_pair(app_id, package_name);
   MaybeStartTracing();
 }
 
@@ -195,6 +215,109 @@
   task_id_to_app_id_.erase(task_id);
 }
 
+void ArcAppPerformanceTracing::StartJankinessTracing() {
+  DCHECK(!jankiness_timer_.IsRunning());
+  jankiness_timer_.Start(
+      FROM_HERE, kJankinessTracingTime,
+      base::BindOnce(&ArcAppPerformanceTracing::FinalizeJankinessTracing,
+                     base::Unretained(this), false /* stopped_early */));
+}
+
+void ArcAppPerformanceTracing::CancelJankinessTracing() {
+  jankiness_timer_.Stop();
+}
+
+void ArcAppPerformanceTracing::FinalizeJankinessTracing(bool stopped_early) {
+  // Never started. Nothing to do.
+  if (!jankiness_timer_.IsRunning() && stopped_early)
+    return;
+
+  jankiness_timer_.Stop();
+
+  // Check if we have all conditions met, ARC++ window is active and information
+  // is available for associated task.
+  if (!arc_active_window_)
+    return;
+
+  const int32_t task_id = arc::GetWindowTaskId(arc_active_window_);
+  DCHECK_GT(task_id, 0);
+
+  const auto it = task_id_to_app_id_.find(task_id);
+  if (it == task_id_to_app_id_.end())
+    // It is normal that information might not be available at this time.
+    return;
+
+  // Test instances might not have Service Manager running.
+  auto* arc_service_manager = ArcServiceManager::Get();
+  if (!arc_service_manager)
+    return;
+
+  auto* instance = ARC_GET_INSTANCE_FOR_METHOD(
+      arc_service_manager->arc_bridge_service()->metrics(), GetGfxMetrics);
+  if (!instance)
+    return;
+
+  const std::string package_name = it->second.second;
+  auto callback = base::BindOnce(&ArcAppPerformanceTracing::OnGfxMetrics,
+                                 base::Unretained(this), package_name);
+  instance->GetGfxMetrics(package_name, std::move(callback));
+
+  // Finalized normally, safe to restart.
+  if (!stopped_early)
+    StartJankinessTracing();
+}
+
+void ArcAppPerformanceTracing::OnGfxMetrics(const std::string& package_name,
+                                            mojom::GfxMetricsPtr metrics) {
+  if (!metrics) {
+    LOG(ERROR) << "Failed to resolve GFX metrics";
+    return;
+  }
+
+  uint64_t framesTotal = metrics->framesTotal;
+  uint64_t framesJanky = metrics->framesJanky;
+  const uint32_t frameTime95 = metrics->frameTimePercentile95;  // in ms.
+
+  const auto it = package_name_to_gfx_metrics_.find(package_name);
+  const bool first_time = it == package_name_to_gfx_metrics_.end();
+
+  // Cached data exists and not outdated. Calculate delta.
+  if (!first_time && it->second.framesTotal <= framesTotal) {
+    framesTotal -= it->second.framesTotal;
+    framesJanky -= it->second.framesJanky;
+  }
+
+  // Update cache.
+  package_name_to_gfx_metrics_[package_name] = *metrics;
+
+  // Not enough data.
+  if (framesTotal < kMinTotalFramesJankiness) {
+    VLOG(1) << "Not enough GFX metrics data collected to report.";
+    return;
+  }
+
+  // We can only calculate real numbers for initial data. Only report if first
+  // time.
+  if (first_time) {
+    const base::TimeDelta frameTime =
+        base::TimeDelta::FromMilliseconds(frameTime95);
+    base::UmaHistogramTimes("Arc.Runtime.Performance.Generic.FrameTime",
+                            frameTime);
+    VLOG(1) << "Total Frames: " << framesTotal << " | "
+            << "Janky Frames: " << framesJanky << " | "
+            << "95 Percentile Frame Time: " << frameTime.InMilliseconds()
+            << "ms";
+  } else {
+    VLOG(1) << "Total Frames: " << framesTotal << " | "
+            << "Janky Frames: " << framesJanky;
+  }
+
+  const int jankiness = (framesJanky * 100) / framesTotal;
+
+  base::UmaHistogramPercentage("Arc.Runtime.Performance.Generic.Jankiness",
+                               jankiness);
+}
+
 bool ArcAppPerformanceTracing::WasReported(const std::string& category) const {
   DCHECK(!category.empty());
   return reported_categories_.count(category);
@@ -226,8 +349,8 @@
     return;
   }
 
-  const std::string& category =
-      AppToCategoryMapper::GetInstance().GetCategory(it->second /* app_id */);
+  const std::string& category = AppToCategoryMapper::GetInstance().GetCategory(
+      it->second.first /* app_id */);
 
   if (category.empty()) {
     // App is not recognized as app for tracing, ignore it.
diff --git a/chrome/browser/chromeos/arc/tracing/arc_app_performance_tracing.h b/chrome/browser/chromeos/arc/tracing/arc_app_performance_tracing.h
index 687768ea..ec6706c 100644
--- a/chrome/browser/chromeos/arc/tracing/arc_app_performance_tracing.h
+++ b/chrome/browser/chromeos/arc/tracing/arc_app_performance_tracing.h
@@ -15,6 +15,7 @@
 #include "base/callback.h"
 #include "base/macros.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
+#include "components/arc/mojom/metrics.mojom.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "ui/aura/window_observer.h"
 #include "ui/wm/public/activation_change_observer.h"
@@ -33,7 +34,7 @@
 class ArcBridgeService;
 
 // Service that monitors ARC++ apps, measures and reports performance metrics
-// for the set of predefined apps.
+// for the set of predefined apps. Also report GFX metrics jankiness results.
 class ArcAppPerformanceTracing : public KeyedService,
                                  public wm::ActivationChangeObserver,
                                  public aura::WindowObserver,
@@ -121,13 +122,35 @@
   // |arc_active_window_|.
   void DetachActiveWindow();
 
+  // Starts timer for jankiness tracing. Called by OnWindowActivation() and
+  // FinalizeJankinessTracing().
+  void StartJankinessTracing();
+
+  // Cancels jankiness tracing without reporting partial results.
+  void CancelJankinessTracing();
+
+  // Retrieves and reports jankiness metrics and restarts timer. May be called
+  // early by OnWindowActivation() and OnWindowDestroying().
+  // In this case, |stopped_early| is set to true.
+  void FinalizeJankinessTracing(bool stopped_early);
+
+  // Callback for jankiness results. Reports results to UMA.
+  // Note: Results are cumulative. Uses task_id_to_gfx_metrics_
+  // values for delta calculations.
+  void OnGfxMetrics(const std::string& package_name,
+                    mojom::GfxMetricsPtr metrics_ptr);
+
   // Unowned pointers.
   content::BrowserContext* const context_;
   // Currently active ARC++ app window.
   aura::Window* arc_active_window_ = nullptr;
 
-  // Maps active tasks to app id.
-  std::map<int, std::string> task_id_to_app_id_;
+  // Maps active tasks to app id and package name.
+  std::map<int, std::pair<std::string, std::string>> task_id_to_app_id_;
+
+  // Maps tasks to most recent GFX jankiness results. Used for delta
+  // calculation.
+  std::map<std::string, mojom::GfxMetrics> package_name_to_gfx_metrics_;
 
   // Set of already reported ARC++ apps for the current session. Used to prevent
   // capturing too frequently.
@@ -139,6 +162,9 @@
   // Callback to call when custom session is ready for testing.
   CustomSessionReadyCallback custom_session_ready_callback_;
 
+  // Timer for jankiness tracing.
+  base::OneShotTimer jankiness_timer_;
+
   DISALLOW_COPY_AND_ASSIGN(ArcAppPerformanceTracing);
 };
 
diff --git a/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc b/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
index d8fb996..869e69b4 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
@@ -103,6 +103,11 @@
     return *this;
   }
 
+  TestCase& EnableSinglePartitionFormat() {
+    options.single_partition_format = true;
+    return *this;
+  }
+
   // Show the startup browser. Some tests invoke the file picker dialog during
   // the test. Requesting a file picker from a background page is forbidden by
   // the apps platform, and it's a bug that these tests do so.
@@ -149,6 +154,9 @@
     if (options.zip_no_nacl)
       full_name += "_ZipNoNaCl";
 
+    if (options.single_partition_format)
+      full_name += "_SinglePartitionFormat";
+
     return full_name;
   }
 
@@ -272,6 +280,7 @@
         TestCase("fileDisplayMtp"),
         TestCase("fileDisplayUsb"),
         TestCase("fileDisplayUsbPartition"),
+        TestCase("fileDisplayUsbPartition").EnableSinglePartitionFormat(),
         TestCase("fileDisplayUsbPartitionSort"),
         TestCase("fileDisplayPartitionFileTable"),
         TestCase("fileSearch"),
@@ -613,9 +622,11 @@
         TestCase("dirContextMenuCrostini"),
         TestCase("dirContextMenuPlayFiles"),
         TestCase("dirContextMenuUsbs"),
+        TestCase("dirContextMenuUsbs").EnableSinglePartitionFormat(),
         TestCase("dirContextMenuFsp"),
         TestCase("dirContextMenuDocumentsProvider").EnableDocumentsProvider(),
         TestCase("dirContextMenuUsbDcim"),
+        TestCase("dirContextMenuUsbDcim").EnableSinglePartitionFormat(),
         TestCase("dirContextMenuMtp"),
         TestCase("dirContextMenuMediaView").EnableArc(),
         TestCase("dirContextMenuMyDrive"),
@@ -629,24 +640,26 @@
 WRAPPED_INSTANTIATE_TEST_SUITE_P(
     DriveSpecific, /* drive_specific.js */
     FilesAppBrowserTest,
-    ::testing::Values(TestCase("driveOpenSidebarOffline"),
-                      TestCase("driveOpenSidebarSharedWithMe"),
-                      TestCase("driveAutoCompleteQuery"),
-                      TestCase("drivePinMultiple"),
-                      TestCase("drivePinHosted"),
-                      TestCase("drivePinFileMobileNetwork"),
-                      TestCase("driveClickFirstSearchResult"),
-                      TestCase("drivePressEnterToSearch"),
-                      TestCase("drivePressClearSearch"),
-                      TestCase("drivePressCtrlAFromSearch"),
-                      TestCase("driveBackupPhotos"),
-                      TestCase("driveAvailableOfflineGearMenu"),
-                      TestCase("driveAvailableOfflineDirectoryGearMenu"),
-                      TestCase("driveAvailableOfflineActionBar"),
-                      TestCase("driveLinkToDirectory"),
-                      TestCase("driveLinkOpenFileThroughLinkedDirectory"),
-                      TestCase("driveLinkOpenFileThroughTransitiveLink"),
-                      TestCase("driveWelcomeBanner")));
+    ::testing::Values(
+        TestCase("driveOpenSidebarOffline"),
+        TestCase("driveOpenSidebarSharedWithMe"),
+        TestCase("driveAutoCompleteQuery"),
+        TestCase("drivePinMultiple"),
+        TestCase("drivePinHosted"),
+        TestCase("drivePinFileMobileNetwork"),
+        TestCase("driveClickFirstSearchResult"),
+        TestCase("drivePressEnterToSearch"),
+        TestCase("drivePressClearSearch"),
+        TestCase("drivePressCtrlAFromSearch"),
+        TestCase("driveBackupPhotos"),
+        TestCase("driveBackupPhotos").EnableSinglePartitionFormat(),
+        TestCase("driveAvailableOfflineGearMenu"),
+        TestCase("driveAvailableOfflineDirectoryGearMenu"),
+        TestCase("driveAvailableOfflineActionBar"),
+        TestCase("driveLinkToDirectory"),
+        TestCase("driveLinkOpenFileThroughLinkedDirectory"),
+        TestCase("driveLinkOpenFileThroughTransitiveLink"),
+        TestCase("driveWelcomeBanner")));
 
 WRAPPED_INSTANTIATE_TEST_SUITE_P(
     Transfer, /* transfer.js */
@@ -674,6 +687,8 @@
         TestCase("transferDragDropTreeItemDenies").FilesNg(),
         TestCase("transferDragAndHoverTreeItemEntryList"),
         TestCase("transferDragAndHoverTreeItemFakeEntry"),
+        TestCase("transferDragAndHoverTreeItemFakeEntry")
+            .EnableSinglePartitionFormat(),
         TestCase("transferDragFileListItemSelects"),
         TestCase("transferDragAndDrop"),
         TestCase("transferDragAndHover"),
@@ -1031,11 +1046,18 @@
 WRAPPED_INSTANTIATE_TEST_SUITE_P(
     FormatDialog, /* format_dialog.js */
     FilesAppBrowserTest,
-    ::testing::Values(TestCase("formatDialog"),
-                      TestCase("formatDialogEmpty"),
-                      TestCase("formatDialogCancel"),
-                      TestCase("formatDialogNameLength"),
-                      TestCase("formatDialogNameInvalid"),
-                      TestCase("formatDialogGearMenu")));
+    ::testing::Values(
+        TestCase("formatDialog"),
+        TestCase("formatDialogEmpty"),
+        TestCase("formatDialogCancel"),
+        TestCase("formatDialogNameLength"),
+        TestCase("formatDialogNameInvalid"),
+        TestCase("formatDialogGearMenu"),
+        TestCase("formatDialog").EnableSinglePartitionFormat(),
+        TestCase("formatDialogEmpty").EnableSinglePartitionFormat(),
+        TestCase("formatDialogCancel").EnableSinglePartitionFormat(),
+        TestCase("formatDialogNameLength").EnableSinglePartitionFormat(),
+        TestCase("formatDialogNameInvalid").EnableSinglePartitionFormat(),
+        TestCase("formatDialogGearMenu").EnableSinglePartitionFormat()));
 
 }  // namespace file_manager
diff --git a/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc b/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
index afe1918..b67977c2 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
@@ -718,6 +718,7 @@
   PRINT_IF_NOT_DEFAULT(tablet_mode)
   PRINT_IF_NOT_DEFAULT(zip)
   PRINT_IF_NOT_DEFAULT(zip_no_nacl)
+  PRINT_IF_NOT_DEFAULT(single_partition_format)
 
 #undef PRINT_IF_NOT_DEFAULT
 
@@ -1583,6 +1584,10 @@
     enabled_features.push_back(features::kSharesheet);
   }
 
+  if (options.single_partition_format) {
+    enabled_features.push_back(chromeos::features::kFilesSinglePartitionFormat);
+  }
+
   // This is destroyed in |TearDown()|. We cannot initialize this in the
   // constructor due to this feature values' above dependence on virtual
   // method calls, but by convention subclasses of this fixture may initialize
diff --git a/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.h b/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.h
index 63ed339..1e09001b 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.h
+++ b/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.h
@@ -92,6 +92,9 @@
 
     // Whether test should enable sharesheet.
     bool enable_sharesheet = false;
+
+    // Whether test needs the single partition format feature.
+    bool single_partition_format = false;
   };
 
  protected:
diff --git a/chrome/browser/chromeos/release_notes/release_notes_storage.cc b/chrome/browser/chromeos/release_notes/release_notes_storage.cc
index 13853f1..3f480295 100644
--- a/chrome/browser/chromeos/release_notes/release_notes_storage.cc
+++ b/chrome/browser/chromeos/release_notes/release_notes_storage.cc
@@ -111,4 +111,9 @@
       times_left_to_show - 1);
 }
 
+void ReleaseNotesStorage::StopShowingSuggestionChip() {
+  profile_->GetPrefs()->SetInteger(
+      prefs::kReleaseNotesSuggestionChipTimesLeftToShow, 0);
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/release_notes/release_notes_storage.h b/chrome/browser/chromeos/release_notes/release_notes_storage.h
index e40e09c..7f70d0d4 100644
--- a/chrome/browser/chromeos/release_notes/release_notes_storage.h
+++ b/chrome/browser/chromeos/release_notes/release_notes_storage.h
@@ -35,6 +35,9 @@
   // Decreases the amount of times left to show the suggestion chip.
   void DecreaseTimesLeftToShowSuggestionChip();
 
+  // Sets the number of times left to show the suggestion chip to 0.
+  void StopShowingSuggestionChip();
+
  private:
   Profile* const profile_;
 
diff --git a/chrome/browser/chromeos/scanning/scanning_type_converters.cc b/chrome/browser/chromeos/scanning/scanning_type_converters.cc
index 3cea91c..dd1f382f 100644
--- a/chrome/browser/chromeos/scanning/scanning_type_converters.cc
+++ b/chrome/browser/chromeos/scanning/scanning_type_converters.cc
@@ -44,11 +44,13 @@
         return mojo_ipc::SourceType::kAdfSimplex;
       case lorgnette::SOURCE_ADF_DUPLEX:
         return mojo_ipc::SourceType::kAdfDuplex;
+      case lorgnette::SOURCE_DEFAULT:
+        return mojo_ipc::SourceType::kDefault;
       case lorgnette::SOURCE_UNSPECIFIED:
       case lorgnette::SourceType_INT_MIN_SENTINEL_DO_NOT_USE_:
       case lorgnette::SourceType_INT_MAX_SENTINEL_DO_NOT_USE_:
         NOTREACHED();
-        return mojo_ipc::SourceType::kFlatbed;
+        return mojo_ipc::SourceType::kUnknown;
     }
   }
 };
diff --git a/chrome/browser/chromeos/scanning/scanning_type_converters_unittest.cc b/chrome/browser/chromeos/scanning/scanning_type_converters_unittest.cc
index e6f7e34..d9803b7 100644
--- a/chrome/browser/chromeos/scanning/scanning_type_converters_unittest.cc
+++ b/chrome/browser/chromeos/scanning/scanning_type_converters_unittest.cc
@@ -100,6 +100,9 @@
             mojo_ipc::SourceType::kAdfSimplex, mojo_ipc::ColorMode::kGrayscale},
         ScanningTypeConvertersTestParams{
             lorgnette::SOURCE_ADF_DUPLEX, lorgnette::MODE_COLOR,
-            mojo_ipc::SourceType::kAdfDuplex, mojo_ipc::ColorMode::kColor}));
+            mojo_ipc::SourceType::kAdfDuplex, mojo_ipc::ColorMode::kColor},
+        ScanningTypeConvertersTestParams{
+            lorgnette::SOURCE_DEFAULT, lorgnette::MODE_COLOR,
+            mojo_ipc::SourceType::kDefault, mojo_ipc::ColorMode::kColor}));
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/web_applications/help_app_integration_browsertest.cc b/chrome/browser/chromeos/web_applications/help_app_integration_browsertest.cc
index 8b64461..f2ad7db 100644
--- a/chrome/browser/chromeos/web_applications/help_app_integration_browsertest.cc
+++ b/chrome/browser/chromeos/web_applications/help_app_integration_browsertest.cc
@@ -47,6 +47,7 @@
   HelpAppIntegrationTest() {
     scoped_feature_list_.InitWithFeatures(
         {chromeos::features::kHelpAppReleaseNotes,
+         chromeos::features::kHelpAppSearchServiceIntegration,
          chromeos::features::kReleaseNotesNotificationAllChannels},
         {});
   }
@@ -354,6 +355,45 @@
   EXPECT_EQ(expected_url, GetActiveWebContents()->GetVisibleURL());
 }
 
+// Test that the Help App delegate uses the local search service methods.
+IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest,
+                       HelpAppV2UsesLocalSearchServiceMethods) {
+  WaitForTestSystemAppInstall();
+  content::WebContents* web_contents = LaunchApp(web_app::SystemAppType::HELP);
+
+  // Script that adds a data item to the search index, then tries to find that
+  // data item.
+  constexpr char kScript[] = R"(
+    (async () => {
+      const delegate = document.querySelector('showoff-app').getDelegate();
+
+      await delegate.addOrUpdateSearchIndex([{
+        id: 'test-id',
+        title: 'foobar',
+        body: 'foo bar baz',
+        mainCategoryName: 'Help',
+        locale: 'en-US',
+      }]);
+
+      // Polling is required as addOrUpdateSearchIndex resolves before the
+      // search index is actually updated.
+      setInterval(async () => {
+        // Note that the LSS will fuzzy match into foobar.
+        const {results} = await delegate.findInSearchIndex('foober');
+        if (results && results.length === 1) {
+          window.domAutomationController.send(results[0].id);
+        }
+      }, 10);
+    })();
+  )";
+  std::string result;
+  // Use ExecuteScript instead of EvalJsInAppFrame because the script needs to
+  // run in the same world as the page's code.
+  EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+      SandboxedWebUiAppTestBase::GetAppFrame(web_contents), kScript, &result));
+  EXPECT_EQ(result, "test-id");
+}
+
 // Test that the Help App opens when Gesture help requested.
 IN_PROC_BROWSER_TEST_P(HelpAppAllProfilesIntegrationTest, HelpAppOpenGestures) {
   WaitForTestSystemAppInstall();
diff --git a/chrome/browser/component_updater/floc_component_installer.cc b/chrome/browser/component_updater/floc_component_installer.cc
index f5e06a2d..4f2d35d 100644
--- a/chrome/browser/component_updater/floc_component_installer.cc
+++ b/chrome/browser/component_updater/floc_component_installer.cc
@@ -59,15 +59,12 @@
     std::unique_ptr<base::DictionaryValue> manifest) {
   DCHECK(!install_dir.empty());
 
-  // TODO(yaoxia): Pass along the |version| to each service. At the end of each
-  // floc computation cycle, it should verify the two versions match. This is
-  // not needed currently as the floc_sorting_lsh_clusters_service is not set up
-  // yet.
   floc_blocklist_service_->OnBlocklistFileReady(
-      install_dir.Append(federated_learning::kBlocklistFileName));
+      install_dir.Append(federated_learning::kBlocklistFileName), version);
 
   floc_sorting_lsh_clusters_service_->OnSortingLshClustersFileReady(
-      install_dir.Append(federated_learning::kSortingLshClustersFileName));
+      install_dir.Append(federated_learning::kSortingLshClustersFileName),
+      version);
 }
 
 // Called during startup and installation before ComponentReady().
diff --git a/chrome/browser/component_updater/floc_component_installer_unittest.cc b/chrome/browser/component_updater/floc_component_installer_unittest.cc
index 4e91736..124077f 100644
--- a/chrome/browser/component_updater/floc_component_installer_unittest.cc
+++ b/chrome/browser/component_updater/floc_component_installer_unittest.cc
@@ -49,14 +49,18 @@
 
   ~MockFlocBlocklistService() override = default;
 
-  void OnBlocklistFileReady(const base::FilePath& file_path) override {
+  void OnBlocklistFileReady(const base::FilePath& file_path,
+                            const base::Version& version) override {
     file_paths_.push_back(file_path);
+    versions_.push_back(version);
   }
 
   const std::vector<base::FilePath>& file_paths() const { return file_paths_; }
+  const std::vector<base::Version>& versions() const { return versions_; }
 
  private:
   std::vector<base::FilePath> file_paths_;
+  std::vector<base::Version> versions_;
 };
 
 // This class monitors the OnSortingLshClustersFileReady method calls.
@@ -72,14 +76,18 @@
 
   ~MockFlocSortingLshClustersService() override = default;
 
-  void OnSortingLshClustersFileReady(const base::FilePath& file_path) override {
+  void OnSortingLshClustersFileReady(const base::FilePath& file_path,
+                                     const base::Version& version) override {
     file_paths_.push_back(file_path);
+    versions_.push_back(version);
   }
 
   const std::vector<base::FilePath>& file_paths() const { return file_paths_; }
+  const std::vector<base::Version>& versions() const { return versions_; }
 
  private:
   std::vector<base::FilePath> file_paths_;
+  std::vector<base::Version> versions_;
 };
 
 }  //  namespace
@@ -203,10 +211,12 @@
   std::string contents = "abcd";
   ASSERT_NO_FATAL_FAILURE(CreateTestFlocComponentFiles(contents, contents));
   ASSERT_NO_FATAL_FAILURE(LoadFlocComponent(
-      "1.0.0", federated_learning::kCurrentFlocComponentFormatVersion));
+      "1.0.1", federated_learning::kCurrentFlocComponentFormatVersion));
 
   ASSERT_EQ(blocklist_service()->file_paths().size(), 1u);
+  ASSERT_EQ(blocklist_service()->versions().size(), 1u);
   ASSERT_EQ(sorting_lsh_clusters_service()->file_paths().size(), 1u);
+  ASSERT_EQ(sorting_lsh_clusters_service()->versions().size(), 1u);
 
   // Assert that the file path is the concatenation of |component_install_dir_|
   // and the corresponding file name, which implies that the |version| argument
@@ -222,6 +232,9 @@
                 .Append(federated_learning::kSortingLshClustersFileName)
                 .AsUTF8Unsafe());
 
+  EXPECT_EQ(blocklist_service()->versions()[0].GetString(), "1.0.1");
+  EXPECT_EQ(sorting_lsh_clusters_service()->versions()[0].GetString(), "1.0.1");
+
   std::string actual_contents;
   ASSERT_TRUE(base::ReadFileToString(blocklist_service()->file_paths()[0],
                                      &actual_contents));
diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn
index d02c734..18315b38 100644
--- a/chrome/browser/extensions/BUILD.gn
+++ b/chrome/browser/extensions/BUILD.gn
@@ -176,6 +176,8 @@
     "api/identity/gaia_web_auth_flow.h",
     "api/identity/identity_api.cc",
     "api/identity/identity_api.h",
+    "api/identity/identity_clear_all_cached_auth_tokens_function.cc",
+    "api/identity/identity_clear_all_cached_auth_tokens_function.h",
     "api/identity/identity_constants.cc",
     "api/identity/identity_constants.h",
     "api/identity/identity_get_accounts_function.cc",
diff --git a/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_browsertest.cc b/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_browsertest.cc
index daf006fc..a11c0768 100644
--- a/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_browsertest.cc
+++ b/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_browsertest.cc
@@ -450,14 +450,15 @@
 
   void SetActionsAsBadgeText(const ExtensionId& extension_id, bool pref) {
     const char* pref_string = pref ? "true" : "false";
-    static constexpr char kSetActionCountAsBadgeTextScript[] = R"(
-      chrome.declarativeNetRequest.setActionCountAsBadgeText(%s);
+    static constexpr char kSetExtensionActionOptionsScript[] = R"(
+      chrome.declarativeNetRequest.setExtensionActionOptions(
+        {displayActionCountAsBadgeText: %s});
       window.domAutomationController.send("done");
     )";
 
     ExecuteScriptInBackgroundPage(
         extension_id,
-        base::StringPrintf(kSetActionCountAsBadgeTextScript, pref_string));
+        base::StringPrintf(kSetExtensionActionOptionsScript, pref_string));
   }
 
   // Navigates frame with name |frame_name| to |url|.
@@ -3091,8 +3092,9 @@
   EXPECT_FALSE(action->HasDNRActionCount(first_tab_id));
 }
 
-// Test that enabling the setActionCountAsBadgeText preference will update
-// all browsers sharing the same browser context.
+// Test that enabling the "displayActionCountAsBadgeText" preference using
+// setExtensionActionOptions will update all browsers sharing the same browser
+// context.
 IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestBrowserTest,
                        ActionCountPreferenceMultipleWindows) {
   // Load the extension with a background script so scripts can be run from its
diff --git a/chrome/browser/extensions/api/extension_action/extension_action_api.cc b/chrome/browser/extensions/api/extension_action/extension_action_api.cc
index b55acbd..ac64e70 100644
--- a/chrome/browser/extensions/api/extension_action/extension_action_api.cc
+++ b/chrome/browser/extensions/api/extension_action/extension_action_api.cc
@@ -513,16 +513,16 @@
 
 ExtensionFunction::ResponseAction
 ExtensionActionGetBadgeTextFunction::RunExtensionAction() {
-  // Return a placeholder value if the extension has called
-  // setActionCountAsBadgeText(true) and the badge count shown for this tab is
-  // the number of actions matched.
+  // Return a placeholder value if the extension has enabled using
+  // declarativeNetRequest action count as badge text and the badge count shown
+  // for this tab is the number of actions matched.
   std::string badge_text =
       extension_action_->UseDNRActionCountAsBadgeText(tab_id_)
           ? declarative_net_request::kActionCountPlaceholderBadgeText
           : extension_action_->GetExplicitlySetBadgeText(tab_id_);
 
   // TODO(crbug.com/990224): Document this behavior once
-  // chrome.declarativeNetRequest.setActionCountAsBadgeText is promoted to beta
+  // chrome.declarativeNetRequest.setExtensionActionOptions is promoted to beta
   // from trunk.
   return RespondNow(
       OneArgument(std::make_unique<base::Value>(std::move(badge_text))));
diff --git a/chrome/browser/extensions/api/identity/identity_api.h b/chrome/browser/extensions/api/identity/identity_api.h
index 1b9db68..02b1162 100644
--- a/chrome/browser/extensions/api/identity/identity_api.h
+++ b/chrome/browser/extensions/api/identity/identity_api.h
@@ -18,6 +18,7 @@
 #include "build/build_config.h"
 #include "build/buildflag.h"
 #include "chrome/browser/extensions/api/identity/gaia_web_auth_flow.h"
+#include "chrome/browser/extensions/api/identity/identity_clear_all_cached_auth_tokens_function.h"
 #include "chrome/browser/extensions/api/identity/identity_get_accounts_function.h"
 #include "chrome/browser/extensions/api/identity/identity_get_auth_token_function.h"
 #include "chrome/browser/extensions/api/identity/identity_get_profile_user_info_function.h"
diff --git a/chrome/browser/extensions/api/identity/identity_apitest.cc b/chrome/browser/extensions/api/identity/identity_apitest.cc
index 35829f5..d3ec2b0 100644
--- a/chrome/browser/extensions/api/identity/identity_apitest.cc
+++ b/chrome/browser/extensions/api/identity/identity_apitest.cc
@@ -11,9 +11,11 @@
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/memory/ptr_util.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/run_loop.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
+#include "base/test/bind_test_util.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/values.h"
@@ -60,12 +62,14 @@
 #include "components/signin/public/identity_manager/identity_test_utils.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/notification_source.h"
+#include "content/public/browser/storage_partition.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/test_utils.h"
 #include "extensions/browser/api_test_utils.h"
 #include "extensions/common/extension_builder.h"
 #include "extensions/common/extension_features.h"
 #include "google_apis/gaia/oauth2_mint_token_flow.h"
+#include "net/cookies/cookie_util.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/test/test_url_loader_factory.h"
@@ -3583,6 +3587,111 @@
             url);
 }
 
+class ClearAllCachedAuthTokensFunctionTest : public AsyncExtensionBrowserTest {
+ public:
+  void SetUpOnMainThread() override {
+    AsyncExtensionBrowserTest::SetUpOnMainThread();
+    base::FilePath manifest_path =
+        test_data_dir_.AppendASCII("platform_apps/oauth2");
+    extension_ = LoadExtension(manifest_path);
+  }
+
+  const Extension* extension() { return extension_; }
+
+  bool RunClearAllCachedAuthTokensFunction() {
+    auto function =
+        base::MakeRefCounted<IdentityClearAllCachedAuthTokensFunction>();
+    function->set_extension(extension_);
+    return utils::RunFunction(function.get(), "[]", browser(),
+                              api_test_utils::NONE);
+  }
+
+  IdentityAPI* id_api() {
+    return IdentityAPI::GetFactoryInstance()->Get(browser()->profile());
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+  const Extension* extension_ = nullptr;
+};
+
+IN_PROC_BROWSER_TEST_F(ClearAllCachedAuthTokensFunctionTest,
+                       EraseCachedGaiaId) {
+  id_api()->SetGaiaIdForExtension(extension()->id(), "test_gaia");
+  EXPECT_EQ("test_gaia", id_api()->GetGaiaIdForExtension(extension()->id()));
+  ASSERT_TRUE(RunClearAllCachedAuthTokensFunction());
+  EXPECT_FALSE(id_api()->GetGaiaIdForExtension(extension()->id()).has_value());
+}
+
+IN_PROC_BROWSER_TEST_F(ClearAllCachedAuthTokensFunctionTest,
+                       EraseCachedTokens) {
+  ExtensionTokenKey token_key(extension()->id(), CoreAccountInfo(), {"foo"});
+  id_api()->token_cache()->SetToken(
+      token_key,
+      IdentityTokenCacheValue::CreateToken("access_token", {"foo"},
+                                           base::TimeDelta::FromSeconds(3600)));
+  EXPECT_NE(IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND,
+            id_api()->token_cache()->GetToken(token_key).status());
+  ASSERT_TRUE(RunClearAllCachedAuthTokensFunction());
+  EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND,
+            id_api()->token_cache()->GetToken(token_key).status());
+}
+
+class ClearAllCachedAuthTokensFunctionTestWithPartitionParam
+    : public ClearAllCachedAuthTokensFunctionTest,
+      public testing::WithParamInterface<WebAuthFlow::Partition> {
+ public:
+  network::mojom::CookieManager* GetCookieManager() {
+    Profile* profile = browser()->profile();
+    return content::BrowserContext::GetStoragePartition(
+               profile,
+               WebAuthFlow::GetWebViewPartitionConfig(GetParam(), profile))
+        ->GetCookieManagerForBrowserProcess();
+  }
+
+  // Returns the list of cookies in the cookie manager.
+  net::CookieList GetCookies() {
+    net::CookieList result;
+    base::RunLoop get_all_cookies_loop;
+    GetCookieManager()->GetAllCookies(base::BindLambdaForTesting(
+        [&get_all_cookies_loop, &result](const net::CookieList& cookie_list) {
+          result = cookie_list;
+          get_all_cookies_loop.Quit();
+        }));
+    get_all_cookies_loop.Run();
+    return result;
+  }
+};
+
+IN_PROC_BROWSER_TEST_P(ClearAllCachedAuthTokensFunctionTestWithPartitionParam,
+                       CleanWebAuthFlowCookies) {
+  net::CanonicalCookie test_cookie(
+      "test_name", "test_value", "test.com", "/", base::Time(), base::Time(),
+      base::Time(), true, false, net::CookieSameSite::NO_RESTRICTION,
+      net::COOKIE_PRIORITY_DEFAULT);
+  base::RunLoop set_cookie_loop;
+  GetCookieManager()->SetCanonicalCookie(
+      test_cookie,
+      net::cookie_util::SimulatedCookieSource(test_cookie, url::kHttpsScheme),
+      net::CookieOptions(),
+      net::cookie_util::AdaptCookieAccessResultToBool(
+          base::BindLambdaForTesting([&](bool include) {
+            set_cookie_loop.Quit();
+            EXPECT_TRUE(include);
+          })));
+  set_cookie_loop.Run();
+
+  EXPECT_FALSE(GetCookies().empty());
+  ASSERT_TRUE(RunClearAllCachedAuthTokensFunction());
+  EXPECT_TRUE(GetCookies().empty());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    ClearAllCachedAuthTokensFunctionTestWithPartitionParam,
+    ::testing::Values(WebAuthFlow::Partition::LAUNCH_WEB_AUTH_FLOW,
+                      WebAuthFlow::Partition::GET_AUTH_TOKEN));
+
 class OnSignInChangedEventTest : public IdentityTestWithSignin {
  protected:
   void SetUpOnMainThread() override {
@@ -3751,7 +3860,7 @@
   EXPECT_FALSE(HasExpectedEvent());
 }
 
-// Tests the chrome.identity API implemented by custom JS bindings .
+// Tests the chrome.identity API implemented by custom JS bindings.
 IN_PROC_BROWSER_TEST_F(ExtensionApiTest, ChromeIdentityJsBindings) {
   ASSERT_TRUE(RunExtensionTest("identity/js_bindings")) << message_;
 }
diff --git a/chrome/browser/extensions/api/identity/identity_clear_all_cached_auth_tokens_function.cc b/chrome/browser/extensions/api/identity/identity_clear_all_cached_auth_tokens_function.cc
new file mode 100644
index 0000000..a4deb13
--- /dev/null
+++ b/chrome/browser/extensions/api/identity/identity_clear_all_cached_auth_tokens_function.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 "chrome/browser/extensions/api/identity/identity_clear_all_cached_auth_tokens_function.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/stl_util.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/extensions/api/identity/identity_api.h"
+#include "chrome/browser/extensions/api/identity/identity_constants.h"
+#include "chrome/browser/extensions/api/identity/web_auth_flow.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/extensions/api/identity.h"
+#include "content/public/browser/storage_partition.h"
+
+namespace extensions {
+
+namespace {
+
+constexpr WebAuthFlow::Partition kPartitionsToClean[] = {
+    WebAuthFlow::GET_AUTH_TOKEN, WebAuthFlow::LAUNCH_WEB_AUTH_FLOW};
+
+}
+
+IdentityClearAllCachedAuthTokensFunction::
+    IdentityClearAllCachedAuthTokensFunction() = default;
+IdentityClearAllCachedAuthTokensFunction::
+    ~IdentityClearAllCachedAuthTokensFunction() = default;
+
+ExtensionFunction::ResponseAction
+IdentityClearAllCachedAuthTokensFunction::Run() {
+  Profile* profile = Profile::FromBrowserContext(browser_context());
+  if (profile->IsOffTheRecord())
+    return RespondNow(Error(identity_constants::kOffTheRecord));
+
+  IdentityAPI* id_api = IdentityAPI::GetFactoryInstance()->Get(profile);
+  id_api->EraseGaiaIdForExtension(extension()->id());
+  id_api->token_cache()->EraseAllTokensForExtension(extension()->id());
+
+  for (WebAuthFlow::Partition partition : kPartitionsToClean) {
+    content::BrowserContext::GetStoragePartition(
+        profile, WebAuthFlow::GetWebViewPartitionConfig(partition, profile))
+        ->GetCookieManagerForBrowserProcess()
+        ->DeleteCookies(
+            network::mojom::CookieDeletionFilter::New(),
+            base::BindOnce(
+                &IdentityClearAllCachedAuthTokensFunction::OnCookiesDeleted,
+                this));
+  }
+
+  // This object is retained by the DeleteCookies callbacks.
+  return RespondLater();
+}
+
+void IdentityClearAllCachedAuthTokensFunction::OnCookiesDeleted(
+    uint32_t num_deleted) {
+  ++cleaned_partitions_;
+
+  if (cleaned_partitions_ < base::size(kPartitionsToClean))
+    return;
+
+  // Post a task to ensure Respond() is not synchronously called from Run(). The
+  // object is retained by this task.
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&IdentityClearAllCachedAuthTokensFunction::Respond, this,
+                     NoArguments()));
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/api/identity/identity_clear_all_cached_auth_tokens_function.h b/chrome/browser/extensions/api/identity/identity_clear_all_cached_auth_tokens_function.h
new file mode 100644
index 0000000..ef2302d
--- /dev/null
+++ b/chrome/browser/extensions/api/identity/identity_clear_all_cached_auth_tokens_function.h
@@ -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.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_API_IDENTITY_IDENTITY_CLEAR_ALL_CACHED_AUTH_TOKENS_FUNCTION_H_
+#define CHROME_BROWSER_EXTENSIONS_API_IDENTITY_IDENTITY_CLEAR_ALL_CACHED_AUTH_TOKENS_FUNCTION_H_
+
+#include "extensions/browser/extension_function.h"
+#include "extensions/browser/extension_function_histogram_value.h"
+
+namespace extensions {
+
+class IdentityClearAllCachedAuthTokensFunction : public ExtensionFunction {
+ public:
+  DECLARE_EXTENSION_FUNCTION("identity.clearAllCachedAuthTokens",
+                             IDENTITY_CLEARALLCACHEDAUTHTOKENS)
+  IdentityClearAllCachedAuthTokensFunction();
+
+ private:
+  ~IdentityClearAllCachedAuthTokensFunction() override;
+
+  // ExtensionFunction:
+  ResponseAction Run() override;
+
+  void OnCookiesDeleted(uint32_t num_deleted);
+
+  size_t cleaned_partitions_ = 0;
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_API_IDENTITY_IDENTITY_CLEAR_ALL_CACHED_AUTH_TOKENS_FUNCTION_H_
diff --git a/chrome/browser/extensions/api/identity/identity_token_cache.cc b/chrome/browser/extensions/api/identity/identity_token_cache.cc
index 8b3276b..aad54f1 100644
--- a/chrome/browser/extensions/api/identity/identity_token_cache.cc
+++ b/chrome/browser/extensions/api/identity/identity_token_cache.cc
@@ -192,6 +192,20 @@
   }
 }
 
+void IdentityTokenCache::EraseAllTokensForExtension(
+    const std::string& extension_id) {
+  base::EraseIf(access_tokens_cache_,
+                [&extension_id](const auto& key_value_pair) {
+                  const AccessTokensKey& key = key_value_pair.first;
+                  return key.extension_id == extension_id;
+                });
+  base::EraseIf(intermediate_value_cache_,
+                [&extension_id](const auto& key_value_pair) {
+                  const ExtensionTokenKey& key = key_value_pair.first;
+                  return key.extension_id == extension_id;
+                });
+}
+
 void IdentityTokenCache::EraseAllTokens() {
   intermediate_value_cache_.clear();
   access_tokens_cache_.clear();
diff --git a/chrome/browser/extensions/api/identity/identity_token_cache.h b/chrome/browser/extensions/api/identity/identity_token_cache.h
index 9fb8e47..1662baee 100644
--- a/chrome/browser/extensions/api/identity/identity_token_cache.h
+++ b/chrome/browser/extensions/api/identity/identity_token_cache.h
@@ -106,6 +106,7 @@
                 const IdentityTokenCacheValue& token_data);
   void EraseAccessToken(const std::string& extension_id,
                         const std::string& token);
+  void EraseAllTokensForExtension(const std::string& extension_id);
   void EraseAllTokens();
   const IdentityTokenCacheValue& GetToken(const ExtensionTokenKey& key);
 
diff --git a/chrome/browser/extensions/api/identity/identity_token_cache_unittest.cc b/chrome/browser/extensions/api/identity/identity_token_cache_unittest.cc
index 2ea53e9..a35343a 100644
--- a/chrome/browser/extensions/api/identity/identity_token_cache_unittest.cc
+++ b/chrome/browser/extensions/api/identity/identity_token_cache_unittest.cc
@@ -213,6 +213,32 @@
             GetToken(ext_2, scopes_2).status());
 }
 
+TEST_F(IdentityTokenCacheTest, EraseAllTokensForExtension) {
+  std::string token_string = "token";
+  std::set<std::string> scopes_1 = {"foo", "bar"};
+  SetAccessToken(kDefaultExtensionId, token_string, scopes_1);
+
+  std::string remote_consent = "approved";
+  std::set<std::string> scopes_2 = {"foo", "foobar"};
+  SetRemoteConsentApprovedToken(kDefaultExtensionId, remote_consent, scopes_2);
+
+  std::string unrelated_extension = "ext_unrelated";
+  SetAccessToken(unrelated_extension, token_string, scopes_1);
+  SetRemoteConsentApprovedToken(unrelated_extension, remote_consent, scopes_2);
+
+  cache().EraseAllTokensForExtension(kDefaultExtensionId);
+
+  EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND,
+            GetToken(kDefaultExtensionId, scopes_1).status());
+  EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND,
+            GetToken(kDefaultExtensionId, scopes_2).status());
+
+  EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN,
+            GetToken(unrelated_extension, scopes_1).status());
+  EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_REMOTE_CONSENT_APPROVED,
+            GetToken(unrelated_extension, scopes_2).status());
+}
+
 TEST_F(IdentityTokenCacheTest, GetAccessTokens) {
   std::string ext_1 = "ext_1";
   std::string token_string_1 = "token_1";
diff --git a/chrome/browser/extensions/api/tab_capture/tab_capture_apitest.cc b/chrome/browser/extensions/api/tab_capture/tab_capture_apitest.cc
index b4bb0d3515..27208378 100644
--- a/chrome/browser/extensions/api/tab_capture/tab_capture_apitest.cc
+++ b/chrome/browser/extensions/api/tab_capture/tab_capture_apitest.cc
@@ -148,7 +148,8 @@
 }
 
 // Tests API behaviors, including info queries, and constraints violations.
-IN_PROC_BROWSER_TEST_F(TabCaptureApiTest, ApiTests) {
+// Disabled due to high flake rate; see https://crbug.com/764464.
+IN_PROC_BROWSER_TEST_F(TabCaptureApiTest, DISABLED_ApiTests) {
   AddExtensionToCommandLineAllowlist();
   ASSERT_TRUE(RunExtensionSubtest("tab_capture", "api_tests.html")) << message_;
 }
diff --git a/chrome/browser/extensions/menu_manager.cc b/chrome/browser/extensions/menu_manager.cc
index 55d56730..497b0dc 100644
--- a/chrome/browser/extensions/menu_manager.cc
+++ b/chrome/browser/extensions/menu_manager.cc
@@ -669,14 +669,8 @@
                            webview_guest->view_instance_id());
   }
 
-  auto args = std::make_unique<base::ListValue>();
-  args->Reserve(2);
-  args->Append(std::move(properties));
-  // |properties| is invalidated at this time, which is why |args| needs to be
-  // queried for the pointer. The obtained pointer is guaranteed to stay valid
-  // even after further Appends, because enough storage was reserved above.
-  base::DictionaryValue* raw_properties = nullptr;
-  args->GetDictionary(0, &raw_properties);
+  base::Value::ListStorage args;
+  args.push_back(base::Value::FromUniquePtrValue(std::move(properties)));
 
   // Add the tab info to the argument list.
   // No tab info in a platform app.
@@ -685,7 +679,7 @@
     if (web_contents) {
       int frame_id = ExtensionApiFrameIdMap::GetFrameId(render_frame_host);
       if (frame_id != ExtensionApiFrameIdMap::kInvalidFrameId)
-        raw_properties->SetInteger("frameId", frame_id);
+        args[0].SetIntKey("frameId", frame_id);
 
       // We intentionally don't scrub the tab data here, since the user chose to
       // invoke the extension on the page.
@@ -693,26 +687,26 @@
       // on permissions.
       ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior = {
           ExtensionTabUtil::kDontScrubTab, ExtensionTabUtil::kDontScrubTab};
-      args->Append(ExtensionTabUtil::CreateTabObject(
-                       web_contents, scrub_tab_behavior, extension)
-                       ->ToValue());
+      args.push_back(base::Value::FromUniquePtrValue(
+          ExtensionTabUtil::CreateTabObject(web_contents, scrub_tab_behavior,
+                                            extension)
+              ->ToValue()));
     } else {
-      args->Append(std::make_unique<base::DictionaryValue>());
+      args.push_back(base::DictionaryValue());
     }
   }
 
   if (item->type() == MenuItem::CHECKBOX ||
       item->type() == MenuItem::RADIO) {
     bool was_checked = item->checked();
-    raw_properties->SetBoolean("wasChecked", was_checked);
+    args[0].SetBoolKey("wasChecked", was_checked);
 
     // RADIO items always get set to true when you click on them, but CHECKBOX
     // items get their state toggled.
-    bool checked =
-        (item->type() == MenuItem::RADIO) ? true : !was_checked;
+    bool checked = item->type() == MenuItem::RADIO || !was_checked;
 
     item->SetChecked(checked);
-    raw_properties->SetBoolean("checked", item->checked());
+    args[0].SetBoolKey("checked", item->checked());
 
     if (extension)
       WriteToStorage(extension, item->id().extension_key);
@@ -732,7 +726,7 @@
         webview_guest ? events::WEB_VIEW_INTERNAL_CONTEXT_MENUS
                       : events::CONTEXT_MENUS,
         webview_guest ? kOnWebviewContextMenus : kOnContextMenus,
-        std::unique_ptr<base::ListValue>(args->DeepCopy()), context);
+        std::make_unique<base::ListValue>(args), context);
     event->user_gesture = EventRouter::USER_GESTURE_ENABLED;
     event_router->DispatchEventToExtension(item->extension_id(),
                                            std::move(event));
@@ -744,7 +738,7 @@
                       : events::CONTEXT_MENUS_ON_CLICKED,
         webview_guest ? api::chrome_web_view_internal::OnClicked::kEventName
                       : api::context_menus::OnClicked::kEventName,
-        std::move(args), context);
+        std::make_unique<base::ListValue>(std::move(args)), context);
     event->user_gesture = EventRouter::USER_GESTURE_ENABLED;
     if (webview_guest)
       event->filter_info.instance_id = webview_guest->view_instance_id();
diff --git a/chrome/browser/federated_learning/floc_id_provider_browsertest.cc b/chrome/browser/federated_learning/floc_id_provider_browsertest.cc
index 21fd9801..babffe4 100644
--- a/chrome/browser/federated_learning/floc_id_provider_browsertest.cc
+++ b/chrome/browser/federated_learning/floc_id_provider_browsertest.cc
@@ -25,6 +25,7 @@
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
+#include "components/federated_learning/floc_constants.h"
 #include "components/history/core/test/fake_web_history_service.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "components/sync/driver/test_sync_service.h"
@@ -237,11 +238,25 @@
     run_loop.Run();
   }
 
+  void FinishOutstandingSortingLshQueries() {
+    base::RunLoop run_loop;
+    FlocId dummy_floc = FlocId(0u);
+    g_browser_process->floc_sorting_lsh_clusters_service()->ApplySortingLsh(
+        dummy_floc,
+        base::BindLambdaForTesting(
+            [&](FlocId floc, base::Optional<base::Version> version) {
+              run_loop.Quit();
+            }));
+    run_loop.Run();
+  }
+
   void FinishOutstandingBlocklistQueries() {
     base::RunLoop run_loop;
     FlocId dummy_unfiltered_floc = FlocId(0u);
+    base::Optional<base::Version> no_need_to_verify_version = base::nullopt;
+
     g_browser_process->floc_blocklist_service()->FilterByBlocklist(
-        dummy_unfiltered_floc,
+        dummy_unfiltered_floc, no_need_to_verify_version,
         base::BindLambdaForTesting(
             [&](FlocId filtered_floc) { run_loop.Quit(); }));
     run_loop.Run();
@@ -264,6 +279,30 @@
         base::NumberToString(next_unique_file_suffix_++));
   }
 
+  base::FilePath CreateSortingLshFile(
+      const std::vector<uint32_t>& sorting_lsh_entries) {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+
+    base::FilePath file_path = GetUniqueTemporaryPath();
+    base::File file(file_path, base::File::FLAG_CREATE | base::File::FLAG_READ |
+                                   base::File::FLAG_WRITE);
+    CHECK(file.IsValid());
+
+    CopyingFileOutputStream copying_stream(std::move(file));
+    google::protobuf::io::CopyingOutputStreamAdaptor zero_copy_stream_adaptor(
+        &copying_stream);
+
+    google::protobuf::io::CodedOutputStream output_stream(
+        &zero_copy_stream_adaptor);
+
+    for (uint32_t next : sorting_lsh_entries)
+      output_stream.WriteVarint32(next);
+
+    CHECK(!output_stream.HadError());
+
+    return file_path;
+  }
+
   base::FilePath CreateBlocklistFile(
       const std::vector<uint64_t>& blocklist_entries) {
     base::ScopedAllowBlockingForTesting allow_blocking;
@@ -292,21 +331,36 @@
   void FinishOutstandingAsyncQueries() {
     FinishOutstandingRemotePermissionQueries();
     FinishOutstandingHistoryQueries();
+    FinishOutstandingSortingLshQueries();
     FinishOutstandingBlocklistQueries();
   }
 
-  // Turn on sync-history, set up the blocklist file, and trigger the blocklist
-  // file-ready event.
-  void InitializeBlocklist(const std::vector<uint64_t>& blocklist_entries) {
+  // Turn on sync-history, set up the blocklist and sorting-lsh file, and
+  // trigger the blocklist file-ready event.
+  void InitializeBlocklistAndSortingLsh(
+      const std::vector<uint64_t>& blocklist_entries,
+      base::Version blocklist_version,
+      const std::vector<uint32_t>& sorting_lsh_entries,
+      base::Version sorting_lsh_version) {
     sync_service()->SetActiveDataTypes(syncer::ModelTypeSet::All());
     sync_service()->FireStateChanged();
 
     g_browser_process->floc_blocklist_service()->OnBlocklistFileReady(
-        CreateBlocklistFile(blocklist_entries));
+        CreateBlocklistFile(blocklist_entries), blocklist_version);
+
+    g_browser_process->floc_sorting_lsh_clusters_service()
+        ->OnSortingLshClustersFileReady(
+            CreateSortingLshFile(sorting_lsh_entries), sorting_lsh_version);
 
     FinishOutstandingAsyncQueries();
   }
 
+  void InitializeBlocklist(const std::vector<uint64_t>& blocklist_entries) {
+    base::Version kDummyVersion("1.0.0");
+    InitializeBlocklistAndSortingLsh(blocklist_entries, kDummyVersion, {},
+                                     kDummyVersion);
+  }
+
   history::HistoryService* history_service() {
     return HistoryServiceFactory::GetForProfile(
         browser()->profile(), ServiceAccessType::IMPLICIT_ACCESS);
@@ -658,4 +712,143 @@
             InvokeInterestCohortJsApi(web_contents()));
 }
 
+class FlocIdProviderSortingLshEnabledBrowserTest
+    : public FlocIdProviderWithCustomizedServicesBrowserTest {
+ public:
+  FlocIdProviderSortingLshEnabledBrowserTest() {
+    scoped_feature_list_.Reset();
+    scoped_feature_list_.InitWithFeatures(
+        {features::kFlocIdComputedEventLogging,
+         features::kFlocIdSortingLshBasedComputation,
+         features::kFlocIdBlocklistFiltering},
+        {});
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(FlocIdProviderSortingLshEnabledBrowserTest,
+                       SingleSortingLshCluster) {
+  net::IPAddress::ConsiderLoopbackIPToBePubliclyRoutableForTesting();
+
+  ConfigureReplacementHostAndPortForRemotePermissionService();
+
+  std::string cookies_to_set = "/set-cookie?user_id=123";
+  ui_test_utils::NavigateToURL(
+      browser(), https_server_.GetURL(test_host(), cookies_to_set));
+
+  EXPECT_EQ(1u, GetHistoryUrls().size());
+
+  EXPECT_FALSE(GetFlocId().IsValid());
+
+  // All sim_hash will be encoded as 0 during sorting-lsh
+  std::vector<uint32_t> single_cluster_representation = {
+      kMaxNumberOfBitsInFloc};
+
+  InitializeBlocklistAndSortingLsh({}, base::Version("1.0.0"),
+                                   single_cluster_representation,
+                                   base::Version("1.0.0"));
+
+  // Expect that the FlocIdComputed user event is recorded.
+  ASSERT_EQ(1u, user_event_service()->GetRecordedUserEvents().size());
+
+  // Check that the original sim_hash is not 0.
+  EXPECT_NE(FlocId(0), FlocId::CreateFromHistory({test_host()}));
+
+  // Expect that the final id is 0 because the sorting-lsh was applied.
+  EXPECT_EQ(FlocId(0), GetFlocId());
+}
+
+IN_PROC_BROWSER_TEST_F(FlocIdProviderSortingLshEnabledBrowserTest,
+                       MismatchedBlocklistAndSortingLshVersion) {
+  net::IPAddress::ConsiderLoopbackIPToBePubliclyRoutableForTesting();
+
+  ConfigureReplacementHostAndPortForRemotePermissionService();
+
+  std::string cookies_to_set = "/set-cookie?user_id=123";
+  ui_test_utils::NavigateToURL(
+      browser(), https_server_.GetURL(test_host(), cookies_to_set));
+
+  EXPECT_EQ(1u, GetHistoryUrls().size());
+
+  EXPECT_FALSE(GetFlocId().IsValid());
+
+  // All sim_hash will be encoded as 0 during sorting-lsh
+  std::vector<uint32_t> single_cluster_representation = {
+      kMaxNumberOfBitsInFloc};
+
+  InitializeBlocklistAndSortingLsh({}, base::Version("1.0.1"),
+                                   single_cluster_representation,
+                                   base::Version("1.0.0"));
+
+  // Expect that the FlocIdComputed user event is recorded.
+  ASSERT_EQ(1u, user_event_service()->GetRecordedUserEvents().size());
+
+  // Check that the original sim_hash is not 0.
+  EXPECT_NE(FlocId(0), FlocId::CreateFromHistory({test_host()}));
+
+  // Expect that the final id is invalid because of version mismatch.
+  EXPECT_FALSE(GetFlocId().IsValid());
+}
+
+IN_PROC_BROWSER_TEST_F(FlocIdProviderSortingLshEnabledBrowserTest,
+                       SortingLshAndThenBlocked) {
+  net::IPAddress::ConsiderLoopbackIPToBePubliclyRoutableForTesting();
+
+  ConfigureReplacementHostAndPortForRemotePermissionService();
+
+  std::string cookies_to_set = "/set-cookie?user_id=123";
+  ui_test_utils::NavigateToURL(
+      browser(), https_server_.GetURL(test_host(), cookies_to_set));
+
+  EXPECT_EQ(1u, GetHistoryUrls().size());
+
+  EXPECT_FALSE(GetFlocId().IsValid());
+
+  // All sim_hash will be encoded as 0 during sorting-lsh
+  std::vector<uint32_t> single_cluster_representation = {
+      kMaxNumberOfBitsInFloc};
+
+  // Configure a blocklist that would block 0.
+  InitializeBlocklistAndSortingLsh({0}, base::Version("1.0.0"),
+                                   single_cluster_representation,
+                                   base::Version("1.0.0"));
+
+  // Expect that the FlocIdComputed user event is recorded.
+  ASSERT_EQ(1u, user_event_service()->GetRecordedUserEvents().size());
+
+  // Check that the original sim_hash is not 0.
+  EXPECT_NE(FlocId(0), FlocId::CreateFromHistory({test_host()}));
+
+  // Expect that the final id is invalid because it was blocked.
+  EXPECT_FALSE(GetFlocId().IsValid());
+}
+
+IN_PROC_BROWSER_TEST_F(FlocIdProviderSortingLshEnabledBrowserTest,
+                       CorruptedSortingLSH) {
+  net::IPAddress::ConsiderLoopbackIPToBePubliclyRoutableForTesting();
+
+  ConfigureReplacementHostAndPortForRemotePermissionService();
+
+  std::string cookies_to_set = "/set-cookie?user_id=123";
+  ui_test_utils::NavigateToURL(
+      browser(), https_server_.GetURL(test_host(), cookies_to_set));
+
+  EXPECT_EQ(1u, GetHistoryUrls().size());
+
+  EXPECT_FALSE(GetFlocId().IsValid());
+
+  // All sim_hash will be encoded as an invalid id.
+  std::vector<uint32_t> corrupted_sorting_lsh = {};
+
+  InitializeBlocklistAndSortingLsh({}, base::Version("1.0.0"),
+                                   corrupted_sorting_lsh,
+                                   base::Version("1.0.0"));
+
+  // Expect that the FlocIdComputed user event is recorded.
+  ASSERT_EQ(1u, user_event_service()->GetRecordedUserEvents().size());
+
+  // Expect that the final id is invalid due to unexpected sorting-lsh file
+  // format.
+  EXPECT_FALSE(GetFlocId().IsValid());
+}
+
 }  // namespace federated_learning
diff --git a/chrome/browser/federated_learning/floc_id_provider_impl.cc b/chrome/browser/federated_learning/floc_id_provider_impl.cc
index e8a9acb..3fd3468 100644
--- a/chrome/browser/federated_learning/floc_id_provider_impl.cc
+++ b/chrome/browser/federated_learning/floc_id_provider_impl.cc
@@ -48,10 +48,16 @@
       user_event_service_(user_event_service) {
   history_service->AddObserver(this);
   sync_service_->AddObserver(this);
+  g_browser_process->floc_sorting_lsh_clusters_service()->AddObserver(this);
   g_browser_process->floc_blocklist_service()->AddObserver(this);
 
   OnStateChanged(sync_service);
 
+  if (g_browser_process->floc_sorting_lsh_clusters_service()
+          ->IsSortingLshClustersFileReady()) {
+    OnSortingLshClustersFileReady();
+  }
+
   if (g_browser_process->floc_blocklist_service()->IsBlocklistFileReady())
     OnBlocklistFileReady();
 }
@@ -179,6 +185,15 @@
   ComputeFloc(ComputeFlocTrigger::kHistoryDelete);
 }
 
+void FlocIdProviderImpl::OnSortingLshClustersFileReady() {
+  if (first_sorting_lsh_file_ready_seen_)
+    return;
+
+  first_sorting_lsh_file_ready_seen_ = true;
+
+  MaybeTriggerFirstFlocComputation();
+}
+
 void FlocIdProviderImpl::OnBlocklistFileReady() {
   if (first_blocklist_file_ready_seen_)
     return;
@@ -204,9 +219,17 @@
   if (first_floc_computation_triggered_)
     return;
 
-  if (!first_sync_history_enabled_seen_ ||
-      (base::FeatureList::IsEnabled(features::kFlocIdBlocklistFiltering) &&
-       !first_blocklist_file_ready_seen_)) {
+  bool sorting_lsh_ready_or_not_required =
+      !base::FeatureList::IsEnabled(
+          features::kFlocIdSortingLshBasedComputation) ||
+      first_sorting_lsh_file_ready_seen_;
+
+  bool blocklist_ready_or_not_required =
+      !base::FeatureList::IsEnabled(features::kFlocIdBlocklistFiltering) ||
+      first_blocklist_file_ready_seen_;
+
+  if (!first_sync_history_enabled_seen_ || !sorting_lsh_ready_or_not_required ||
+      !blocklist_ready_or_not_required) {
     return;
   }
 
@@ -369,15 +392,39 @@
     const FlocId& sim_hash) {
   DCHECK(sim_hash.IsValid());
 
-  if (!base::FeatureList::IsEnabled(features::kFlocIdBlocklistFiltering)) {
-    std::move(callback).Run(ComputeFlocResult(sim_hash, sim_hash));
+  if (!base::FeatureList::IsEnabled(
+          features::kFlocIdSortingLshBasedComputation)) {
+    SkippedOrAppliedSortingLsh(std::move(callback), sim_hash,
+                               /*sim_hash_or_sorting_lsh=*/sim_hash,
+                               /*version_to_validate=*/base::nullopt);
+    return;
+  }
+
+  g_browser_process->floc_sorting_lsh_clusters_service()->ApplySortingLsh(
+      sim_hash, base::BindOnce(&FlocIdProviderImpl::SkippedOrAppliedSortingLsh,
+                               weak_ptr_factory_.GetWeakPtr(),
+                               std::move(callback), sim_hash));
+}
+
+void FlocIdProviderImpl::SkippedOrAppliedSortingLsh(
+    ComputeFlocCompletedCallback callback,
+    const FlocId& sim_hash,
+    FlocId sim_hash_or_sorting_lsh,
+    base::Optional<base::Version> version_to_validate) {
+  // |!sim_hash_or_sorting_lsh.IsValid()| indicates a missing or corrupted
+  // sorting-lsh file.
+  if (!base::FeatureList::IsEnabled(features::kFlocIdBlocklistFiltering) ||
+      !sim_hash_or_sorting_lsh.IsValid()) {
+    std::move(callback).Run(
+        ComputeFlocResult(sim_hash, sim_hash_or_sorting_lsh));
     return;
   }
 
   g_browser_process->floc_blocklist_service()->FilterByBlocklist(
-      sim_hash, base::BindOnce(&FlocIdProviderImpl::DidApplyAdditionalFiltering,
-                               weak_ptr_factory_.GetWeakPtr(),
-                               std::move(callback), sim_hash));
+      sim_hash_or_sorting_lsh, version_to_validate,
+      base::BindOnce(&FlocIdProviderImpl::DidApplyAdditionalFiltering,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback),
+                     sim_hash));
 }
 
 void FlocIdProviderImpl::DidApplyAdditionalFiltering(
diff --git a/chrome/browser/federated_learning/floc_id_provider_impl.h b/chrome/browser/federated_learning/floc_id_provider_impl.h
index b7bccec..268f443 100644
--- a/chrome/browser/federated_learning/floc_id_provider_impl.h
+++ b/chrome/browser/federated_learning/floc_id_provider_impl.h
@@ -10,6 +10,7 @@
 #include "base/timer/timer.h"
 #include "chrome/browser/federated_learning/floc_id_provider.h"
 #include "components/federated_learning/floc_blocklist_service.h"
+#include "components/federated_learning/floc_sorting_lsh_clusters_service.h"
 #include "components/history/core/browser/history_service.h"
 #include "components/history/core/browser/history_service_observer.h"
 #include "components/sync/driver/sync_service_observer.h"
@@ -44,6 +45,7 @@
 // the event of history deletion, the floc will be recomputed immediately and
 // reset the timer of any currently scheduled computation to be 24 hours later.
 class FlocIdProviderImpl : public FlocIdProvider,
+                           public FlocSortingLshClustersService::Observer,
                            public FlocBlocklistService::Observer,
                            public history::HistoryServiceObserver,
                            public syncer::SyncServiceObserver {
@@ -112,6 +114,9 @@
   void OnURLsDeleted(history::HistoryService* history_service,
                      const history::DeletionInfo& deletion_info) override;
 
+  // FlocSortingLshClustersService::Observer
+  void OnSortingLshClustersFileReady() override;
+
   // FlocBlocklistService::Observer
   void OnBlocklistFileReady() override;
 
@@ -143,6 +148,11 @@
   // history. For example, invalidate it if it's in the blocklist.
   void ApplyAdditionalFiltering(ComputeFlocCompletedCallback callback,
                                 const FlocId& sim_hash);
+  void SkippedOrAppliedSortingLsh(
+      ComputeFlocCompletedCallback callback,
+      const FlocId& sim_hash,
+      FlocId sim_hash_or_sorting_lsh,
+      base::Optional<base::Version> version_to_validate);
   void DidApplyAdditionalFiltering(ComputeFlocCompletedCallback callback,
                                    FlocId sim_hash,
                                    FlocId final_hash);
@@ -158,6 +168,7 @@
   // loggings, updates, etc.), and compute again.
   base::Optional<ComputeFlocTrigger> pending_recompute_event_;
 
+  bool first_sorting_lsh_file_ready_seen_ = false;
   bool first_blocklist_file_ready_seen_ = false;
   bool first_sync_history_enabled_seen_ = false;
 
diff --git a/chrome/browser/federated_learning/floc_id_provider_unittest.cc b/chrome/browser/federated_learning/floc_id_provider_unittest.cc
index 548efd88..9a45d07 100644
--- a/chrome/browser/federated_learning/floc_id_provider_unittest.cc
+++ b/chrome/browser/federated_learning/floc_id_provider_unittest.cc
@@ -32,17 +32,22 @@
 
 using ComputeFlocTrigger = FlocIdProviderImpl::ComputeFlocTrigger;
 using ComputeFlocResult = FlocIdProviderImpl::ComputeFlocResult;
+using ComputeFlocCompletedCallback =
+    FlocIdProviderImpl::ComputeFlocCompletedCallback;
+using CanComputeFlocCallback = FlocIdProviderImpl::CanComputeFlocCallback;
 
 class MockFlocBlocklistService : public FlocBlocklistService {
  public:
   using FlocBlocklistService::FlocBlocklistService;
 
-  void ConfigureFlocToBlock(const FlocId& floc_to_block) {
+  void ConfigureBlocklist(const FlocId& floc_to_block) {
     floc_to_block_ = floc_to_block;
   }
 
-  void FilterByBlocklist(const FlocId& unfiltered_floc,
-                         FilterByBlocklistCallback callback) override {
+  void FilterByBlocklist(
+      const FlocId& unfiltered_floc,
+      const base::Optional<base::Version>& version_to_validate,
+      FilterByBlocklistCallback callback) override {
     if (floc_to_block_ == unfiltered_floc) {
       std::move(callback).Run(FlocId());
       return;
@@ -54,6 +59,33 @@
   FlocId floc_to_block_;
 };
 
+class MockFlocSortingLshService : public FlocSortingLshClustersService {
+ public:
+  using FlocSortingLshClustersService::FlocSortingLshClustersService;
+
+  void ConfigureSortingLsh(
+      const std::unordered_map<uint64_t, FlocId>& sorting_lsh_map,
+      const base::Version& version) {
+    sorting_lsh_map_ = sorting_lsh_map;
+    version_ = version;
+  }
+
+  void ApplySortingLsh(const FlocId& raw_floc_id,
+                       ApplySortingLshCallback callback) override {
+    if (sorting_lsh_map_.count(raw_floc_id.ToUint64())) {
+      std::move(callback).Run(sorting_lsh_map_.at(raw_floc_id.ToUint64()),
+                              version_);
+      return;
+    }
+
+    std::move(callback).Run(FlocId(), version_);
+  }
+
+ private:
+  std::unordered_map<uint64_t, FlocId> sorting_lsh_map_;
+  base::Version version_;
+};
+
 class FakeFlocRemotePermissionService : public FlocRemotePermissionService {
  public:
   using FlocRemotePermissionService::FlocRemotePermissionService;
@@ -203,6 +235,11 @@
         &prefs_, /*is_off_the_record=*/false, /*store_last_modified=*/false,
         /*restore_session=*/false);
 
+    auto sorting_lsh_service = std::make_unique<MockFlocSortingLshService>();
+    sorting_lsh_service_ = sorting_lsh_service.get();
+    TestingBrowserProcess::GetGlobal()->SetFlocSortingLshClustersService(
+        std::move(sorting_lsh_service));
+
     auto blocklist_service = std::make_unique<MockFlocBlocklistService>();
     blocklist_service_ = blocklist_service.get();
     TestingBrowserProcess::GetGlobal()->SetFlocBlocklistService(
@@ -238,13 +275,16 @@
     history_service_->RemoveObserver(floc_id_provider_.get());
   }
 
-  void CheckCanComputeFloc(
-      FlocIdProviderImpl::CanComputeFlocCallback callback) {
+  void ApplyAdditionalFiltering(ComputeFlocCompletedCallback callback,
+                                const FlocId& sim_hash) {
+    floc_id_provider_->ApplyAdditionalFiltering(std::move(callback), sim_hash);
+  }
+
+  void CheckCanComputeFloc(CanComputeFlocCallback callback) {
     floc_id_provider_->CheckCanComputeFloc(std::move(callback));
   }
 
-  void IsSwaaNacAccountEnabled(
-      FlocIdProviderImpl::CanComputeFlocCallback callback) {
+  void IsSwaaNacAccountEnabled(CanComputeFlocCallback callback) {
     floc_id_provider_->IsSwaaNacAccountEnabled(std::move(callback));
   }
 
@@ -324,6 +364,7 @@
   scoped_refptr<FakeCookieSettings> fake_cookie_settings_;
   std::unique_ptr<MockFlocIdProvider> floc_id_provider_;
 
+  MockFlocSortingLshService* sorting_lsh_service_;
   MockFlocBlocklistService* blocklist_service_;
 
   base::ScopedTempDir temp_dir_;
@@ -833,7 +874,7 @@
 
   // Trigger the blocklist ready event. The 1st floc computation should be
   // triggered now as sync & sync-history are enabled the blocklist is ready.
-  blocklist_service_->OnBlocklistFileReady(base::FilePath());
+  blocklist_service_->OnBlocklistFileReady(base::FilePath(), base::Version());
 
   EXPECT_TRUE(first_floc_computation_triggered());
 }
@@ -845,7 +886,7 @@
 
   // Trigger the blocklist ready event. The 1st floc computation should not be
   // triggered as sync & sync-history are not enabled yet.
-  blocklist_service_->OnBlocklistFileReady(base::FilePath());
+  blocklist_service_->OnBlocklistFileReady(base::FilePath(), base::Version());
 
   EXPECT_FALSE(first_floc_computation_triggered());
 
@@ -876,7 +917,7 @@
 
   // Trigger the blocklist ready event, and turn on sync & sync-history to
   // trigger the 1st floc computation.
-  blocklist_service_->OnBlocklistFileReady(base::FilePath());
+  blocklist_service_->OnBlocklistFileReady(base::FilePath(), base::Version());
 
   test_sync_service_->SetTransportState(
       syncer::SyncService::TransportState::ACTIVE);
@@ -895,7 +936,7 @@
   EXPECT_EQ(floc_from_history, floc_id());
 
   // Set the blocklist to block |floc_from_history|.
-  blocklist_service_->ConfigureFlocToBlock(floc_from_history);
+  blocklist_service_->ConfigureBlocklist(floc_from_history);
 
   task_environment_.FastForwardBy(base::TimeDelta::FromDays(1));
 
@@ -927,7 +968,7 @@
   EXPECT_EQ(floc_from_history.ToUint64(), event.floc_id());
 
   // Reset the blocklist to block nothing.
-  blocklist_service_->ConfigureFlocToBlock(FlocId());
+  blocklist_service_->ConfigureBlocklist(FlocId());
 
   task_environment_.FastForwardBy(base::TimeDelta::FromDays(1));
 
@@ -1148,4 +1189,52 @@
   EXPECT_EQ(FlocId::CreateFromHistory({domain1}), floc_id());
 }
 
+TEST_F(FlocIdProviderUnitTest, ApplyAdditionalFiltering_SortingLsh) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures({features::kFlocIdSortingLshBasedComputation},
+                                {});
+
+  bool callback_called = false;
+  auto callback = base::BindLambdaForTesting([&](ComputeFlocResult result) {
+    EXPECT_FALSE(callback_called);
+    EXPECT_EQ(result.sim_hash, FlocId(3));
+    EXPECT_EQ(result.final_hash, FlocId(2));
+    callback_called = true;
+  });
+
+  // Map 3 to 2
+  sorting_lsh_service_->OnSortingLshClustersFileReady(base::FilePath(),
+                                                      base::Version());
+  sorting_lsh_service_->ConfigureSortingLsh({{3, FlocId(2)}},
+                                            base::Version("3.4.5"));
+
+  FlocId sim_hash(3);
+  ApplyAdditionalFiltering(std::move(callback), sim_hash);
+  task_environment_.RunUntilIdle();
+  EXPECT_TRUE(callback_called);
+}
+
+TEST_F(FlocIdProviderUnitTest, ApplyAdditionalFiltering_SortingLshCorrupted) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures({features::kFlocIdSortingLshBasedComputation},
+                                {});
+
+  bool callback_called = false;
+  auto callback = base::BindLambdaForTesting([&](ComputeFlocResult result) {
+    EXPECT_FALSE(callback_called);
+    EXPECT_EQ(result.sim_hash, FlocId(3));
+    EXPECT_EQ(result.final_hash, FlocId());
+    callback_called = true;
+  });
+
+  sorting_lsh_service_->OnSortingLshClustersFileReady(base::FilePath(),
+                                                      base::Version());
+  sorting_lsh_service_->ConfigureSortingLsh({}, base::Version("3.4.5"));
+
+  FlocId sim_hash(3);
+  ApplyAdditionalFiltering(std::move(callback), sim_hash);
+  task_environment_.RunUntilIdle();
+  EXPECT_TRUE(callback_called);
+}
+
 }  // namespace federated_learning
diff --git a/chrome/browser/installable/installable_manager_browsertest.cc b/chrome/browser/installable/installable_manager_browsertest.cc
index a175bb3..720aafe6 100644
--- a/chrome/browser/installable/installable_manager_browsertest.cc
+++ b/chrome/browser/installable/installable_manager_browsertest.cc
@@ -95,15 +95,16 @@
 class LazyWorkerInstallableManager : public InstallableManager {
  public:
   LazyWorkerInstallableManager(content::WebContents* web_contents,
-                               base::Closure quit_closure)
-      : InstallableManager(web_contents), quit_closure_(quit_closure) {}
-  ~LazyWorkerInstallableManager() override {}
+                               base::OnceClosure quit_closure)
+      : InstallableManager(web_contents),
+        quit_closure_(std::move(quit_closure)) {}
+  ~LazyWorkerInstallableManager() override = default;
 
  protected:
-  void OnWaitingForServiceWorker() override { quit_closure_.Run(); }
+  void OnWaitingForServiceWorker() override { std::move(quit_closure_).Run(); }
 
  private:
-  base::Closure quit_closure_;
+  base::OnceClosure quit_closure_;
 };
 
 // Used only for testing pages where the manifest URL is changed. This class
@@ -114,7 +115,7 @@
       : InstallableManager(web_contents) {}
   ~ResetDataInstallableManager() override {}
 
-  void SetQuitClosure(base::Closure quit_closure) {
+  void SetQuitClosure(base::RepeatingClosure quit_closure) {
     quit_closure_ = quit_closure;
   }
 
@@ -125,12 +126,12 @@
   }
 
  private:
-  base::Closure quit_closure_;
+  base::RepeatingClosure quit_closure_;
 };
 
 class CallbackTester {
  public:
-  explicit CallbackTester(base::Closure quit_closure)
+  explicit CallbackTester(base::RepeatingClosure quit_closure)
       : quit_closure_(quit_closure) {}
 
   void OnDidFinishInstallableCheck(const InstallableData& data) {
@@ -162,7 +163,7 @@
   bool has_worker() const { return has_worker_; }
 
  private:
-  base::Closure quit_closure_;
+  base::RepeatingClosure quit_closure_;
   std::vector<InstallableStatusCode> errors_;
   GURL manifest_url_;
   blink::Manifest manifest_;
@@ -179,8 +180,10 @@
  public:
   NestedCallbackTester(InstallableManager* manager,
                        const InstallableParams& params,
-                       base::Closure quit_closure)
-      : manager_(manager), params_(params), quit_closure_(quit_closure) {}
+                       base::OnceClosure quit_closure)
+      : manager_(manager),
+        params_(params),
+        quit_closure_(std::move(quit_closure)) {}
 
   void Run() {
     manager_->GetData(
@@ -217,13 +220,13 @@
     EXPECT_EQ(manifest_.short_name, data.manifest->short_name);
     EXPECT_EQ(manifest_.display_override, data.manifest->display_override);
 
-    quit_closure_.Run();
+    std::move(quit_closure_).Run();
   }
 
  private:
   InstallableManager* manager_;
   InstallableParams params_;
-  base::Closure quit_closure_;
+  base::OnceClosure quit_closure_;
   std::vector<InstallableStatusCode> errors_;
   GURL manifest_url_;
   blink::Manifest manifest_;
diff --git a/chrome/browser/metrics/BUILD.gn b/chrome/browser/metrics/BUILD.gn
index b4a3aad..5691a2d 100644
--- a/chrome/browser/metrics/BUILD.gn
+++ b/chrome/browser/metrics/BUILD.gn
@@ -29,6 +29,123 @@
 }
 
 generate_expired_histograms_array("expired_histograms_array") {
+  inputs = [
+    "//tools/metrics/histograms/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/accessibility/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/android/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/apps/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/arc/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/ash/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/assistant/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/auth/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/auto/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/autofill/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/back_forward_cache/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/background/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/blink/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/bluetooth/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/browser/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/chrome/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/cloud/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/compositing/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/content/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/cookie/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/cras/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/cros/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/crostini/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/crypt/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/cryptohome/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/custom_tabs/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/data_reduction_proxy/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/dev/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/diagnostics/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/direct/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/disk/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/dom/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/download/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/enterprise/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/event/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/extension/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/extensions/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/file/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/fingerprint/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/gcm/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/geolocation/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/google/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/gpu/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml",
+    "//tools/metrics/histograms/histograms_xml/history/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/holding_space/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/image/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/input/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/installer/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/instant/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/interstitial/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/ios/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/local/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/login/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/media/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/memory/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/mobile/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/multi_device/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/na_cl/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/navigation/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/net/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/network/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/new_tab_page/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/notifications/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/offline/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/omnibox/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/oobe/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/optimization/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/others/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/page/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/password/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/payment/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/permissions/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/platform/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/plugin/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/power/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/print/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/printing/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/profile/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/quickoffice/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/quota/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/renderer/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/renderer4/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/safe_browsing/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/sb_client/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/search/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/security/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/service/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/session/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/settings/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/sharing/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/signin/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/simple/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/smart/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/software/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/stability/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/startup/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/storage/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/subresource/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/sync/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/tab/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/translate/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/ukm/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/uma/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/update_engine/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/v8/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/variations/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/video_tutorials/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/web_apk/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/web_audio/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/web_core/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/web_rtc/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/windows/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/obsolete_histograms.xml",
+    "//tools/metrics/histograms/enums.xml",
+  ]
   namespace = "chrome_metrics"
   header_filename = "expired_histograms_array.h"
   major_branch_date_filepath = "//chrome/MAJOR_BRANCH_DATE"
diff --git a/chrome/browser/nearby_sharing/client/nearby_share_api_call_flow_impl_unittest.cc b/chrome/browser/nearby_sharing/client/nearby_share_api_call_flow_impl_unittest.cc
index af865f4..c92f282 100644
--- a/chrome/browser/nearby_sharing/client/nearby_share_api_call_flow_impl_unittest.cc
+++ b/chrome/browser/nearby_sharing/client/nearby_share_api_call_flow_impl_unittest.cc
@@ -82,12 +82,12 @@
 
   void StartPostRequestApiCallFlowWithSerializedRequest(
       const std::string& serialized_request) {
-    flow_.StartPostRequest(GURL(kRequestUrl), serialized_request,
-                           shared_factory_, kAccessToken,
-                           base::Bind(&NearbyShareApiCallFlowImplTest::OnResult,
-                                      base::Unretained(this)),
-                           base::Bind(&NearbyShareApiCallFlowImplTest::OnError,
-                                      base::Unretained(this)));
+    flow_.StartPostRequest(
+        GURL(kRequestUrl), serialized_request, shared_factory_, kAccessToken,
+        base::BindOnce(&NearbyShareApiCallFlowImplTest::OnResult,
+                       base::Unretained(this)),
+        base::BindOnce(&NearbyShareApiCallFlowImplTest::OnError,
+                       base::Unretained(this)));
     // A pending fetch for the API request should be created.
     CheckNearbySharingClientHttpPostRequest(serialized_request);
   }
@@ -100,10 +100,10 @@
       const std::string& serialized_request) {
     flow_.StartPatchRequest(
         GURL(kRequestUrl), serialized_request, shared_factory_, kAccessToken,
-        base::Bind(&NearbyShareApiCallFlowImplTest::OnResult,
-                   base::Unretained(this)),
-        base::Bind(&NearbyShareApiCallFlowImplTest::OnError,
-                   base::Unretained(this)));
+        base::BindOnce(&NearbyShareApiCallFlowImplTest::OnResult,
+                       base::Unretained(this)),
+        base::BindOnce(&NearbyShareApiCallFlowImplTest::OnError,
+                       base::Unretained(this)));
     // A pending fetch for the API request should be created.
     CheckNearbySharingClientHttpPatchRequest(serialized_request);
   }
@@ -116,12 +116,13 @@
   void StartGetRequestApiCallFlowWithRequestAsQueryParameters(
       const NearbyShareApiCallFlow::QueryParameters&
           request_as_query_parameters) {
-    flow_.StartGetRequest(GURL(kRequestUrl), request_as_query_parameters,
-                          shared_factory_, kAccessToken,
-                          base::Bind(&NearbyShareApiCallFlowImplTest::OnResult,
-                                     base::Unretained(this)),
-                          base::Bind(&NearbyShareApiCallFlowImplTest::OnError,
-                                     base::Unretained(this)));
+    flow_.StartGetRequest(
+        GURL(kRequestUrl), request_as_query_parameters, shared_factory_,
+        kAccessToken,
+        base::BindOnce(&NearbyShareApiCallFlowImplTest::OnResult,
+                       base::Unretained(this)),
+        base::BindOnce(&NearbyShareApiCallFlowImplTest::OnError,
+                       base::Unretained(this)));
     // A pending fetch for the API request should be created.
     CheckNearbySharingClientHttpGetRequest(request_as_query_parameters);
   }
diff --git a/chrome/browser/password_check/android/password_check_manager.cc b/chrome/browser/password_check/android/password_check_manager.cc
index da527fd..1afa9fc 100644
--- a/chrome/browser/password_check/android/password_check_manager.cc
+++ b/chrome/browser/password_check/android/password_check_manager.cc
@@ -91,7 +91,6 @@
   // The request is being handled, so reset the boolean.
   was_start_requested_ = false;
   is_check_running_ = true;
-
   progress_ = std::make_unique<PasswordCheckProgress>();
   for (const auto& password : saved_passwords_presenter_.GetSavedPasswords())
     progress_->IncrementCounts(password);
@@ -197,6 +196,11 @@
   if (state != State::kRunning) {
     progress_.reset();
     is_check_running_ = false;
+    if (saved_passwords_presenter_.GetSavedPasswords().empty()) {
+      observer_->OnPasswordCheckStatusChanged(
+          PasswordCheckUIStatus::kErrorNoPasswords);
+      return;
+    }
   }
 
   observer_->OnPasswordCheckStatusChanged(GetUIStatus(state));
diff --git a/chrome/browser/password_check/android/password_check_manager_unittest.cc b/chrome/browser/password_check/android/password_check_manager_unittest.cc
index 21f753fb..8a3d42d2 100644
--- a/chrome/browser/password_check/android/password_check_manager_unittest.cc
+++ b/chrome/browser/password_check/android/password_check_manager_unittest.cc
@@ -593,3 +593,20 @@
                                                 base::ASCIIToUTF16(kPassword1)),
           password_manager::IsLeaked(false));
 }
+
+TEST_F(PasswordCheckManagerTest, TurnsIdleIntoNoPasswords) {
+  InitializeManager();
+  RunUntilIdle();
+
+  EXPECT_CALL(mock_observer(),
+              OnPasswordCheckStatusChanged(PasswordCheckUIStatus::kRunning))
+      .Times(1);
+  EXPECT_CALL(mock_observer(),
+              OnPasswordCheckStatusChanged(PasswordCheckUIStatus::kIdle))
+      .Times(0);
+  EXPECT_CALL(mock_observer(), OnPasswordCheckStatusChanged(
+                                   PasswordCheckUIStatus::kErrorNoPasswords))
+      .Times(1);
+
+  manager().StartCheck();
+}
diff --git a/chrome/browser/prerender/isolated/isolated_prerender_browsertest.cc b/chrome/browser/prerender/isolated/isolated_prerender_browsertest.cc
index 8951900..f13eefe 100644
--- a/chrome/browser/prerender/isolated/isolated_prerender_browsertest.cc
+++ b/chrome/browser/prerender/isolated/isolated_prerender_browsertest.cc
@@ -2573,6 +2573,50 @@
       "IsolatedPrerender.AfterClick.Subresources.UsedCache", true, 2);
 }
 
+IN_PROC_BROWSER_TEST_F(IsolatedPrerenderWithNSPBrowserTest,
+                       DISABLE_ON_WIN_MAC_CHROMEOS(StartsSpareRenderer)) {
+  // Enable low-end device mode to turn off automatic spare renderers. Note that
+  // this will also prevent NSPs from triggering, but the logic under test
+  // happens before that anyways.
+  base::CommandLine::ForCurrentProcess()->AppendSwitch(
+      switches::kEnableLowEndDeviceMode);
+  base::CommandLine::ForCurrentProcess()->AppendSwitch(
+      "isolated-prerender-start-spare-renderer");
+
+  SetDataSaverEnabled(true);
+  GURL starting_page = GetOriginServerURL("/simple.html");
+  ui_test_utils::NavigateToURL(browser(), starting_page);
+  WaitForUpdatedCustomProxyConfig();
+
+  IsolatedPrerenderTabHelper* tab_helper =
+      IsolatedPrerenderTabHelper::FromWebContents(GetWebContents());
+
+  GURL eligible_link =
+      GetOriginServerURL("/prerender/isolated/prefetch_page.html");
+
+  TestTabHelperObserver tab_helper_observer(tab_helper);
+  tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link});
+
+  base::RunLoop prefetch_run_loop;
+  tab_helper_observer.SetOnPrefetchSuccessfulClosure(
+      prefetch_run_loop.QuitClosure());
+
+  GURL doc_url("https://www.google.com/search?q=test");
+  MakeNavigationPrediction(doc_url, {eligible_link});
+
+  base::HistogramTester histogram_tester;
+
+  // This run loop will quit when all the prefetch responses have been
+  // successfully done and processed.
+  prefetch_run_loop.Run();
+
+  // Navigate to trigger the histogram recording.
+  ui_test_utils::NavigateToURL(browser(), GURL("about:blank"));
+
+  histogram_tester.ExpectUniqueSample(
+      "IsolatedPrerender.SpareRenderer.CountStartedOnSRP", 1, 1);
+}
+
 namespace {
 std::unique_ptr<net::test_server::HttpResponse> HandleNonEligibleOrigin(
     const net::test_server::HttpRequest& request) {
diff --git a/chrome/browser/prerender/isolated/isolated_prerender_params.cc b/chrome/browser/prerender/isolated/isolated_prerender_params.cc
index 953b8fa..4f91bc9a 100644
--- a/chrome/browser/prerender/isolated/isolated_prerender_params.cc
+++ b/chrome/browser/prerender/isolated/isolated_prerender_params.cc
@@ -156,3 +156,10 @@
   return base::GetFieldTrialParamByFeatureAsInt(
       features::kIsolatePrerenders, "max_subresource_count_per_prerender", 50);
 }
+
+bool IsolatedPrerenderStartsSpareRenderer() {
+  return base::CommandLine::ForCurrentProcess()->HasSwitch(
+             "isolated-prerender-start-spare-renderer") ||
+         base::GetFieldTrialParamByFeatureAsBool(features::kIsolatePrerenders,
+                                                 "start_spare_renderer", false);
+}
diff --git a/chrome/browser/prerender/isolated/isolated_prerender_params.h b/chrome/browser/prerender/isolated/isolated_prerender_params.h
index caeadac5..1017a58d 100644
--- a/chrome/browser/prerender/isolated/isolated_prerender_params.h
+++ b/chrome/browser/prerender/isolated/isolated_prerender_params.h
@@ -73,4 +73,8 @@
 // The maximum number of subresources that will be fetched per prefetched page.
 size_t IsolatedPrerenderMaxSubresourcesPerPrerender();
 
+// Whether a spare renderer should be started after all prefetching and NSP is
+// complete.
+bool IsolatedPrerenderStartsSpareRenderer();
+
 #endif  // CHROME_BROWSER_PRERENDER_ISOLATED_ISOLATED_PRERENDER_PARAMS_H_
diff --git a/chrome/browser/prerender/isolated/isolated_prerender_tab_helper.cc b/chrome/browser/prerender/isolated/isolated_prerender_tab_helper.cc
index 69ea90a..19ec6efa 100644
--- a/chrome/browser/prerender/isolated/isolated_prerender_tab_helper.cc
+++ b/chrome/browser/prerender/isolated/isolated_prerender_tab_helper.cc
@@ -38,6 +38,7 @@
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/network_service_instance.h"
 #include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
 #include "content/public/browser/service_worker_context.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
@@ -147,6 +148,23 @@
   run_me.Run();
 }
 
+bool ShouldStartSpareRenderer() {
+  if (!IsolatedPrerenderStartsSpareRenderer()) {
+    return false;
+  }
+
+  for (content::RenderProcessHost::iterator iter(
+           content::RenderProcessHost::AllHostsIterator());
+       !iter.IsAtEnd(); iter.Advance()) {
+    if (iter.GetCurrentValue()->IsUnused()) {
+      // There is already a spare renderer.
+      return false;
+    }
+  }
+
+  return true;
+}
+
 }  // namespace
 
 IsolatedPrerenderTabHelper::PrefetchMetrics::PrefetchMetrics() = default;
@@ -168,6 +186,12 @@
 }
 
 IsolatedPrerenderTabHelper::CurrentPageLoad::~CurrentPageLoad() {
+  if (IsolatedPrerenderStartsSpareRenderer()) {
+    UMA_HISTOGRAM_COUNTS_100(
+        "IsolatedPrerender.SpareRenderer.CountStartedOnSRP",
+        number_of_spare_renderers_started_);
+  }
+
   if (!profile_)
     return;
 
@@ -680,6 +704,12 @@
       IsolatedPrerenderMainframeBodyLengthLimit());
 
   page_->url_loaders_.emplace(std::move(loader));
+
+  // Start a spare renderer now so that it will be ready by the time it is
+  // useful to have.
+  if (ShouldStartSpareRenderer()) {
+    StartSpareRenderer();
+  }
 }
 
 void IsolatedPrerenderTabHelper::OnPrefetchRedirect(
@@ -922,6 +952,12 @@
 void IsolatedPrerenderTabHelper::OnPrerenderDone(const GURL& url) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  // The completed NSP probably consumed a previously started spare renderer, so
+  // kick off another one if needed.
+  if (ShouldStartSpareRenderer()) {
+    StartSpareRenderer();
+  }
+
   // It is possible that this is run as a callback after a navigation has
   // already happened and |page_| is now a different instance than when the
   // prerender was started. In this case, just return.
@@ -943,6 +979,11 @@
   DoNoStatePrefetch();
 }
 
+void IsolatedPrerenderTabHelper::StartSpareRenderer() {
+  page_->number_of_spare_renderers_started_++;
+  content::RenderProcessHost::WarmupSpareRenderProcessHost(profile_);
+}
+
 void IsolatedPrerenderTabHelper::OnPredictionUpdated(
     const base::Optional<NavigationPredictorKeyedService::Prediction>
         prediction) {
diff --git a/chrome/browser/prerender/isolated/isolated_prerender_tab_helper.h b/chrome/browser/prerender/isolated/isolated_prerender_tab_helper.h
index 443b7a9..be234bf 100644
--- a/chrome/browser/prerender/isolated/isolated_prerender_tab_helper.h
+++ b/chrome/browser/prerender/isolated/isolated_prerender_tab_helper.h
@@ -362,6 +362,9 @@
     // The number of no state prefetch requests that have been made.
     size_t number_of_no_state_prefetch_attempts_ = 0;
 
+    // The number of spare renderers that were started during this page load.
+    size_t number_of_spare_renderers_started_ = 0;
+
     // All urls that are eligible to be no state prefetched. Once a no state
     // prefetch finishes, in success or in error, it is removed from this list.
     // If there is an active no state prefetch, its url will always be the first
@@ -445,6 +448,10 @@
   // Starts a new no state prefetch for the next eligible url.
   void DoNoStatePrefetch();
 
+  // Starts a spare renderer. Should only be called when all NSPs and
+  // prefetching is complete.
+  void StartSpareRenderer();
+
   // Makes a clone of |this|'s prefetch response so that it can be used for
   // NoStatePrefetch now and later reused if the user navigates to that page.
   std::unique_ptr<PrefetchedMainframeResponseContainer>
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/i_tutorial/components/i_tutorial.js b/chrome/browser/resources/chromeos/accessibility/chromevox/i_tutorial/components/i_tutorial.js
index 7148dea..b0f8a23 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/i_tutorial/components/i_tutorial.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/i_tutorial/components/i_tutorial.js
@@ -90,6 +90,8 @@
 
     numLessons: {type: Number, value: 0},
 
+    numLoadedLessons: {type: Number, value: 0},
+
     activeScreen: {type: String, observer: 'onActiveScreenChanged'},
 
     interactiveMode: {type: Boolean, value: false},
@@ -436,14 +438,13 @@
 
         {
           title: 'Sounds',
-          content: [
-            'ChromeVox uses sounds to give you essential and additional ' +
-                'information. You can use these sounds to navigate more ' +
-                'quickly by learning what each sound means. Once you get ' +
-                'more comfortable, you can turn off verbose descriptions in ' +
-                'speech and rely on them for essential information about the ' +
-                'page. Here is a complete list of sounds and what they mean',
-          ],
+          content:
+              ['ChromeVox uses sounds to give you essential and additional ' +
+               'information. You can use these sounds to navigate more ' +
+               'quickly by learning what each sound means. Once you get ' +
+               'more comfortable, you can turn off verbose descriptions in ' +
+               'speech and rely on them for essential information about the ' +
+               'page. Here is a complete list of sounds and what they mean'],
           medium: InteractionMedium.KEYBOARD,
           curriculums: [Curriculum.SOUNDS_AND_SETTINGS]
         },
@@ -488,8 +489,16 @@
 
   /** @override */
   ready() {
-    document.addEventListener('keydown', this.onKeyDown.bind(this));
     this.hideAllScreens();
+    document.addEventListener('keydown', this.onKeyDown.bind(this));
+    this.addEventListener('lessonready', () => {
+      this.numLoadedLessons += 1;
+      if (this.numLoadedLessons === this.lessonData.length) {
+        this.buildEarconLesson();
+        this.dispatchEvent(
+            new CustomEvent('readyfortesting', {composed: true}));
+      }
+    });
     this.$.lessonTemplate.addEventListener('dom-change', (evt) => {
       // Executes once all lessons have been added to the dom.
       this.show();
@@ -968,5 +977,45 @@
       // Queue lesson content so it is read after the lesson title.
       this.requestSpeech(text, QueueMode.QUEUE);
     }
+  },
+
+  /**
+   * @private
+   * @suppress {undefinedVars|missingProperties} For referencing
+   * EarconDescription and Msgs, which are defined on the Panel window.
+   */
+  buildEarconLesson() {
+    // Find earcon lesson.
+    let earconLesson;
+    const elements = this.$.lessonContainer.children;
+    for (const element of elements) {
+      if (element.is === 'tutorial-lesson' && element.title === 'Sounds') {
+        earconLesson = element;
+      }
+    }
+
+    if (!earconLesson) {
+      throw new Error('Could not find the earcon lesson.');
+    }
+
+    // Add text and listeners.
+    for (const earconId in EarconDescription) {
+      const msgid = EarconDescription[earconId];
+      const earconElement = document.createElement('p');
+      earconElement.innerText = Msgs.getMsg(msgid);
+      earconElement.setAttribute('tabindex', -1);
+      earconElement.addEventListener(
+          'focus', this.requestEarcon.bind(this, earconId));
+      earconLesson.contentDiv.appendChild(earconElement);
+    }
+  },
+
+  /**
+   * @param {string} earconId
+   * @private
+   */
+  requestEarcon(earconId) {
+    this.dispatchEvent(
+        new CustomEvent('requestearcon', {composed: true, detail: {earconId}}));
   }
 });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/i_tutorial/components/tutorial_lesson.html b/chrome/browser/resources/chromeos/accessibility/chromevox/i_tutorial/components/tutorial_lesson.html
index 9e37df8..a9caeba 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/i_tutorial/components/tutorial_lesson.html
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/i_tutorial/components/tutorial_lesson.html
@@ -97,7 +97,8 @@
     <h1 id="title" tabindex="-1">[[ title ]]</h1>
   </template>
   <div id="content">
-    <template is="dom-repeat" items="[[ content ]]" as="text">
+    <template id="contentTemplate" is="dom-repeat" items="[[ content ]]"
+        as="text">
       <p tabindex="-1">[[ text ]]</p>
     </template>
   </div>
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/i_tutorial/components/tutorial_lesson.js b/chrome/browser/resources/chromeos/accessibility/chromevox/i_tutorial/components/tutorial_lesson.js
index aa9d5820..28d1988 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/i_tutorial/components/tutorial_lesson.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/i_tutorial/components/tutorial_lesson.js
@@ -51,6 +51,11 @@
 
   /** @override */
   ready() {
+    this.$.contentTemplate.addEventListener('dom-change', (evt) => {
+      this.dispatchEvent(new CustomEvent('lessonready', {composed: true}));
+    });
+
+
     if (this.practiceFile) {
       this.populatePracticeContent();
       for (const evt of this.events) {
@@ -258,4 +263,9 @@
 
     return false;
   },
+
+  /** @return {Element} */
+  get contentDiv() {
+    return this.$.content;
+  }
 });
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.js b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.js
index de3f75f..48d6d22 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.js
@@ -8,6 +8,7 @@
 
 goog.provide('Panel');
 
+goog.require('AbstractEarcons');
 goog.require('AnnotationsUI');
 goog.require('BackgroundKeyboardHandler');
 goog.require('BrailleCommandData');
@@ -187,6 +188,9 @@
         'enable-experimental-accessibility-chromevox-tutorial', (enabled) => {
           Panel.iTutorialEnabled_ = enabled;
         });
+
+    /** @private {boolean} */
+    Panel.iTutorialReadyForTesting_ = false;
   }
 
   /**
@@ -1230,6 +1234,14 @@
           chrome.extension.getBackgroundPage()['CommandHandler'];
       commandHandler.onCommand('fullyDescribe');
     });
+    $('i-tutorial').addEventListener('requestearcon', (evt) => {
+      const earconId = evt.detail.earconId;
+      chrome.extension
+          .getBackgroundPage()['ChromeVox']['earcons']['playEarcon'](earconId);
+    });
+    $('i-tutorial').addEventListener('readyfortesting', () => {
+      Panel.iTutorialReadyForTesting_ = true;
+    });
   }
 
   /**
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/tutorial_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/tutorial_test.js
index 17e205a..1e254804 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/tutorial_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/tutorial_test.js
@@ -82,8 +82,16 @@
             if (mutation.type === 'childList') {
               for (const node of mutation.addedNodes) {
                 if (node.id === 'i-tutorial-container') {
-                  // Resolve once the tutorial has been added to the document.
-                  resolve();
+                  // Once the tutorial has been added to the document, we need
+                  // to wait for the lesson templates to load.
+                  const panel = this.getPanel();
+                  if (panel.iTutorialReadyForTesting_) {
+                    resolve();
+                  } else {
+                    panel.iTutorial.addEventListener('readyfortesting', () => {
+                      resolve();
+                    });
+                  }
                   observer.disconnect();
                 }
               }
@@ -445,3 +453,33 @@
         .replay();
   });
 });
+
+// Tests for correct speech and earcons on the earcons lesson.
+TEST_F('ChromeVoxTutorialTest', 'EarconLesson', function() {
+  const mockFeedback = this.createMockFeedback();
+  this.runWithLoadedTree(this.simpleDoc, async function(root) {
+    await this.launchAndWaitForTutorial();
+    const tutorial = this.getPanel().iTutorial;
+    const nextObjectAndExpectSpeechAndEarcon = (speech, earcon) => {
+      mockFeedback.call(doCmd('nextObject'))
+          .expectSpeech(speech)
+          .expectEarcon(earcon);
+    };
+    mockFeedback.expectSpeech('Choose your tutorial experience')
+        .call(() => {
+          // Show the lesson.
+          tutorial.curriculum = 'sounds_and_settings';
+          tutorial.showLesson(0);
+        })
+        .expectSpeech('Sounds')
+        .call(doCmd('nextObject'))
+        .expectSpeech(new RegExp(
+            'ChromeVox uses sounds to give you essential and additional ' +
+            'information.'));
+    nextObjectAndExpectSpeechAndEarcon('A modal alert', Earcon.ALERT_MODAL);
+    nextObjectAndExpectSpeechAndEarcon(
+        'A non modal alert', Earcon.ALERT_NONMODAL);
+    nextObjectAndExpectSpeechAndEarcon('A button', Earcon.BUTTON);
+    mockFeedback.replay();
+  });
+});
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access.js b/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access.js
index 97e751d..778535cc 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access.js
@@ -10,8 +10,6 @@
 class SwitchAccess {
   static initialize() {
     SwitchAccess.instance = new SwitchAccess();
-    chrome.virtualKeyboardPrivate.setKeyboardState(
-        chrome.virtualKeyboardPrivate.KeyboardState.ENABLED);
 
     chrome.automation.getDesktop((desktop) => {
       // NavigationManager must be initialized first.
diff --git a/chrome/browser/resources/settings/chromeos/device_page/pointers.html b/chrome/browser/resources/settings/chromeos/device_page/pointers.html
index 20b7b39..a58258f 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/pointers.html
+++ b/chrome/browser/resources/settings/chromeos/device_page/pointers.html
@@ -5,6 +5,7 @@
 <link rel="import" href="../deep_linking_behavior.html">
 <link rel="import" href="../localized_link/localized_link.html">
 <link rel="import" href="../../controls/settings_radio_group.html">
+<link rel="import" href="../../controls/settings_dropdown_menu.html">
 <link rel="import" href="../../controls/settings_slider.html">
 <link rel="import" href="../../controls/settings_toggle_button.html">
 <link rel="import" href="../../prefs/prefs_behavior.html">
@@ -40,14 +41,16 @@
       <div class$="[[getSubsectionClass_(showMouseSection_, hasTouchpad)]]">
         <!-- Do not change the mouse button pref before the mouse is released.
              See crbug.com/686949 -->
-        <settings-toggle-button id="mouseSwapButton"
-            pref="{{prefs.settings.mouse.primary_right}}"
-            label="$i18n{mouseSwapButtons}"
-            on-settings-boolean-control-change="onMouseSwapButtonsChange_"
-            on-down="onMouseSwapButtonsDown_" on-up="onMouseSwapButtonsUp_"
-            deep-link-focus-id$="[[Setting.kMouseSwapPrimaryButtons]]"
-            no-set-pref>
-        </settings-toggle-button>
+        <div class="settings-box">
+          <div class="start settings-box-text" id="mouseSwapButtonLabel">
+            $i18n{mouseSwapButtons}
+          </div>
+          <settings-dropdown-menu aria-labeledby="mouseSwapButtonLabel"
+              pref="{{prefs.settings.mouse.primary_right}}"
+              menu-options="[[swapPrimaryOptions]]"
+              deep-link-focus-id$="[[Setting.kMouseSwapPrimaryButtons]]">
+          </settings-dropdown-menu>
+        </div>
         <template is="dom-if" if="[[allowDisableAcceleration_]]">
           <settings-toggle-button id="mouseAcceleration"
               class="hr"
diff --git a/chrome/browser/resources/settings/chromeos/device_page/pointers.js b/chrome/browser/resources/settings/chromeos/device_page/pointers.js
index be6fbda0..dbe4c81 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/pointers.js
+++ b/chrome/browser/resources/settings/chromeos/device_page/pointers.js
@@ -27,6 +27,23 @@
 
     hasTouchpad: Boolean,
 
+    swapPrimaryOptions: {
+      readOnly: true,
+      type: Array,
+      value() {
+        return [
+          {
+            value: false,
+            name: loadTimeData.getString('primaryMouseButtonLeft')
+          },
+          {
+            value: true,
+            name: loadTimeData.getString('primaryMouseButtonRight')
+          },
+        ];
+      },
+    },
+
     /**
      * Interim property for use until we have a separate subsection for pointing
      * sticks. (See crbug.com/1114828)
@@ -101,10 +118,6 @@
     return hasMouse || hasPointingStick;
   },
 
-  // Used to correctly identify when the mouse button has been released.
-  // crbug.com/686949.
-  receivedMouseSwapButtonsDown_: false,
-
   /**
    * @param {!settings.Route} route
    * @param {settings.Route} oldRoute
@@ -129,26 +142,6 @@
     return showMouseSection && hasTouchpad ? 'subsection' : '';
   },
 
-  /** @private */
-  onMouseSwapButtonsDown_() {
-    this.receivedMouseSwapButtonsDown_ = true;
-  },
-
-  /** @private */
-  onMouseSwapButtonsUp_() {
-    this.receivedMouseSwapButtonsDown_ = false;
-    /** @type {!SettingsToggleButtonElement} */ (this.$.mouseSwapButton)
-        .sendPrefChange();
-  },
-
-  /** @private */
-  onMouseSwapButtonsChange_() {
-    if (!this.receivedMouseSwapButtonsDown_) {
-      /** @type {!SettingsToggleButtonElement} */ (this.$.mouseSwapButton)
-          .sendPrefChange();
-    }
-  },
-
   /**
    * @param {!Event} event
    * @private
diff --git a/chrome/browser/sharing/click_to_call/phone_number_regex.cc b/chrome/browser/sharing/click_to_call/phone_number_regex.cc
index a589248..a3e8dbb 100644
--- a/chrome/browser/sharing/click_to_call/phone_number_regex.cc
+++ b/chrome/browser/sharing/click_to_call/phone_number_regex.cc
@@ -8,7 +8,6 @@
 
 #include "base/bind.h"
 #include "base/feature_list.h"
-#include "base/metrics/histogram_macros.h"
 #include "base/task/post_task.h"
 #include "base/task/thread_pool.h"
 #include "base/time/time.h"
@@ -26,7 +25,6 @@
     R"((?:^|\p{Z})((?:\(?\+[0-9]+\)?)?(?:[.\p{Z}\-(]?[0-9][\p{Z}\-)]?){8,}))";
 
 void PrecompilePhoneNumberRegexes() {
-  SCOPED_UMA_HISTOGRAM_TIMER("Sharing.ClickToCallPhoneNumberPrecompileTime");
   static const char kExampleInput[] = "+01(2)34-5678 9012";
 
   std::string parsed;
diff --git a/chrome/browser/speech/speech_recognition_service_browsertest.cc b/chrome/browser/speech/speech_recognition_service_browsertest.cc
new file mode 100644
index 0000000..0fc448e
--- /dev/null
+++ b/chrome/browser/speech/speech_recognition_service_browsertest.cc
@@ -0,0 +1,179 @@
+// 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 "base/files/file_util.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/speech/speech_recognition_service.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/test/browser_test.h"
+#include "media/audio/wav_audio_handler.h"
+#include "media/base/media_switches.h"
+#include "media/mojo/mojom/media_types.mojom.h"
+#include "media/mojo/mojom/speech_recognition_service.mojom.h"
+#include "sandbox/policy/switches.h"
+
+namespace speech {
+
+constexpr base::FilePath::CharType kSodaResourcesDir[] =
+    FILE_PATH_LITERAL("third_party/soda/resources");
+
+constexpr base::FilePath::CharType kSodaLanguagePackRelativePath[] =
+    FILE_PATH_LITERAL("en_us");
+
+constexpr base::FilePath::CharType kSodaTestAudioRelativePath[] =
+    FILE_PATH_LITERAL("hey_google.wav");
+
+constexpr int kExpectedChannelCount = 1;
+
+constexpr base::FilePath::CharType kSodaBinaryRelativePath[] =
+    FILE_PATH_LITERAL("libsoda_for_testing.so");
+
+class SpeechRecognitionServiceTest
+    : public InProcessBrowserTest,
+      public media::mojom::SpeechRecognitionRecognizerClient {
+ public:
+  SpeechRecognitionServiceTest() {
+    scoped_feature_list_.InitWithFeatures(
+        {media::kLiveCaption, media::kUseSodaForLiveCaption}, {});
+  }
+  ~SpeechRecognitionServiceTest() override = default;
+
+  // media::mojom::SpeechRecognitionRecognizerClient
+  void OnSpeechRecognitionRecognitionEvent(
+      media::mojom::SpeechRecognitionResultPtr result) override;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    // Required for the utility process to access the directory containing the
+    // test files.
+    command_line->AppendSwitch(sandbox::policy::switches::kNoSandbox);
+  }
+
+ protected:
+  void LaunchService();
+  base::test::ScopedFeatureList scoped_feature_list_;
+  mojo::Remote<media::mojom::SpeechRecognitionContext>
+      speech_recognition_context_;
+
+  mojo::Remote<media::mojom::SpeechRecognitionRecognizer>
+      speech_recognition_recognizer_;
+
+  mojo::Receiver<media::mojom::SpeechRecognitionRecognizerClient>
+      speech_recognition_client_receiver_{this};
+
+  std::vector<std::string> recognition_results_;
+
+  DISALLOW_COPY_AND_ASSIGN(SpeechRecognitionServiceTest);
+};
+
+void SpeechRecognitionServiceTest::OnSpeechRecognitionRecognitionEvent(
+    media::mojom::SpeechRecognitionResultPtr result) {
+  recognition_results_.push_back(std::move(result->transcription));
+}
+
+void SpeechRecognitionServiceTest::LaunchService() {
+  // Launch the Speech Recognition service.
+  auto* browser_context =
+      static_cast<content::BrowserContext*>(browser()->profile());
+  auto* service = new SpeechRecognitionService(browser_context);
+
+  mojo::PendingReceiver<media::mojom::SpeechRecognitionContext>
+      speech_recognition_context_receiver =
+          speech_recognition_context_.BindNewPipeAndPassReceiver();
+  service->Create(std::move(speech_recognition_context_receiver));
+
+  mojo::PendingReceiver<media::mojom::SpeechRecognitionRecognizer>
+      pending_recognizer_receiver =
+          speech_recognition_recognizer_.BindNewPipeAndPassReceiver();
+
+  bool is_multichannel_supported = true;
+  auto run_loop = std::make_unique<base::RunLoop>();
+  // Bind the recognizer pipes used to send audio and receive results.
+  speech_recognition_context_->BindRecognizer(
+      std::move(pending_recognizer_receiver),
+      speech_recognition_client_receiver_.BindNewPipeAndPassRemote(),
+      base::BindOnce(
+          [](bool* p_is_multichannel_supported, base::RunLoop* run_loop,
+             bool is_multichannel_supported) {
+            *p_is_multichannel_supported = is_multichannel_supported;
+            run_loop->Quit();
+          },
+          &is_multichannel_supported, run_loop.get()));
+
+  run_loop->Run();
+  ASSERT_TRUE(is_multichannel_supported);
+}
+
+IN_PROC_BROWSER_TEST_F(SpeechRecognitionServiceTest, RecognizePhrase) {
+  ProfileManager::GetActiveUserProfile()->GetPrefs()->SetFilePath(
+      prefs::kSodaBinaryPath,
+      base::FilePath(kSodaResourcesDir).Append(kSodaBinaryRelativePath));
+  ProfileManager::GetActiveUserProfile()->GetPrefs()->SetFilePath(
+      prefs::kSodaEnUsConfigPath,
+      base::FilePath(kSodaResourcesDir).Append(kSodaLanguagePackRelativePath));
+  LaunchService();
+
+  std::string buffer;
+  auto audio_file = base::FilePath(kSodaResourcesDir)
+                        .Append(base::FilePath(kSodaTestAudioRelativePath));
+  {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    ASSERT_TRUE(base::PathExists(audio_file));
+    ASSERT_TRUE(base::ReadFileToString(audio_file, &buffer));
+  }
+
+  auto handler = media::WavAudioHandler::Create(buffer);
+  ASSERT_TRUE(handler.get());
+  ASSERT_EQ(handler->num_channels(), kExpectedChannelCount);
+
+  auto bus =
+      media::AudioBus::Create(kExpectedChannelCount, handler->total_frames());
+
+  size_t bytes_written = 0u;
+  ASSERT_TRUE(handler->CopyTo(bus.get(), 0, &bytes_written));
+
+  std::vector<int16_t> audio_data(bus->frames());
+  bus->ToInterleaved<media::SignedInt16SampleTypeTraits>(bus->frames(),
+                                                         audio_data.data());
+
+  constexpr size_t kMaxChunkSize = 1024;
+  constexpr int kReplayAudioCount = 2;
+  for (int i = 0; i < kReplayAudioCount; i++) {
+    int chunk_start = 0;
+    // Upload chunks of 1024 frames at a time.
+    while (chunk_start < static_cast<int>(audio_data.size())) {
+      int chunk_size = kMaxChunkSize < audio_data.size() - chunk_start
+                           ? kMaxChunkSize
+                           : audio_data.size() - chunk_start;
+
+      auto signed_buffer = media::mojom::AudioDataS16::New();
+      signed_buffer->channel_count = kExpectedChannelCount;
+      signed_buffer->frame_count = chunk_size;
+      signed_buffer->sample_rate = handler->sample_rate();
+      for (int i = 0; i < chunk_size; i++) {
+        signed_buffer->data.push_back(audio_data[chunk_start + i]);
+      }
+
+      speech_recognition_recognizer_->SendAudioToSpeechRecognitionService(
+          std::move(signed_buffer));
+      chunk_start += chunk_size;
+
+      // Sleep for 20ms to simulate real-time audio. SODA requires audio
+      // streaming in order to return events.
+      usleep(20000);
+    }
+  }
+
+  base::RunLoop().RunUntilIdle();
+  // Sleep for 50ms to ensure SODA has returned real-time results.
+  usleep(50000);
+  ASSERT_GT(static_cast<int>(recognition_results_.size()), kReplayAudioCount);
+  ASSERT_EQ(recognition_results_.back(), "Hey Google Hey Google");
+}
+
+}  // namespace speech
diff --git a/chrome/browser/ssl/sct_reporting_service_browsertest.cc b/chrome/browser/ssl/sct_reporting_service_browsertest.cc
index 16ce472e..1030c0a 100644
--- a/chrome/browser/ssl/sct_reporting_service_browsertest.cc
+++ b/chrome/browser/ssl/sct_reporting_service_browsertest.cc
@@ -230,8 +230,9 @@
 
 // Tests that reports are still sent for opted-in profiles after the network
 // service crashes and is restarted.
+// Disabled due to high flake rate; see https://crbug.com/1131803.
 IN_PROC_BROWSER_TEST_F(SCTReportingServiceBrowserTest,
-                       ReportsSentAfterNetworkServiceRestart) {
+                       DISABLED_ReportsSentAfterNetworkServiceRestart) {
   // This test is only applicable to out-of-process network service because it
   // tests what happens when the network service crashes and restarts.
   if (content::IsInProcessNetworkService()) {
diff --git a/chrome/browser/ui/app_list/app_service/app_service_app_model_builder_unittest.cc b/chrome/browser/ui/app_list/app_service/app_service_app_model_builder_unittest.cc
index beeb50f..38021d5 100644
--- a/chrome/browser/ui/app_list/app_service/app_service_app_model_builder_unittest.cc
+++ b/chrome/browser/ui/app_list/app_service/app_service_app_model_builder_unittest.cc
@@ -35,6 +35,7 @@
 #include "chrome/browser/ui/app_list/chrome_app_list_item.h"
 #include "chrome/browser/ui/app_list/icon_standardizer.h"
 #include "chrome/browser/ui/app_list/internal_app/internal_app_metadata.h"
+#include "chrome/browser/ui/app_list/md_icon_normalizer.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/web_applications/test/web_app_browsertest_util.h"
@@ -281,8 +282,15 @@
             &output_image_skia, run_loop.QuitClosure()));
     run_loop.Run();
 
-    if (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon))
+    if (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon)) {
       output_image_skia = app_list::CreateStandardIconImage(output_image_skia);
+    } else {
+      extensions::ChromeAppIcon::ApplyEffects(
+          size_in_dip,
+          base::BindRepeating(&app_list::MaybeResizeAndPadIconForMd),
+          true /* app_launchable */, false /* from_bookmark */,
+          extensions::ChromeAppIcon::Badge::kNone, &output_image_skia);
+    }
   }
 
   void GenerateExtensionAppCompressedIcon(const std::string app_id,
@@ -602,7 +610,7 @@
   apps::IconEffects icon_effects =
       (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon))
           ? apps::IconEffects::kCrOsStandardIcon
-          : apps::IconEffects::kNone;
+          : apps::IconEffects::kResizeAndPad;
 
   base::RunLoop run_loop;
   apps::mojom::IconValuePtr dst_icon;
diff --git a/chrome/browser/ui/app_list/search/app_service_app_result.cc b/chrome/browser/ui/app_list/search/app_service_app_result.cc
index 5c2e9cd8..1fd00ad 100644
--- a/chrome/browser/ui/app_list/search/app_service_app_result.cc
+++ b/chrome/browser/ui/app_list/search/app_service_app_result.cc
@@ -172,6 +172,7 @@
         base::UserMetricsAction("ReleaseNotes.SuggestionChipLaunched"));
     proxy->LaunchAppWithUrl(app_id(), event_flags, query_url().value(),
                             launch_source, controller()->GetAppListDisplayId());
+    chromeos::ReleaseNotesStorage(profile()).StopShowingSuggestionChip();
     return;
   }
 
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_ui_browsertest.cc b/chrome/browser/ui/ash/holding_space/holding_space_ui_browsertest.cc
index 2d07299..b3c7bf5 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_ui_browsertest.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_ui_browsertest.cc
@@ -11,7 +11,6 @@
 #include "ash/public/cpp/holding_space/holding_space_model_observer.h"
 #include "base/scoped_observer.h"
 #include "base/test/bind_test_util.h"
-#include "chrome/browser/ui/browser_window.h"
 #include "content/public/test/browser_test.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "ui/aura/window.h"
@@ -267,14 +266,6 @@
   // Otherwise the screenshot will be taken using the `ChromeScreenshotGrabber`.
   PressAndReleaseKey(ui::VKEY_MEDIA_LAUNCH_APP1,
                      ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN);
-  // Move the mouse over to the browser window. The reason for that is with
-  // `features::kCaptureMode` enabled, the new capture mode implementation will
-  // not automatically capture the topmost window unless the mouse is hovered
-  // above it.
-  aura::Window* browser_window = browser()->window()->GetNativeWindow();
-  ui::test::EventGenerator event_generator(browser_window->GetRootWindow());
-  event_generator.MoveMouseTo(
-      browser_window->GetBoundsInScreen().CenterPoint());
   PressAndReleaseKey(ui::VKEY_RETURN);
 
   // Bind an observer to watch for updates to the holding space model.
diff --git a/chrome/browser/ui/content_settings/content_setting_image_model.cc b/chrome/browser/ui/content_settings/content_setting_image_model.cc
index b40309b..0c230099 100644
--- a/chrome/browser/ui/content_settings/content_setting_image_model.cc
+++ b/chrome/browser/ui/content_settings/content_setting_image_model.cc
@@ -941,16 +941,11 @@
   // Show promo the first time a quiet prompt is shown to the user.
   set_should_show_promo(
       QuietNotificationPermissionUiState::ShouldShowPromo(profile));
-  using QuietUiReason = permissions::PermissionRequestManager::QuietUiReason;
-  switch (manager->ReasonForUsingQuietUi()) {
-    case QuietUiReason::kEnabledInPrefs:
-      set_explanatory_string_id(IDS_NOTIFICATIONS_OFF_EXPLANATORY_TEXT);
-      break;
-    case QuietUiReason::kTriggeredByCrowdDeny:
-    case QuietUiReason::kTriggeredDueToAbusiveRequests:
-    case QuietUiReason::kTriggeredDueToAbusiveContent:
-      set_explanatory_string_id(0);
-      break;
+  if (permissions::NotificationPermissionUiSelector::ShouldSuppressAnimation(
+          manager->ReasonForUsingQuietUi())) {
+    set_explanatory_string_id(0);
+  } else {
+    set_explanatory_string_id(IDS_NOTIFICATIONS_OFF_EXPLANATORY_TEXT);
   }
   return true;
 }
diff --git a/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc b/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc
index 999223a..7d5afdb7 100644
--- a/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc
+++ b/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc
@@ -13,8 +13,10 @@
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/permission_bubble/permission_prompt_bubble_view.h"
+#include "components/permissions/notification_permission_ui_selector.h"
 #include "components/permissions/permission_request.h"
 #include "components/permissions/permission_request_manager.h"
+#include "components/permissions/permission_uma_util.h"
 #include "content/public/browser/web_contents.h"
 #include "ui/views/bubble/bubble_frame_view.h"
 
@@ -111,3 +113,23 @@
   return permissions::PermissionPrompt::TabSwitchingBehavior::
       kDestroyPromptButKeepRequestPending;
 }
+
+permissions::PermissionPromptDisposition
+PermissionPromptImpl::GetPromptDisposition() const {
+  switch (prompt_style_) {
+    case PromptStyle::kBubble:
+      return permissions::PermissionPromptDisposition::ANCHORED_BUBBLE;
+    case PromptStyle::kChip:
+      return permissions::PermissionPromptDisposition::LOCATION_BAR_LEFT_CHIP;
+    case PromptStyle::kQuiet: {
+      permissions::PermissionRequestManager* manager =
+          permissions::PermissionRequestManager::FromWebContents(web_contents_);
+      return permissions::NotificationPermissionUiSelector::
+                     ShouldSuppressAnimation(manager->ReasonForUsingQuietUi())
+                 ? permissions::PermissionPromptDisposition::
+                       LOCATION_BAR_RIGHT_STATIC_ICON
+                 : permissions::PermissionPromptDisposition::
+                       LOCATION_BAR_RIGHT_ANIMATED_ICON;
+    }
+  }
+}
diff --git a/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.h b/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.h
index 9b8926c..fdebbb51 100644
--- a/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.h
+++ b/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.h
@@ -30,6 +30,8 @@
   // permissions::PermissionPrompt:
   void UpdateAnchorPosition() override;
   TabSwitchingBehavior GetTabSwitchingBehavior() override;
+  permissions::PermissionPromptDisposition GetPromptDisposition()
+      const override;
 
   PermissionPromptBubbleView* prompt_bubble_for_testing() {
     return prompt_bubble_;
diff --git a/chrome/browser/ui/views/sharing/click_to_call_browsertest.cc b/chrome/browser/ui/views/sharing/click_to_call_browsertest.cc
index 001b21a..e895d15 100644
--- a/chrome/browser/ui/views/sharing/click_to_call_browsertest.cc
+++ b/chrome/browser/ui/views/sharing/click_to_call_browsertest.cc
@@ -86,13 +86,7 @@
 
   base::HistogramTester::CountsMap GetTotalHistogramCounts(
       const base::HistogramTester& histograms) {
-    base::HistogramTester::CountsMap counts =
-        histograms.GetTotalCountsForPrefix(HistogramName(""));
-    // PhoneNumberPrecompileTime will be logged 15 seconds after startup but
-    // we want to ignore it in these browser tests as we don't know if the
-    // test takes more or less time than that.
-    counts.erase(HistogramName("PhoneNumberPrecompileTime"));
-    return counts;
+    return histograms.GetTotalCountsForPrefix(HistogramName(""));
   }
 };
 
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller.cc b/chrome/browser/ui/views/tabs/tab_drag_controller.cc
index 9f394def..1b6caf4 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller.cc
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller.cc
@@ -1216,6 +1216,9 @@
       if (drag_data_[i].pinned)
         add_types |= TabStripModel::ADD_PINNED;
 
+      // We should have owned_contents here, this CHECK is used to gather data
+      // for https://crbug.com/677806.
+      CHECK(drag_data_[i].owned_contents);
       attached_context_->GetTabStripModel()->InsertWebContentsAt(
           index + i - first_tab_index(),
           std::move(drag_data_[i].owned_contents), add_types, group_);
@@ -1755,6 +1758,9 @@
     std::vector<TabStripModelDelegate::NewStripContents> contentses;
     for (size_t i = 0; i < drag_data_.size(); ++i) {
       TabStripModelDelegate::NewStripContents item;
+      // We should have owned_contents here, this CHECK is used to gather data
+      // for https://crbug.com/677806.
+      CHECK(drag_data_[i].owned_contents);
       item.web_contents = std::move(drag_data_[i].owned_contents);
       item.add_types = drag_data_[i].pinned ? TabStripModel::ADD_PINNED
                                             : TabStripModel::ADD_NONE;
diff --git a/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.cc b/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.cc
index 2ebe801..f1a662bd 100644
--- a/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.cc
@@ -247,12 +247,12 @@
 }
 
 void MultidevicePhoneHubHandler::OnPhoneRingingStateChanged() {
-  // TODO(jimmyxgong): Change to casting the enum TBA to int.
-  bool is_ringing = fake_phone_hub_manager_->fake_find_my_device_controller()
-                        ->IsPhoneRinging();
-  int status_as_int = is_ringing ? 2 : 1;
+  phonehub::FindMyDeviceController::Status ringing_status =
+      fake_phone_hub_manager_->fake_find_my_device_controller()
+          ->GetPhoneRingingStatus();
+
   FireWebUIListener("find-my-device-status-changed",
-                    base::Value(status_as_int));
+                    base::Value(static_cast<int>(ringing_status)));
 }
 
 void MultidevicePhoneHubHandler::OnTetherStatusChanged() {
diff --git a/chrome/browser/ui/webui/settings/chromeos/device_section.cc b/chrome/browser/ui/webui/settings/chromeos/device_section.cc
index 6e180aae..b9688248 100644
--- a/chrome/browser/ui/webui/settings/chromeos/device_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/device_section.cc
@@ -1118,6 +1118,9 @@
       {"mouseScrollSpeed", IDS_SETTINGS_MOUSE_SCROLL_SPEED_LABEL},
       {"mouseSpeed", IDS_SETTINGS_MOUSE_SPEED_LABEL},
       {"mouseSwapButtons", IDS_SETTINGS_MOUSE_SWAP_BUTTONS_LABEL},
+      {"primaryMouseButtonLeft", IDS_SETTINGS_PRIMARY_MOUSE_BUTTON_LEFT_LABEL},
+      {"primaryMouseButtonRight",
+       IDS_SETTINGS_PRIMARY_MOUSE_BUTTON_RIGHT_LABEL},
       {"mouseReverseScroll", IDS_SETTINGS_MOUSE_REVERSE_SCROLL_LABEL},
       {"mouseAccelerationLabel", IDS_SETTINGS_MOUSE_ACCELERATION_LABEL},
       {"mouseScrollAccelerationLabel",
diff --git a/chrome/browser/util/android/java/src/org/chromium/chrome/browser/util/KeyNavigationUtil.java b/chrome/browser/util/android/java/src/org/chromium/chrome/browser/util/KeyNavigationUtil.java
index 5fb8057..d119325 100644
--- a/chrome/browser/util/android/java/src/org/chromium/chrome/browser/util/KeyNavigationUtil.java
+++ b/chrome/browser/util/android/java/src/org/chromium/chrome/browser/util/KeyNavigationUtil.java
@@ -73,6 +73,15 @@
     }
 
     /**
+     * Checks whether the given event is any DPAD or NUMPAD direction.
+     * @param event Event to be checked.
+     * @return Whether the event should be processed as any of navigation direction.
+     */
+    public static boolean isGoAnyDirection(KeyEvent event) {
+        return isGoDown(event) || isGoUp(event) || isGoLeft(event) || isGoRight(event);
+    }
+
+    /**
      * Checks whether the given event is any of ENTER or NUMPAD ENTER.
      * @param event Event to be checked.
      * @return Whether the event should be processed as ENTER.
diff --git a/chrome/browser/web_applications/BUILD.gn b/chrome/browser/web_applications/BUILD.gn
index 3b105a6..4cb5243 100644
--- a/chrome/browser/web_applications/BUILD.gn
+++ b/chrome/browser/web_applications/BUILD.gn
@@ -18,6 +18,7 @@
   sources = [
     "daily_metrics_helper.cc",
     "daily_metrics_helper.h",
+    "extension_status_utils.h",
     "manifest_update_manager.cc",
     "manifest_update_manager.h",
     "manifest_update_task.cc",
diff --git a/chrome/browser/web_applications/extension_status_utils.h b/chrome/browser/web_applications/extension_status_utils.h
new file mode 100644
index 0000000..4b73cff2
--- /dev/null
+++ b/chrome/browser/web_applications/extension_status_utils.h
@@ -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.
+
+#ifndef CHROME_BROWSER_WEB_APPLICATIONS_EXTENSION_STATUS_UTILS_H_
+#define CHROME_BROWSER_WEB_APPLICATIONS_EXTENSION_STATUS_UTILS_H_
+
+#include <string>
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+bool IsExtensionBlockedByPolicy(content::BrowserContext* context,
+                                const std::string& extension_id);
+
+// Returns whether the extension with |extension_id| is installed regardless of
+// disabled/blocked/terminated status.
+bool IsExtensionInstalled(content::BrowserContext* context,
+                          const std::string& extension_id);
+
+// Returns whether the user has uninstalled an externally installed extension
+// with |extension_id|.
+bool IsExternalExtensionUninstalled(content::BrowserContext* context,
+                                    const std::string& extension_id);
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_WEB_APPLICATIONS_EXTENSION_STATUS_UTILS_H_
diff --git a/chrome/browser/web_applications/extensions/BUILD.gn b/chrome/browser/web_applications/extensions/BUILD.gn
index 83aaf79..12f0ccb 100644
--- a/chrome/browser/web_applications/extensions/BUILD.gn
+++ b/chrome/browser/web_applications/extensions/BUILD.gn
@@ -31,6 +31,7 @@
     "bookmark_app_shortcut_manager.h",
     "bookmark_app_util.cc",
     "bookmark_app_util.h",
+    "extension_status_utils.cc",
     "web_app_extension_shortcut.cc",
     "web_app_extension_shortcut.h",
     "web_app_extension_shortcut_mac.h",
diff --git a/chrome/browser/web_applications/extensions/extension_status_utils.cc b/chrome/browser/web_applications/extensions/extension_status_utils.cc
new file mode 100644
index 0000000..bd2a37e
--- /dev/null
+++ b/chrome/browser/web_applications/extensions/extension_status_utils.cc
@@ -0,0 +1,45 @@
+// 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/web_applications/extension_status_utils.h"
+#include "chrome/browser/extensions/extension_management.h"
+#include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+
+namespace extensions {
+
+bool IsExtensionBlockedByPolicy(content::BrowserContext* context,
+                                const std::string& extension_id) {
+  auto* registry = ExtensionRegistry::Get(context);
+  // May be nullptr in unit tests.
+  if (!registry)
+    return false;
+
+  const Extension* extension = registry->GetInstalledExtension(extension_id);
+  ExtensionManagement* management =
+      ExtensionManagementFactory::GetForBrowserContext(context);
+  ExtensionManagement::InstallationMode mode =
+      extension ? management->GetInstallationMode(extension)
+                : management->GetInstallationMode(extension_id,
+                                                  /*update_url=*/std::string());
+  return mode == ExtensionManagement::INSTALLATION_BLOCKED ||
+         mode == ExtensionManagement::INSTALLATION_REMOVED;
+}
+
+bool IsExtensionInstalled(content::BrowserContext* context,
+                          const std::string& extension_id) {
+  auto* registry = ExtensionRegistry::Get(context);
+  // May be nullptr in unit tests.
+  return registry && registry->GetInstalledExtension(extension_id);
+}
+
+bool IsExternalExtensionUninstalled(content::BrowserContext* context,
+                                    const std::string& extension_id) {
+  auto* prefs = ExtensionPrefs::Get(context);
+  // May be nullptr in unit tests.
+  return prefs && prefs->IsExternalExtensionUninstalled(extension_id);
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/web_applications/external_web_app_manager.cc b/chrome/browser/web_applications/external_web_app_manager.cc
index bcb899a..7c43a3e 100644
--- a/chrome/browser/web_applications/external_web_app_manager.cc
+++ b/chrome/browser/web_applications/external_web_app_manager.cc
@@ -31,6 +31,7 @@
 #include "chrome/browser/web_applications/components/pending_app_manager.h"
 #include "chrome/browser/web_applications/components/web_app_constants.h"
 #include "chrome/browser/web_applications/components/web_app_install_utils.h"
+#include "chrome/browser/web_applications/extension_status_utils.h"
 #include "chrome/browser/web_applications/external_web_app_utils.h"
 #include "chrome/browser/web_applications/preinstalled_web_apps.h"
 #include "chrome/common/chrome_features.h"
@@ -246,31 +247,35 @@
     parsed_configs.options_list.push_back(std::move(options));
   parsed_configs.disabled_count += preinstalled_web_apps.disabled_count;
 
-  // Save this as we may remove apps due to user uninstall (not the same as
-  // being disabled).
+  // Track this separately as we may remove apps due to user uninstall (not the
+  // same as being disabled).
   int enabled_count = parsed_configs.options_list.size();
 
-  // Remove web apps whose replace target was uninstalled.
-  if (extensions::ExtensionSystem::Get(profile_)) {
-    auto* extension_prefs = extensions::ExtensionPrefs::Get(profile_);
-    auto* extension_registry = extensions::ExtensionRegistry::Get(profile_);
-
-    base::EraseIf(
-        parsed_configs.options_list,
-        [&](const ExternalInstallOptions& options) {
-          for (const AppId& app_id : options.uninstall_and_replace) {
-            if (extension_registry->GetInstalledExtension(app_id))
-              return false;
+  base::EraseIf(
+      parsed_configs.options_list, [&](const ExternalInstallOptions& options) {
+        // Remove if any blocked by admin policy.
+        for (const AppId& app_id : options.uninstall_and_replace) {
+          if (extensions::IsExtensionBlockedByPolicy(profile_, app_id)) {
+            ++parsed_configs.disabled_count;
+            --enabled_count;
+            return true;
           }
+        }
 
-          for (const AppId& app_id : options.uninstall_and_replace) {
-            if (extension_prefs->IsExternalExtensionUninstalled(app_id))
-              return true;
-          }
+        // Keep if any installed.
+        for (const AppId& app_id : options.uninstall_and_replace) {
+          if (extensions::IsExtensionInstalled(profile_, app_id))
+            return false;
+        }
 
-          return false;
-        });
-  }
+        // Remove if any previously uninstalled.
+        for (const AppId& app_id : options.uninstall_and_replace) {
+          if (extensions::IsExternalExtensionUninstalled(profile_, app_id))
+            return true;
+        }
+
+        return false;
+      });
 
   base::UmaHistogramCounts100(ExternalWebAppManager::kHistogramEnabledCount,
                               enabled_count);
diff --git a/chrome/browser/web_applications/external_web_app_manager_unittest.cc b/chrome/browser/web_applications/external_web_app_manager_unittest.cc
index 9486376..a062612 100644
--- a/chrome/browser/web_applications/external_web_app_manager_unittest.cc
+++ b/chrome/browser/web_applications/external_web_app_manager_unittest.cc
@@ -19,13 +19,16 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/scoped_path_override.h"
+#include "chrome/browser/extensions/extension_management_test_util.h"
 #include "chrome/browser/supervised_user/supervised_user_constants.h"
 #include "chrome/browser/web_applications/components/external_app_install_features.h"
 #include "chrome/browser/web_applications/components/web_app_constants.h"
+#include "chrome/browser/web_applications/preinstalled_web_apps.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/account_id/account_id.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
@@ -186,6 +189,53 @@
   DISALLOW_COPY_AND_ASSIGN(ExternalWebAppManagerTest);
 };
 
+TEST_F(ExternalWebAppManagerTest, ReplacementExtensionBlockedByPolicy) {
+  using PolicyUpdater = extensions::ExtensionManagementPrefUpdater<
+      sync_preferences::TestingPrefServiceSyncable>;
+  auto test_profile = std::make_unique<TestingProfile>();
+  sync_preferences::TestingPrefServiceSyncable* prefs =
+      test_profile->GetTestingPrefService();
+
+  ScopedTestingPreinstalledAppData scoped_preinstalled_apps;
+  GURL install_url("https://test.app");
+  constexpr char kExtensionId[] = "abcdefghijklmnopabcdefghijklmnop";
+  scoped_preinstalled_apps.apps.push_back({
+      .install_url = install_url,
+      .feature_name = nullptr,
+      .app_id_to_replace = kExtensionId,
+  });
+
+  auto expect_present = [&]() {
+    std::vector<ExternalInstallOptions> options_list =
+        LoadApps(/*test_dir=*/"", test_profile.get());
+    ASSERT_EQ(options_list.size(), 1u);
+    EXPECT_EQ(options_list[0].install_url, install_url);
+  };
+
+  auto expect_not_present = [&]() {
+    std::vector<ExternalInstallOptions> options_list =
+        LoadApps(/*test_dir=*/"", test_profile.get());
+    ASSERT_EQ(options_list.size(), 0u);
+  };
+
+  expect_present();
+
+  PolicyUpdater(prefs).SetBlocklistedByDefault(false);
+  expect_present();
+
+  PolicyUpdater(prefs).SetBlocklistedByDefault(true);
+  expect_not_present();
+
+  PolicyUpdater(prefs).SetIndividualExtensionInstallationAllowed(kExtensionId,
+                                                                 true);
+  expect_present();
+
+  PolicyUpdater(prefs).SetBlocklistedByDefault(false);
+  PolicyUpdater(prefs).SetIndividualExtensionInstallationAllowed(kExtensionId,
+                                                                 false);
+  expect_not_present();
+}
+
 // Only Chrome OS parses config files.
 #if defined(OS_CHROMEOS)
 TEST_F(ExternalWebAppManagerTest, GoodJson) {
diff --git a/chrome/browser/web_applications/preinstalled_web_apps.cc b/chrome/browser/web_applications/preinstalled_web_apps.cc
index 5296f0f..ab8d068 100644
--- a/chrome/browser/web_applications/preinstalled_web_apps.cc
+++ b/chrome/browser/web_applications/preinstalled_web_apps.cc
@@ -46,7 +46,8 @@
   PreinstalledWebApps result;
 
   for (const PreinstalledAppData& app_data : GetPreinstalledAppData()) {
-    if (!IsExternalAppInstallFeatureEnabled(app_data.feature_name)) {
+    if (app_data.feature_name &&
+        !IsExternalAppInstallFeatureEnabled(app_data.feature_name)) {
       ++result.disabled_count;
       continue;
     }
diff --git a/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/FeedActionsHandler.java b/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/FeedActionsHandler.java
index 713f2a1..90f980a3 100644
--- a/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/FeedActionsHandler.java
+++ b/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/FeedActionsHandler.java
@@ -4,6 +4,10 @@
 
 package org.chromium.chrome.browser.xsurface;
 
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
 import java.util.Map;
 
 /**
@@ -24,6 +28,13 @@
     default void processThereAndBackAgainData(byte[] data) {}
 
     /**
+     * Sends data back to the server when content is clicked and provides the corresponding view
+     * through |actionSourceView| which can be null.
+     */
+    @Deprecated
+    default void processThereAndBackAgainData(byte[] data, @Nullable View actionSourceView) {}
+
+    /**
      * Stores a view FeedAction for eventual upload. 'data' is a serialized FeedAction protobuf
      * message.
      */
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 047ef7f..b762e601 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-master-1601575120-46ddcdbe4da420985f324196a05692689b215d89.profdata
+chrome-linux-master-1601596804-53a4ffa238a2dd27e204a4d6f101c3543de8d4a0.profdata
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 84f07d2..6a4afa2f 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -66,7 +66,7 @@
 #if !defined(OS_ANDROID)
 // App Service related flags. See components/services/app_service/README.md.
 const base::Feature kAppServiceAdaptiveIcon{"AppServiceAdaptiveIcon",
-                                            base::FEATURE_ENABLED_BY_DEFAULT};
+                                            base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kAppServiceExternalProtocol{
     "AppServiceExternalProtocol", base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kAppServiceIntentHandling{"AppServiceIntentHandling",
@@ -379,12 +379,17 @@
 // floc id is first computed for a browsing session or is refreshed due to a
 // long period of time has passed since the last computation.
 const base::Feature kFlocIdComputedEventLogging{
-    "FlocIdComputedEventLogging", base::FEATURE_DISABLED_BY_DEFAULT};
+    "FlocIdComputedEventLogging", base::FEATURE_ENABLED_BY_DEFAULT};
 
 // If enabled, a computed floc will be invalidated if it appears in a blocklist.
 const base::Feature kFlocIdBlocklistFiltering{
     "FlocIdBlocklistFiltering", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// If enabled, the sim-hash floc computed from history will be further encoded
+// based on the sorting-lsh.
+const base::Feature kFlocIdSortingLshBasedComputation{
+    "FlocIdSortingLshBasedComputation", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables Focus Mode which brings up a PWA-like window look.
 const base::Feature kFocusMode{"FocusMode", base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 02a63426..4f22f07 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -249,6 +249,9 @@
 extern const base::Feature kFlocIdBlocklistFiltering;
 
 COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kFlocIdSortingLshBasedComputation;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kFocusMode;
 
 #if defined(OS_WIN)
diff --git a/chrome/common/extensions/api/identity.idl b/chrome/common/extensions/api/identity.idl
index d8d7e548..e05f91fe 100644
--- a/chrome/common/extensions/api/identity.idl
+++ b/chrome/common/extensions/api/identity.idl
@@ -94,6 +94,7 @@
   callback GetAccountsCallback = void (AccountInfo[] accounts);
   callback GetProfileUserInfoCallback = void (ProfileUserInfo userInfo);
   callback InvalidateAuthTokenCallback = void ();
+  callback ClearAllCachedAuthTokensCallback = void ();
   callback LaunchWebAuthFlowCallback = void (optional DOMString responseUrl);
 
   interface Functions {
@@ -160,6 +161,15 @@
         InvalidTokenDetails details,
         optional InvalidateAuthTokenCallback callback);
 
+    // Resets the state of the Identity API:
+    // - Removes all OAuth2 access tokens from the token cache
+    // - Removes user's account preferences
+    // - De-authorizes the user from all auth flows
+    //
+    // |callback| : Called when the state has been cleared.
+    static void clearAllCachedAuthTokens(
+        ClearAllCachedAuthTokensCallback callback);
+
     // Starts an auth flow at the specified URL.
     //
     // This method enables auth flows with non-Google identity
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index ba2f8309..9771f28 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -16,6 +16,7 @@
 import("//chrome/browser/page_load_metrics/integration_tests/sources.gni")
 import("//chrome/common/features.gni")
 import("//chrome/services/machine_learning/features.gni")
+import("//chrome/services/speech/buildflags.gni")
 import("//chrome/test/base/js2gtest.gni")
 import("//chrome/test/include_js_tests.gni")
 import("//chromeos/assistant/assistant.gni")
@@ -1590,6 +1591,11 @@
       deps += [ "//chrome/app:chrome_dll_resources" ]
     }
 
+    if (enable_soda) {
+      sources +=
+          [ "../browser/speech/speech_recognition_service_browsertest.cc" ]
+    }
+
     if (use_ozone) {
       deps += [ "//ui/ozone" ]
     }
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index ce9809f..586966d 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -4721,8 +4721,7 @@
   },
 
   "EnableDeprecatedWebPlatformFeatures": {
-    "os": ["win", "linux", "mac", "chromeos"],
-    "policy_pref_mapping_test": []
+    "note": "This policy has been removed, as of M88."
   },
 
   "TouchVirtualKeyboardEnabled": {
diff --git a/chrome/test/data/webui/settings/chromeos/device_page_tests.js b/chrome/test/data/webui/settings/chromeos/device_page_tests.js
index 4f0dd422..4995bea 100644
--- a/chrome/test/data/webui/settings/chromeos/device_page_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/device_page_tests.js
@@ -667,8 +667,6 @@
       test('mouse', function() {
         expectLT(0, pointersPage.$$('#mouse').offsetHeight);
 
-        expectFalse(pointersPage.$$('#mouse settings-toggle-button').checked);
-
         const slider = assert(pointersPage.$$('#mouse settings-slider'));
         expectEquals(4, slider.pref.value);
         MockInteractions.pressAndReleaseKeyOn(
diff --git a/chromeos/components/diagnostics_ui/backend/system_data_provider.cc b/chromeos/components/diagnostics_ui/backend/system_data_provider.cc
index ea2f0c46..20e36701 100644
--- a/chromeos/components/diagnostics_ui/backend/system_data_provider.cc
+++ b/chromeos/components/diagnostics_ui/backend/system_data_provider.cc
@@ -31,6 +31,7 @@
 
 constexpr int kBatteryHealthRefreshIntervalInSeconds = 60;
 constexpr int kChargeStatusRefreshIntervalInSeconds = 15;
+constexpr int kMemoryUsageRefreshIntervalInSeconds = 10;
 constexpr int kMilliampsInAnAmp = 1000;
 
 void PopulateBoardName(const healthd::SystemInfo& system_info,
@@ -128,6 +129,12 @@
       out_battery_health.charge_full_design_milliamp_hours;
 }
 
+void PopulateMemoryUsage(const healthd::MemoryInfo& memory_info,
+                         mojom::MemoryUsage& out_memory_usage) {
+  out_memory_usage.total_memory_kib = memory_info.total_memory_kib;
+  out_memory_usage.free_memory_kib = memory_info.free_memory_kib;
+  out_memory_usage.available_memory_kib = memory_info.available_memory_kib;
+}
 }  // namespace
 
 SystemDataProvider::SystemDataProvider() {
@@ -187,6 +194,20 @@
   UpdateBatteryHealth();
 }
 
+void SystemDataProvider::ObserveMemoryUsage(
+    mojo::PendingRemote<mojom::MemoryUsageObserver> observer) {
+  memory_usage_observers_.Add(std::move(observer));
+
+  if (!memory_usage_timer_->IsRunning()) {
+    memory_usage_timer_->Start(
+        FROM_HERE,
+        base::TimeDelta::FromSeconds(kMemoryUsageRefreshIntervalInSeconds),
+        base::BindRepeating(&SystemDataProvider::UpdateMemoryUsage,
+                            base::Unretained(this)));
+  }
+  UpdateMemoryUsage();
+}
+
 void SystemDataProvider::PowerChanged(
     const power_manager::PowerSupplyProperties& proto) {
   if (battery_charge_status_observers_.empty()) {
@@ -211,6 +232,11 @@
   battery_health_timer_ = std::move(timer);
 }
 
+void SystemDataProvider::SetMemoryUsageTimerForTesting(
+    std::unique_ptr<base::RepeatingTimer> timer) {
+  memory_usage_timer_ = std::move(timer);
+}
+
 void SystemDataProvider::OnSystemInfoProbeResponse(
     GetSystemInfoCallback callback,
     healthd::TelemetryInfoPtr info_ptr) {
@@ -301,6 +327,15 @@
                      base::Unretained(this)));
 }
 
+void SystemDataProvider::UpdateMemoryUsage() {
+  BindCrosHealthdProbeServiceIfNeccessary();
+
+  probe_service_->ProbeTelemetryInfo(
+      {ProbeCategories::kMemory},
+      base::BindOnce(&SystemDataProvider::OnMemoryUsageUpdated,
+                     base::Unretained(this)));
+}
+
 void SystemDataProvider::OnBatteryChargeStatusUpdated(
     const base::Optional<PowerSupplyProperties>& power_supply_properties,
     healthd::TelemetryInfoPtr info_ptr) {
@@ -359,6 +394,29 @@
   NotifyBatteryHealthObservers(battery_health);
 }
 
+void SystemDataProvider::OnMemoryUsageUpdated(
+    healthd::TelemetryInfoPtr info_ptr) {
+  mojom::MemoryUsagePtr memory_usage = mojom::MemoryUsage::New();
+
+  if (info_ptr.is_null()) {
+    LOG(ERROR) << "Null response from croshealthd::ProbeTelemetryInfo.";
+    NotifyMemoryUsageObservers(memory_usage);
+    memory_usage_timer_.reset();
+    return;
+  }
+
+  const healthd::MemoryInfo* memory_info = GetMemoryInfo(*info_ptr);
+  if (memory_info == nullptr) {
+    LOG(ERROR) << "No MemoryInfo in response from cros_healthd.";
+    NotifyMemoryUsageObservers(memory_usage);
+    memory_usage_timer_.reset();
+    return;
+  }
+
+  PopulateMemoryUsage(*memory_info, *memory_usage.get());
+  NotifyMemoryUsageObservers(memory_usage);
+}
+
 void SystemDataProvider::NotifyBatteryChargeStatusObservers(
     const mojom::BatteryChargeStatusPtr& battery_charge_status) {
   for (auto& observer : battery_charge_status_observers_) {
@@ -373,6 +431,13 @@
   }
 }
 
+void SystemDataProvider::NotifyMemoryUsageObservers(
+    const mojom::MemoryUsagePtr& memory_usage) {
+  for (auto& observer : memory_usage_observers_) {
+    observer->OnMemoryUsageUpdated(memory_usage.Clone());
+  }
+}
+
 void SystemDataProvider::BindCrosHealthdProbeServiceIfNeccessary() {
   if (!probe_service_ || !probe_service_.is_connected()) {
     cros_healthd::ServiceConnection::GetInstance()->GetProbeService(
diff --git a/chromeos/components/diagnostics_ui/backend/system_data_provider.h b/chromeos/components/diagnostics_ui/backend/system_data_provider.h
index 70959957..3b58ba92 100644
--- a/chromeos/components/diagnostics_ui/backend/system_data_provider.h
+++ b/chromeos/components/diagnostics_ui/backend/system_data_provider.h
@@ -39,6 +39,8 @@
       override;
   void ObserveBatteryHealth(
       mojo::PendingRemote<mojom::BatteryHealthObserver> observer) override;
+  void ObserveMemoryUsage(
+      mojo::PendingRemote<mojom::MemoryUsageObserver> observer) override;
 
   // PowerManagerClient::Observer:
   void PowerChanged(const power_manager::PowerSupplyProperties& proto) override;
@@ -49,6 +51,9 @@
   void SetBatteryHealthTimerForTesting(
       std::unique_ptr<base::RepeatingTimer> timer);
 
+  void SetMemoryUsageTimerForTesting(
+      std::unique_ptr<base::RepeatingTimer> timer);
+
  private:
   void BindCrosHealthdProbeServiceIfNeccessary();
 
@@ -66,12 +71,16 @@
 
   void UpdateBatteryHealth();
 
+  void UpdateMemoryUsage();
+
   void NotifyBatteryChargeStatusObservers(
       const mojom::BatteryChargeStatusPtr& battery_charge_status);
 
   void NotifyBatteryHealthObservers(
       const mojom::BatteryHealthPtr& battery_health);
 
+  void NotifyMemoryUsageObservers(const mojom::MemoryUsagePtr& memory_usage);
+
   void OnBatteryChargeStatusUpdated(
       const base::Optional<power_manager::PowerSupplyProperties>&
           power_supply_properties,
@@ -79,13 +88,17 @@
 
   void OnBatteryHealthUpdated(cros_healthd::mojom::TelemetryInfoPtr info_ptr);
 
+  void OnMemoryUsageUpdated(cros_healthd::mojom::TelemetryInfoPtr info_ptr);
+
   mojo::Remote<cros_healthd::mojom::CrosHealthdProbeService> probe_service_;
   mojo::RemoteSet<mojom::BatteryChargeStatusObserver>
       battery_charge_status_observers_;
   mojo::RemoteSet<mojom::BatteryHealthObserver> battery_health_observers_;
+  mojo::RemoteSet<mojom::MemoryUsageObserver> memory_usage_observers_;
 
   std::unique_ptr<base::RepeatingTimer> battery_charge_status_timer_;
   std::unique_ptr<base::RepeatingTimer> battery_health_timer_;
+  std::unique_ptr<base::RepeatingTimer> memory_usage_timer_;
 };
 
 }  // namespace diagnostics
diff --git a/chromeos/components/diagnostics_ui/backend/system_data_provider_unittest.cc b/chromeos/components/diagnostics_ui/backend/system_data_provider_unittest.cc
index feef02b..9db7f89 100644
--- a/chromeos/components/diagnostics_ui/backend/system_data_provider_unittest.cc
+++ b/chromeos/components/diagnostics_ui/backend/system_data_provider_unittest.cc
@@ -183,7 +183,7 @@
       CreateCrosHealthdBatteryInfoResponse(vendor, charge_full_design);
   SetProbeTelemetryInfoResponse(std::move(battery_info), /*cpu_info=*/nullptr,
                                 /*memory_info=*/nullptr,
-                                /*memory_info=*/nullptr);
+                                /*system_info=*/nullptr);
 }
 
 void SetCrosHealthdBatteryChargeStatusResponse(double charge_now,
@@ -192,7 +192,7 @@
       CreateCrosHealthdBatteryChargeStatusResponse(charge_now, current_now);
   SetProbeTelemetryInfoResponse(std::move(battery_info), /*cpu_info=*/nullptr,
                                 /*memory_info=*/nullptr,
-                                /*memory_info=*/nullptr);
+                                /*system_info=*/nullptr);
 }
 
 void SetCrosHealthdBatteryHealthResponse(double charge_full_now,
@@ -203,7 +203,19 @@
                                              charge_full_design, cycle_count);
   SetProbeTelemetryInfoResponse(std::move(battery_info), /*cpu_info=*/nullptr,
                                 /*memory_info=*/nullptr,
-                                /*memory_info=*/nullptr);
+                                /*system_info=*/nullptr);
+}
+
+void SetCrosHealthdMemoryUsageResponse(uint32_t total_memory_kib,
+                                       uint32_t free_memory_kib,
+                                       uint32_t available_memory_kib) {
+  cros_healthd::mojom::MemoryInfoPtr memory_info =
+      cros_healthd::mojom::MemoryInfo::New(total_memory_kib, free_memory_kib,
+                                           available_memory_kib,
+                                           /*page_faults_since_last_boot=*/0);
+  SetProbeTelemetryInfoResponse(/*battery_info=*/nullptr, /*cpu_info=*/nullptr,
+                                /*memory_info=*/std::move(memory_info),
+                                /*system_info=*/nullptr);
 }
 
 bool AreValidPowerTimes(int64_t time_to_full, int64_t time_to_empty) {
@@ -315,6 +327,15 @@
   EXPECT_EQ(expected_battery_wear_percentage, update->battery_wear_percentage);
 }
 
+void VerifyMemoryUsageResult(const mojom::MemoryUsagePtr& update,
+                             uint32_t expected_total_memory_kib,
+                             uint32_t expected_free_memory_kib,
+                             uint32_t expected_available_memory_kib) {
+  EXPECT_EQ(expected_total_memory_kib, update->total_memory_kib);
+  EXPECT_EQ(expected_free_memory_kib, update->free_memory_kib);
+  EXPECT_EQ(expected_available_memory_kib, update->available_memory_kib);
+}
+
 }  // namespace
 
 struct FakeBatteryChargeStatusObserver
@@ -345,6 +366,19 @@
   mojo::Receiver<mojom::BatteryHealthObserver> receiver{this};
 };
 
+struct FakeMemoryUsageObserver : public mojom::MemoryUsageObserver {
+  // mojom::MemoryUsageObserver
+  void OnMemoryUsageUpdated(mojom::MemoryUsagePtr status_ptr) override {
+    updates.emplace_back(std::move(status_ptr));
+  }
+
+  // Tracks calls to OnMemoryUsageUpdated. Each call adds an element to
+  // the vector.
+  std::vector<mojom::MemoryUsagePtr> updates;
+
+  mojo::Receiver<mojom::MemoryUsageObserver> receiver{this};
+};
+
 class SystemDataProviderTest : public testing::Test {
  public:
   SystemDataProviderTest() {
@@ -559,5 +593,53 @@
                      charge_full_design, new_cycle_count);
 }
 
+TEST_F(SystemDataProviderTest, MemoryUsageObserver) {
+  // Setup Timer
+  auto timer = std::make_unique<base::MockRepeatingTimer>();
+  auto* timer_ptr = timer.get();
+  system_data_provider_->SetMemoryUsageTimerForTesting(std::move(timer));
+
+  // Setup initial data
+  const uint32_t total_memory_kib = 10000;
+  const uint32_t free_memory_kib = 2000;
+  const uint32_t available_memory_kib = 4000;
+
+  SetCrosHealthdMemoryUsageResponse(total_memory_kib, free_memory_kib,
+                                    available_memory_kib);
+
+  // Registering as an observer should trigger one update.
+  FakeMemoryUsageObserver memory_usage_observer;
+  system_data_provider_->ObserveMemoryUsage(
+      memory_usage_observer.receiver.BindNewPipeAndPassRemote());
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(1u, memory_usage_observer.updates.size());
+  VerifyMemoryUsageResult(memory_usage_observer.updates[0], total_memory_kib,
+                          free_memory_kib, available_memory_kib);
+
+  // Firing the timer should trigger another.
+  timer_ptr->Fire();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(2u, memory_usage_observer.updates.size());
+  VerifyMemoryUsageResult(memory_usage_observer.updates[1], total_memory_kib,
+                          free_memory_kib, available_memory_kib);
+
+  // Updating the information in Croshealthd does not trigger an update until
+  // the timer fires
+  const uint32_t new_available_memory_kib = available_memory_kib + 1000;
+  SetCrosHealthdMemoryUsageResponse(total_memory_kib, free_memory_kib,
+                                    new_available_memory_kib);
+
+  EXPECT_EQ(2u, memory_usage_observer.updates.size());
+
+  timer_ptr->Fire();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(3u, memory_usage_observer.updates.size());
+  VerifyMemoryUsageResult(memory_usage_observer.updates[2], total_memory_kib,
+                          free_memory_kib, new_available_memory_kib);
+}
+
 }  // namespace diagnostics
 }  // namespace chromeos
diff --git a/chromeos/components/diagnostics_ui/mojom/system_data_provider.mojom b/chromeos/components/diagnostics_ui/mojom/system_data_provider.mojom
index 1bba4fe5..d31d87c8 100644
--- a/chromeos/components/diagnostics_ui/mojom/system_data_provider.mojom
+++ b/chromeos/components/diagnostics_ui/mojom/system_data_provider.mojom
@@ -63,6 +63,13 @@
   int8 battery_wear_percentage;
 };
 
+// Contains information about the usage of memory on the device.
+struct MemoryUsage {
+  uint32 total_memory_kib;
+  uint32 available_memory_kib;
+  uint32 free_memory_kib;
+};
+
 // Implemented by clients that wish to be updated periodically about changes to
 // the battery charge status.
 interface BatteryChargeStatusObserver {
@@ -82,6 +89,15 @@
   OnBatteryHealthUpdated(BatteryHealth battery_health);
 };
 
+// Implemented by clients that wish to be updated periodically about the
+// memory usage of the device.
+interface MemoryUsageObserver {
+  // OnMemoryUsageUpdated calls can be triggered by either of 2 conditions:
+  // 1) A MemoryUsageObserver is registered with SystemDataProvider
+  // 2) A periodic update is sent by SystemDataProvider
+  OnMemoryUsageUpdated(MemoryUsage memory_usage);
+};
+
 // Provides telemetric information about the system. This API is exposed to the
 // Diagnostics SWA.
 interface SystemDataProvider {
@@ -98,6 +114,9 @@
   ObserveBatteryChargeStatus(
       pending_remote<BatteryChargeStatusObserver> observer);
 
-  // Registers an observer of BatteryHealth inforamation.
+  // Registers an observer of BatteryHealth information.
   ObserveBatteryHealth(pending_remote<BatteryHealthObserver> observer);
+
+  // Registers an observer of MemoryUsage information.
+  ObserveMemoryUsage(pending_remote<MemoryUsageObserver> observer);
 };
diff --git a/chromeos/components/help_app_ui/help_app_page_handler.cc b/chromeos/components/help_app_ui/help_app_page_handler.cc
index 3d6ca46c..8f5adce 100644
--- a/chromeos/components/help_app_ui/help_app_page_handler.cc
+++ b/chromeos/components/help_app_ui/help_app_page_handler.cc
@@ -6,13 +6,18 @@
 
 #include <utility>
 
+#include "base/feature_list.h"
 #include "chromeos/components/help_app_ui/help_app_ui.h"
 #include "chromeos/components/help_app_ui/help_app_ui_delegate.h"
+#include "chromeos/constants/chromeos_features.h"
 
 HelpAppPageHandler::HelpAppPageHandler(
     chromeos::HelpAppUI* help_app_ui,
     mojo::PendingReceiver<help_app_ui::mojom::PageHandler> receiver)
-    : receiver_(this, std::move(receiver)), help_app_ui_(help_app_ui) {}
+    : receiver_(this, std::move(receiver)),
+      help_app_ui_(help_app_ui),
+      is_lss_enabled_(base::FeatureList::IsEnabled(
+          chromeos::features::kHelpAppSearchServiceIntegration)) {}
 
 HelpAppPageHandler::~HelpAppPageHandler() = default;
 
@@ -25,3 +30,7 @@
 void HelpAppPageHandler::ShowParentalControls() {
   help_app_ui_->delegate()->ShowParentalControls();
 }
+
+void HelpAppPageHandler::IsLssEnabled(IsLssEnabledCallback callback) {
+  std::move(callback).Run(is_lss_enabled_);
+}
diff --git a/chromeos/components/help_app_ui/help_app_page_handler.h b/chromeos/components/help_app_ui/help_app_page_handler.h
index 1eeec71..c71a1b4 100644
--- a/chromeos/components/help_app_ui/help_app_page_handler.h
+++ b/chromeos/components/help_app_ui/help_app_page_handler.h
@@ -28,10 +28,12 @@
   // help_app_ui::mojom::PageHandler:
   void OpenFeedbackDialog(OpenFeedbackDialogCallback callback) override;
   void ShowParentalControls() override;
+  void IsLssEnabled(IsLssEnabledCallback callback) override;
 
  private:
   mojo::Receiver<help_app_ui::mojom::PageHandler> receiver_;
   chromeos::HelpAppUI* help_app_ui_;  // Owns |this|.
+  bool is_lss_enabled_;
 };
 
 #endif  // CHROMEOS_COMPONENTS_HELP_APP_UI_HELP_APP_PAGE_HANDLER_H_
diff --git a/chromeos/components/help_app_ui/help_app_ui.mojom b/chromeos/components/help_app_ui/help_app_ui.mojom
index 0fa3ecf..95b8e92f5 100644
--- a/chromeos/components/help_app_ui/help_app_ui.mojom
+++ b/chromeos/components/help_app_ui/help_app_ui.mojom
@@ -19,4 +19,7 @@
 
   // Opens the parental controls part of OS settings.
   ShowParentalControls();
+
+  // Returns true if Local Search Service integration is enabled.
+  IsLssEnabled() => (bool enabled);
 };
diff --git a/chromeos/components/help_app_ui/resources/browser_proxy.js b/chromeos/components/help_app_ui/resources/browser_proxy.js
index 95ef49c..aa9743bb 100644
--- a/chromeos/components/help_app_ui/resources/browser_proxy.js
+++ b/chromeos/components/help_app_ui/resources/browser_proxy.js
@@ -11,16 +11,32 @@
     help_app.handler.$.bindNewPipeAndPassReceiver());
 
 // Set up an index remote to talk to Local Search Service.
-// TODO(b/166047521): Define and use API to make calls to this from untrusted
-// context.
+/** @type {!chromeos.localSearchService.mojom.IndexProxyRemote} */
 const indexRemote = chromeos.localSearchService.mojom.IndexProxy.getRemote();
 
 const GUEST_ORIGIN = 'chrome-untrusted://help-app';
-const guestFrame = /** @type {!HTMLIFrameElement} */ (
-    document.createElement('iframe'));
+const guestFrame =
+    /** @type {!HTMLIFrameElement} */ (document.createElement('iframe'));
 guestFrame.src = `${GUEST_ORIGIN}${location.pathname}`;
 document.body.appendChild(guestFrame);
 
+// Cached result whether Local Search Service is enabled.
+/** @type {Promise<boolean>} */
+const isLssEnabled =
+    help_app.handler.isLssEnabled().then(result => result.enabled);
+
+/**
+ * @param {string} s
+ * @return {!mojoBase.mojom.String16Spec}
+ */
+function toString16(s) {
+  return /** @type {!mojoBase.mojom.String16Spec} */ (
+      {data: Array.from(s, (/** @type {string} */ c) => c.charCodeAt())});
+}
+const TITLE_ID = 'title';
+const BODY_ID = 'body';
+const CATEGORY_ID = 'main-category';
+
 /**
  * A pipe through which we can send messages to the guest frame.
  * Use an undefined `target` to find the <iframe> automatically.
@@ -28,9 +44,9 @@
  * throw exceptions that are handled on the other side of the pipe (in the guest
  * frame), not on this side.
  */
-const guestMessagePipe =
-    new MessagePipe('chrome-untrusted://help-app', /*target=*/ undefined,
-        /*rethrowErrors=*/ false);
+const guestMessagePipe = new MessagePipe(
+    'chrome-untrusted://help-app', /*target=*/ undefined,
+    /*rethrowErrors=*/ false);
 
 guestMessagePipe.registerHandler(Message.OPEN_FEEDBACK_DIALOG, () => {
   return help_app.handler.openFeedbackDialog();
@@ -39,3 +55,89 @@
 guestMessagePipe.registerHandler(Message.SHOW_PARENTAL_CONTROLS, () => {
   help_app.handler.showParentalControls();
 });
+
+guestMessagePipe.registerHandler(
+    Message.ADD_OR_UPDATE_SEARCH_INDEX, async (message) => {
+      if (!(await isLssEnabled)) {
+        return;
+      }
+      const data_from_app =
+          /** @type {!Array<!helpApp.SearchableItem>} */ (message);
+      const data_to_send = data_from_app.map(searchable_item => {
+        const contents = [
+          {
+            id: TITLE_ID,
+            content: toString16(searchable_item.title),
+            weight: 1.0,
+          },
+          {
+            id: BODY_ID,
+            content: toString16(searchable_item.body),
+            weight: 0.2,
+          },
+          {
+            id: CATEGORY_ID,
+            content: toString16(searchable_item.mainCategoryName),
+            weight: 0.1,
+          },
+        ];
+        return {
+          id: searchable_item.id,
+          contents,
+          locale: searchable_item.locale,
+        };
+      });
+      indexRemote.addOrUpdate(data_to_send);
+    });
+
+guestMessagePipe.registerHandler(Message.CLEAR_SEARCH_INDEX, async () => {
+  if (!(await isLssEnabled)) {
+    return;
+  }
+  // TODO(b/166047521): Clear the index when that method is available.
+});
+
+guestMessagePipe.registerHandler(
+    Message.FIND_IN_SEARCH_INDEX, async (message) => {
+      if (!(await isLssEnabled)) {
+        return {results: null};
+      }
+      const response = await indexRemote.find(
+          toString16((/** @type {{query: string}} */ (message)).query),
+          /*max_results=*/ 100);
+      if (response.status !==
+              chromeos.localSearchService.mojom.ResponseStatus.kSuccess ||
+          !response.results) {
+        return {results: null};
+      }
+      const search_results =
+          /** @type {!Array<!chromeos.localSearchService.mojom.Result>} */ (
+              response.results);
+      // Sort results by decreasing score.
+      search_results.sort((a, b) => b.score - a.score);
+      /** @type {!Array<!helpApp.SearchResult>} */
+      const results = search_results.map(result => {
+        /** @type {!Array<!helpApp.Position>} */
+        const titlePositions = [];
+        /** @type {!Array<!helpApp.Position>} */
+        const bodyPositions = [];
+        for (const position of result.positions) {
+          if (position.contentId === TITLE_ID) {
+            titlePositions.push(
+                {length: position.length, start: position.start});
+          } else if (position.contentId === BODY_ID) {
+            bodyPositions.push(
+                {length: position.length, start: position.start});
+          }
+        }
+        // Sort positions by start index.
+        titlePositions.sort((a, b) => a.start - b.start);
+        bodyPositions.sort((a, b) => a.start - b.start);
+        return {
+          id: result.id,
+          titlePositions,
+          bodyPositions,
+        };
+      });
+      return {results};
+    });
diff --git a/chromeos/components/help_app_ui/resources/help_app.externs.js b/chromeos/components/help_app_ui/resources/help_app.externs.js
index 39cb0c0..d740058 100644
--- a/chromeos/components/help_app_ui/resources/help_app.externs.js
+++ b/chromeos/components/help_app_ui/resources/help_app.externs.js
@@ -77,12 +77,14 @@
 /** @type {string} */
 helpApp.SearchResult.prototype.id;
 /**
- * List of positions corresponding to the title. Used in snippet.
+ * List of positions corresponding to the title, sorted by start index. Used in
+ * snippet.
  * @type {?Array<!helpApp.Position>}
  */
 helpApp.SearchResult.prototype.titlePositions;
 /**
- * List of positions corresponding to the body. Used in snippet.
+ * List of positions corresponding to the body sorted by start index. Used in
+ * snippet.
  * @type {?Array<!helpApp.Position>}
  */
 helpApp.SearchResult.prototype.bodyPositions;
diff --git a/chromeos/components/help_app_ui/resources/message_types.js b/chromeos/components/help_app_ui/resources/message_types.js
index 3dfb4207..b97f3fc 100644
--- a/chromeos/components/help_app_ui/resources/message_types.js
+++ b/chromeos/components/help_app_ui/resources/message_types.js
@@ -13,5 +13,8 @@
  */
 const Message = {
   OPEN_FEEDBACK_DIALOG: 'open-feedback-dialog',
-  SHOW_PARENTAL_CONTROLS: 'show-parental-controls'
+  SHOW_PARENTAL_CONTROLS: 'show-parental-controls',
+  ADD_OR_UPDATE_SEARCH_INDEX: 'add-or-update-search-index',
+  CLEAR_SEARCH_INDEX: 'clear-search-index',
+  FIND_IN_SEARCH_INDEX: 'find-in-search-index'
 };
diff --git a/chromeos/components/help_app_ui/resources/receiver.js b/chromeos/components/help_app_ui/resources/receiver.js
index c49136f6..bf54dcb 100644
--- a/chromeos/components/help_app_ui/resources/receiver.js
+++ b/chromeos/components/help_app_ui/resources/receiver.js
@@ -24,15 +24,27 @@
   async showParentalControls() {
     await parentMessagePipe.sendMessage(Message.SHOW_PARENTAL_CONTROLS);
   },
-  // TODO(b/166047521): Complete the implementation of these.
-  async addOrUpdateSearchIndex() {
-    return;
+  /**
+   * @override
+   * @param {!Array<!helpApp.SearchableItem>} data
+   */
+  async addOrUpdateSearchIndex(data) {
+    await parentMessagePipe.sendMessage(
+        Message.ADD_OR_UPDATE_SEARCH_INDEX, data);
   },
   async clearSearchIndex() {
+    // TODO(b/166047521): Send the message when clear search index has been
+    // implemented. the index when that method is available.
     return;
   },
-  async findInSearchIndex() {
-    return {results: null};
+  /**
+   * @override
+   * @param {string} query
+   * @return {!Promise<!helpApp.FindResponse>}
+   */
+  findInSearchIndex(query) {
+    return /** @type {!Promise<!helpApp.FindResponse>} */ (
+        parentMessagePipe.sendMessage(Message.FIND_IN_SEARCH_INDEX, {query}));
   },
 };
 
diff --git a/chromeos/components/help_app_ui/test/help_app_ui_browsertest.js b/chromeos/components/help_app_ui/test/help_app_ui_browsertest.js
index a36e84ce..27188f1a 100644
--- a/chromeos/components/help_app_ui/test/help_app_ui_browsertest.js
+++ b/chromeos/components/help_app_ui/test/help_app_ui_browsertest.js
@@ -44,8 +44,6 @@
   }
 };
 
-const toString16 = s => ({data: Array.from(s, c => c.charCodeAt())});
-
 // Tests that chrome://help-app goes somewhere instead of 404ing or crashing.
 TEST_F('HelpAppUIBrowserTest', 'HasChromeSchemeURL', () => {
   const guest = document.querySelector('iframe');
@@ -64,6 +62,7 @@
 
 // Tests that we can make calls to the LSS to search.
 TEST_F('HelpAppUIBrowserTest', 'CanSearchViaLSSIndex', async () => {
+  const toString16 = s => ({data: Array.from(s, c => c.charCodeAt())});
   const result = await indexRemote.find(toString16('search string!'), 100);
 
   // Status 3 corresponds to kEmptyIndex.
diff --git a/chromeos/components/phonehub/fake_find_my_device_controller.cc b/chromeos/components/phonehub/fake_find_my_device_controller.cc
index a85c009a..9781641 100644
--- a/chromeos/components/phonehub/fake_find_my_device_controller.cc
+++ b/chromeos/components/phonehub/fake_find_my_device_controller.cc
@@ -11,22 +11,33 @@
 
 FakeFindMyDeviceController::~FakeFindMyDeviceController() = default;
 
-bool FakeFindMyDeviceController::IsPhoneRinging() const {
-  return is_phone_ringing_;
+void FakeFindMyDeviceController::SetPhoneRingingState(
+    Status phone_ringing_status) {
+  if (phone_ringing_status_ == phone_ringing_status)
+    return;
+  phone_ringing_status_ = phone_ringing_status;
+  NotifyPhoneRingingStateChanged();
 }
 
 void FakeFindMyDeviceController::SetIsPhoneRingingInternal(
     bool is_phone_ringing) {
-  if (is_phone_ringing_ == is_phone_ringing)
+  Status phone_ringing_status =
+      is_phone_ringing ? Status::kRingingOn : Status::kRingingOff;
+
+  if (phone_ringing_status_ == Status::kRingingNotAvailable)
     return;
 
-  is_phone_ringing_ = is_phone_ringing;
-  NotifyPhoneRingingStateChanged();
+  SetPhoneRingingState(phone_ringing_status);
 }
 
 void FakeFindMyDeviceController::RequestNewPhoneRingingState(bool ringing) {
   SetIsPhoneRingingInternal(ringing);
 }
 
+FindMyDeviceController::Status
+FakeFindMyDeviceController::GetPhoneRingingStatus() {
+  return phone_ringing_status_;
+}
+
 }  // namespace phonehub
 }  // namespace chromeos
diff --git a/chromeos/components/phonehub/fake_find_my_device_controller.h b/chromeos/components/phonehub/fake_find_my_device_controller.h
index 60a57e97..514f1f7 100644
--- a/chromeos/components/phonehub/fake_find_my_device_controller.h
+++ b/chromeos/components/phonehub/fake_find_my_device_controller.h
@@ -15,13 +15,15 @@
   FakeFindMyDeviceController();
   ~FakeFindMyDeviceController() override;
 
+  void SetPhoneRingingState(Status status);
+
   // FindMyDeviceController:
-  bool IsPhoneRinging() const override;
   void SetIsPhoneRingingInternal(bool is_phone_ringing) override;
   void RequestNewPhoneRingingState(bool ringing) override;
+  Status GetPhoneRingingStatus() override;
 
  private:
-  bool is_phone_ringing_ = false;
+  Status phone_ringing_status_ = Status::kRingingOff;
 };
 
 }  // namespace phonehub
diff --git a/chromeos/components/phonehub/find_my_device_controller.h b/chromeos/components/phonehub/find_my_device_controller.h
index cd95a2b..e455dffd 100644
--- a/chromeos/components/phonehub/find_my_device_controller.h
+++ b/chromeos/components/phonehub/find_my_device_controller.h
@@ -22,20 +22,31 @@
     virtual void OnPhoneRingingStateChanged() = 0;
   };
 
+  enum class Status {
+    // The connected phone is not currently ringing.
+    kRingingOff = 0,
+    // The connected phone is currently ringing.
+    kRingingOn = 1,
+    // Ringing is not available if the phone's DoNotDisturb mode is enabled.
+    // To re-enable ringing, DoNotDisturb mode must be disabled.
+    kRingingNotAvailable = 2,
+  };
+
   FindMyDeviceController(const FindMyDeviceController&) = delete;
   FindMyDeviceController& operator=(const FindMyDeviceController&) = delete;
   virtual ~FindMyDeviceController();
 
-  // Returns whether the phone is ringing as a result of Find My Device
-  // functionality. Note that this function does not return true if the phone is
-  // ringing for another reason (e.g., a normal phone call).
-  virtual bool IsPhoneRinging() const = 0;
-
   // Note: Ringing the phone via Find My Device is not a synchronous operation,
   // since it requires sending a message to the connected phone. Use the
   // observer interface to be notified of when the state changes.
   virtual void RequestNewPhoneRingingState(bool ringing) = 0;
 
+  // Returns the current ringing state of the connected phone. There are three
+  // possible states (on, off, disabled). The status is a result of Find My
+  // Device Functionality. Note that this function does not return true if the
+  // phone is ringing for another reason (e.g., a normal phone call)
+  virtual Status GetPhoneRingingStatus() = 0;
+
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
 
diff --git a/chromeos/components/phonehub/find_my_device_controller_impl.cc b/chromeos/components/phonehub/find_my_device_controller_impl.cc
index 28cc5d9..17cda6a 100644
--- a/chromeos/components/phonehub/find_my_device_controller_impl.cc
+++ b/chromeos/components/phonehub/find_my_device_controller_impl.cc
@@ -5,26 +5,70 @@
 #include "chromeos/components/phonehub/find_my_device_controller_impl.h"
 
 #include "chromeos/components/multidevice/logging/logging.h"
+#include "chromeos/components/phonehub/message_sender.h"
 
 namespace chromeos {
 namespace phonehub {
 
-FindMyDeviceControllerImpl::FindMyDeviceControllerImpl() = default;
+FindMyDeviceControllerImpl::FindMyDeviceControllerImpl(
+    DoNotDisturbController* do_not_disturb_controller,
+    MessageSender* message_sender)
+    : do_not_disturb_controller_(do_not_disturb_controller),
+      message_sender_(message_sender) {
+  DCHECK(do_not_disturb_controller_);
+  DCHECK(message_sender_);
 
-FindMyDeviceControllerImpl::~FindMyDeviceControllerImpl() = default;
+  do_not_disturb_controller_->AddObserver(this);
+}
 
-bool FindMyDeviceControllerImpl::IsPhoneRinging() const {
-  return is_phone_ringing_;
+FindMyDeviceControllerImpl::~FindMyDeviceControllerImpl() {
+  do_not_disturb_controller_->RemoveObserver(this);
 }
 
 void FindMyDeviceControllerImpl::SetIsPhoneRingingInternal(
     bool is_phone_ringing) {
   is_phone_ringing_ = is_phone_ringing;
+  UpdateStatus();
+}
+
+FindMyDeviceController::Status
+FindMyDeviceControllerImpl::GetPhoneRingingStatus() {
+  return phone_ringing_status_;
 }
 
 void FindMyDeviceControllerImpl::RequestNewPhoneRingingState(bool ringing) {
+  if (phone_ringing_status_ == Status::kRingingNotAvailable) {
+    PA_LOG(WARNING) << "Cannot request new ringing status because DoNotDisturb "
+                    << "mode is enabled.";
+    return;
+  }
+
   PA_LOG(INFO) << "Attempting to set Find My Device phone ring state; new "
                << "value: " << ringing;
+  message_sender_->SendRingDeviceRequest(ringing);
+}
+
+void FindMyDeviceControllerImpl::OnDndStateChanged() {
+  UpdateStatus();
+}
+
+FindMyDeviceController::Status FindMyDeviceControllerImpl::ComputeStatus()
+    const {
+  if (do_not_disturb_controller_->IsDndEnabled()) {
+    PA_LOG(WARNING) << "Cannot set ringing status because DoNotDisturb mode is "
+                    << "enabled.";
+    return Status::kRingingNotAvailable;
+  }
+  return is_phone_ringing_ ? Status::kRingingOn : Status::kRingingOff;
+}
+
+void FindMyDeviceControllerImpl::UpdateStatus() {
+  Status status = ComputeStatus();
+  if (phone_ringing_status_ == status)
+    return;
+
+  phone_ringing_status_ = status;
+  NotifyPhoneRingingStateChanged();
 }
 
 }  // namespace phonehub
diff --git a/chromeos/components/phonehub/find_my_device_controller_impl.h b/chromeos/components/phonehub/find_my_device_controller_impl.h
index c76b15b..f84937c 100644
--- a/chromeos/components/phonehub/find_my_device_controller_impl.h
+++ b/chromeos/components/phonehub/find_my_device_controller_impl.h
@@ -5,24 +5,42 @@
 #ifndef CHROMEOS_COMPONENTS_PHONEHUB_FIND_MY_DEVICE_CONTROLLER_IMPL_H_
 #define CHROMEOS_COMPONENTS_PHONEHUB_FIND_MY_DEVICE_CONTROLLER_IMPL_H_
 
+#include "chromeos/components/phonehub/do_not_disturb_controller.h"
 #include "chromeos/components/phonehub/find_my_device_controller.h"
 
 namespace chromeos {
 namespace phonehub {
 
-// TODO(https://crbug.com/1106937): Add real implementation.
-class FindMyDeviceControllerImpl : public FindMyDeviceController {
+class MessageSender;
+
+// Responsible for sending and receiving updates in regards to the Find My
+// Device feature which involves ringing the user's remote phone.
+class FindMyDeviceControllerImpl : public FindMyDeviceController,
+                                   public DoNotDisturbController::Observer {
  public:
-  FindMyDeviceControllerImpl();
+  FindMyDeviceControllerImpl(DoNotDisturbController* do_not_disturb_controller,
+                             MessageSender* message_sender);
   ~FindMyDeviceControllerImpl() override;
 
  private:
+  friend class FindMyDeviceControllerImplTest;
+
+  Status ComputeStatus() const;
+  void UpdateStatus();
+
   // FindMyDeviceController:
-  bool IsPhoneRinging() const override;
   void SetIsPhoneRingingInternal(bool is_phone_ringing) override;
   void RequestNewPhoneRingingState(bool ringing) override;
+  Status GetPhoneRingingStatus() override;
+
+  // DoNotDisturbController::Observer:
+  void OnDndStateChanged() override;
 
   bool is_phone_ringing_ = false;
+  Status phone_ringing_status_ = Status::kRingingOff;
+
+  DoNotDisturbController* do_not_disturb_controller_;
+  MessageSender* message_sender_;
 };
 
 }  // namespace phonehub
diff --git a/chromeos/components/phonehub/find_my_device_controller_impl_unittest.cc b/chromeos/components/phonehub/find_my_device_controller_impl_unittest.cc
index 7c904bc..1519a18 100644
--- a/chromeos/components/phonehub/find_my_device_controller_impl_unittest.cc
+++ b/chromeos/components/phonehub/find_my_device_controller_impl_unittest.cc
@@ -6,6 +6,9 @@
 
 #include <memory>
 
+#include "chromeos/components/phonehub/fake_do_not_disturb_controller.h"
+#include "chromeos/components/phonehub/fake_message_sender.h"
+#include "chromeos/components/phonehub/find_my_device_controller.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace chromeos {
@@ -39,25 +42,101 @@
 
   // testing::Test:
   void SetUp() override {
-    controller_ = std::make_unique<FindMyDeviceControllerImpl>();
+    fake_do_not_disturb_controller_ =
+        std::make_unique<FakeDoNotDisturbController>();
+    fake_message_sender_ = std::make_unique<FakeMessageSender>();
+    controller_ = std::make_unique<FindMyDeviceControllerImpl>(
+        fake_do_not_disturb_controller_.get(), fake_message_sender_.get());
     controller_->AddObserver(&fake_observer_);
   }
 
   void TearDown() override { controller_->RemoveObserver(&fake_observer_); }
 
-  bool IsPhoneRinging() const { return controller_->IsPhoneRinging(); }
+  FindMyDeviceController::Status GetPhoneRingingStatus() const {
+    return controller_->GetPhoneRingingStatus();
+  }
+
+  void SetIsPhoneRingingInternal(bool is_phone_ringing) {
+    controller_->SetIsPhoneRingingInternal(is_phone_ringing);
+  }
+
+  void RequestNewPhoneRingingState(bool ringing) {
+    controller_->RequestNewPhoneRingingState(ringing);
+  }
 
   size_t GetNumObserverCalls() const { return fake_observer_.num_calls(); }
 
+ protected:
+  std::unique_ptr<FakeDoNotDisturbController> fake_do_not_disturb_controller_;
+  std::unique_ptr<FakeMessageSender> fake_message_sender_;
+
  private:
+  std::unique_ptr<FindMyDeviceControllerImpl> controller_;
   FakeObserver fake_observer_;
-  std::unique_ptr<FindMyDeviceController> controller_;
 };
 
-// TODO(https://crbug.com/1106937): Remove this test once we have real
-// functionality to test.
-TEST_F(FindMyDeviceControllerImplTest, Initialize) {
-  EXPECT_FALSE(IsPhoneRinging());
+TEST_F(FindMyDeviceControllerImplTest, RingingStateChanges) {
+  EXPECT_EQ(FindMyDeviceController::Status::kRingingOff,
+            GetPhoneRingingStatus());
+
+  // Simulate flipping DoNotDisturb mode to enabled, this should set the
+  // FindMyPhone status to kRingingNotAvailable.
+  fake_do_not_disturb_controller_->SetDoNotDisturbStateInternal(
+      /*is_dnd_enabled=*/true);
+  EXPECT_EQ(FindMyDeviceController::Status::kRingingNotAvailable,
+            GetPhoneRingingStatus());
+  // Simulate initiating phone ringing when DoNotDisturb mode is enabled. This
+  // will not update the internal status.
+  SetIsPhoneRingingInternal(/*is_phone_ringing=*/true);
+  EXPECT_EQ(FindMyDeviceController::Status::kRingingNotAvailable,
+            GetPhoneRingingStatus());
+  EXPECT_EQ(1u, GetNumObserverCalls());
+
+  // Flip DoNotDisturb back to disabled, expect status to reset back to its
+  // previous state.
+  fake_do_not_disturb_controller_->SetDoNotDisturbStateInternal(
+      /*is_dnd_enabled=*/false);
+  // Since we previously recorded that the phone should be ringing during
+  // DoNotDisturb mode was enabled, we return to that state once DoNotDisturb
+  // is disabled.
+  EXPECT_EQ(FindMyDeviceController::Status::kRingingOn,
+            GetPhoneRingingStatus());
+  EXPECT_EQ(2u, GetNumObserverCalls());
+
+  // Attempt to set ringing status with the same previous state. Expect that no
+  // observer calls were made.
+  SetIsPhoneRingingInternal(/*is_phone_ringing=*/true);
+  EXPECT_EQ(2u, GetNumObserverCalls());
+}
+
+TEST_F(FindMyDeviceControllerImplTest, RequestNewRingStatus) {
+  RequestNewPhoneRingingState(/*ringing=*/true);
+  EXPECT_EQ(1u, fake_message_sender_->GetRingDeviceRequestCallCount());
+  EXPECT_TRUE(fake_message_sender_->GetRecentRingDeviceRequest());
+
+  // Simulate flipping DoNotDisturb mode to enabled, this should set the
+  // FindMyPhone status to kRingingNotAvailable and not send any new messages.
+  fake_do_not_disturb_controller_->SetDoNotDisturbStateInternal(
+      /*is_dnd_enabled=*/true);
+  EXPECT_EQ(FindMyDeviceController::Status::kRingingNotAvailable,
+            GetPhoneRingingStatus());
+
+  RequestNewPhoneRingingState(/*ringing=*/false);
+  EXPECT_EQ(1u, fake_message_sender_->GetRingDeviceRequestCallCount());
+  // No new messages were sent, expect that the last request was still the
+  // previous "true" value.
+  EXPECT_TRUE(fake_message_sender_->GetRecentRingDeviceRequest());
+
+  // Flip DoNotDisturb mode to disabled, expect that messages are able to be
+  // sent again.
+  fake_do_not_disturb_controller_->SetDoNotDisturbStateInternal(
+      /*is_dnd_enabled=*/false);
+  EXPECT_EQ(FindMyDeviceController::Status::kRingingOff,
+            GetPhoneRingingStatus());
+
+  RequestNewPhoneRingingState(/*ringing=*/false);
+  EXPECT_EQ(2u, fake_message_sender_->GetRingDeviceRequestCallCount());
+  EXPECT_FALSE(fake_message_sender_->GetRecentRingDeviceRequest());
 }
 
 }  // namespace phonehub
diff --git a/chromeos/components/phonehub/phone_hub_manager_impl.cc b/chromeos/components/phonehub/phone_hub_manager_impl.cc
index e6b6350..65dc98e 100644
--- a/chromeos/components/phonehub/phone_hub_manager_impl.cc
+++ b/chromeos/components/phonehub/phone_hub_manager_impl.cc
@@ -45,8 +45,9 @@
       connection_scheduler_(std::make_unique<ConnectionSchedulerImpl>(
           connection_manager_.get(),
           feature_status_provider_.get())),
-      find_my_device_controller_(
-          std::make_unique<FindMyDeviceControllerImpl>()),
+      find_my_device_controller_(std::make_unique<FindMyDeviceControllerImpl>(
+          do_not_disturb_controller_.get(),
+          message_sender_.get())),
       notification_access_manager_(
           std::make_unique<NotificationAccessManagerImpl>(pref_service)),
       notification_manager_(std::make_unique<NotificationManagerImpl>()),
diff --git a/chromeos/components/phonehub/phone_status_processor_unittest.cc b/chromeos/components/phonehub/phone_status_processor_unittest.cc
index c356fdc6..57a4bfe 100644
--- a/chromeos/components/phonehub/phone_status_processor_unittest.cc
+++ b/chromeos/components/phonehub/phone_status_processor_unittest.cc
@@ -137,7 +137,8 @@
   EXPECT_EQ(base::UTF8ToUTF16(test_remote_device_.name()),
             *mutable_phone_model_->phone_name());
   EXPECT_TRUE(fake_do_not_disturb_controller_->IsDndEnabled());
-  EXPECT_TRUE(fake_find_my_device_controller_->IsPhoneRinging());
+  EXPECT_EQ(FindMyDeviceController::Status::kRingingOn,
+            fake_find_my_device_controller_->GetPhoneRingingStatus());
   EXPECT_TRUE(fake_notification_access_manager_->HasAccessBeenGranted());
 
   base::Optional<PhoneStatusModel> phone_status_model =
@@ -198,7 +199,8 @@
   EXPECT_EQ(base::UTF8ToUTF16(test_remote_device_.name()),
             *mutable_phone_model_->phone_name());
   EXPECT_TRUE(fake_do_not_disturb_controller_->IsDndEnabled());
-  EXPECT_TRUE(fake_find_my_device_controller_->IsPhoneRinging());
+  EXPECT_EQ(FindMyDeviceController::Status::kRingingOn,
+            fake_find_my_device_controller_->GetPhoneRingingStatus());
   EXPECT_TRUE(fake_notification_access_manager_->HasAccessBeenGranted());
 
   base::Optional<PhoneStatusModel> phone_status_model =
@@ -221,7 +223,8 @@
   EXPECT_EQ(base::UTF8ToUTF16(test_remote_device_.name()),
             *mutable_phone_model_->phone_name());
   EXPECT_TRUE(fake_do_not_disturb_controller_->IsDndEnabled());
-  EXPECT_TRUE(fake_find_my_device_controller_->IsPhoneRinging());
+  EXPECT_EQ(FindMyDeviceController::Status::kRingingOn,
+            fake_find_my_device_controller_->GetPhoneRingingStatus());
   EXPECT_TRUE(fake_notification_access_manager_->HasAccessBeenGranted());
 
   phone_status_model = mutable_phone_model_->phone_status_model();
diff --git a/chromeos/components/scanning/mojom/scanning.mojom b/chromeos/components/scanning/mojom/scanning.mojom
index 0cb5685..dddf0a6 100644
--- a/chromeos/components/scanning/mojom/scanning.mojom
+++ b/chromeos/components/scanning/mojom/scanning.mojom
@@ -16,12 +16,17 @@
 
 // The source types from which a scan can be obtained.
 enum SourceType {
+  // An unknown source type.
+  kUnknown,
   // A flatbed that scans a single page.
   kFlatbed,
   // An automatic document feeder that scans a single side of each page.
   kAdfSimplex,
   // An automatic document feeder that scans both sides of each page.
   kAdfDuplex,
+  // The implicit source type used for scanners that do not report any source
+  // options.
+  kDefault,
 };
 
 // The source from which a scan can be obtained.
diff --git a/components/arc/mojom/bluetooth.mojom b/components/arc/mojom/bluetooth.mojom
index 64e3f6d..1851507 100644
--- a/components/arc/mojom/bluetooth.mojom
+++ b/components/arc/mojom/bluetooth.mojom
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// Next MinVersion: 18
+// Next MinVersion: 19
 
 module arc.mojom;
 
@@ -316,8 +316,56 @@
   OnConnectFailed@1();
 };
 
-// Next Method ID: 47
-// Deprecated Method ID: 4, 5, 6, 7, 20, 21
+// Copied from Android. See android.bluetooth.BluetoothSocket.
+// Currently we only support RFCOMM and LE CoC (L2CAP_LE) sockets.
+[Extensible]
+enum BluetoothSocketType {
+  TYPE_RFCOMM = 1,
+  TYPE_L2CAP_LE = 4,
+};
+
+// Bluetooth socket security flags in Android, used in open socket requests.
+// Chrome should set the socket option based on these flags.
+struct BluetoothSocketFlags {
+  bool encrypt;
+  bool auth;
+  bool auth_mitm;
+  bool auth_16_digit;
+};
+
+// BluetoothSocketConnection contains the information for a new Bluetooth socket
+// connection. Note that |port| is either the channel number for an RFCOMM
+// socket or PSM for an L2CAP socket. Since we cannot get socket (or peer) name
+// on the transferred socket on Android side, we also need to pass the peer
+// address and port number.
+struct BluetoothSocketConnection {
+  handle sock;
+  BluetoothAddress addr;
+  int32 port;
+};
+
+// The mojo connection represents a listening socket.
+// Next Method ID: 1
+interface BluetoothListenSocketClient {
+  // Called when accept() succeeds. |port| in |connection| is the peer port
+  // number.
+  OnAccepted@0(BluetoothSocketConnection connection);
+};
+
+// The mojo connection represents a connecting (not connected yet) socket.
+// After connect() either succeeds or fails, Android is responsible for closing
+// this mojo connection.
+// Next Method ID: 2
+interface BluetoothConnectSocketClient {
+  // Called when connect() succeeds. |port| in |connection| is the port number
+  // on our side.
+  OnConnected@0(BluetoothSocketConnection connection);
+  // Called when connect() fails.
+  OnConnectFailed@1();
+};
+
+// Next Method ID: 49
+// Deprecated Method ID: 4, 5, 6, 7, 20, 21, 29, 45, 46
 interface BluetoothHost {
   EnableAdapter@0() => (BluetoothAdapterState state);
   DisableAdapter@1() => (BluetoothAdapterState state);
@@ -375,7 +423,9 @@
   [MinVersion=1] ReadRemoteRssi@28(BluetoothAddress remote_addr)
       => (int32 rssi);
 
-  [MinVersion=2] OpenBluetoothSocket@29()
+  // DEPRECATED: Use BluetoothSocketListen@47 or BluetoothSocketConnect@48
+  // instead.
+  [MinVersion=2] OpenBluetoothSocketDeprecated@29()
       => (handle sock);
 
   // Bluetooth Gatt Server functions
@@ -430,7 +480,7 @@
   // we will select a channel number automatically.  If this process succeeds,
   // returns SUCCESS in |status|, the actual listening channel in |channel|,
   // and a new mojo connection which represents the listening socket.
-  [MinVersion=15] RfcommListen@45(int32 channel, int32 optval)
+  [MinVersion=15] RfcommListenDeprecated@45(int32 channel, int32 optval)
       => (BluetoothStatus status, int32 channel,
           RfcommListeningSocketClient&? client);
   // Opens a bluetooth socket with socket option |optval|, and then connect()
@@ -438,9 +488,27 @@
   // in |status| and a new mojo connection which holds the connecting socket.
   // Unlike in RfcommListen(), |channel| here could not be 0, since this is the
   // peer channel number.
-  [MinVersion=15] RfcommConnect@46(BluetoothAddress remote_addr,
-                                   int32 channel, int32 optval)
+  [MinVersion=15] RfcommConnectDeprecated@46(BluetoothAddress remote_addr,
+                                             int32 channel, int32 optval)
       => (BluetoothStatus status, RfcommConnectingSocketClient&? client);
+
+  // Bluetooth socket (RFCOMM and L2CAP LE) functions
+  // Opens a |sock_type| socket with security options in |sock_flags|, and
+  // listens on |port| (RFCOMM channel or L2CAP PSM). When |port| = 0, we will
+  // select a port number automatically. If this process succeeds, the actual
+  // listening port will be returned in |port|.
+  [MinVersion=18] BluetoothSocketListen@47(BluetoothSocketType sock_type,
+                                           BluetoothSocketFlags sock_flags,
+                                           int32 port)
+      => (BluetoothStatus status, int32 port,
+          BluetoothListenSocketClient&? client);
+  // Opens a |sock_type| socket with security options in |sock_flags|, and
+  // connects to |remote_addr| which is listening on |remote_port|.
+  [MinVersion=18] BluetoothSocketConnect@48(BluetoothSocketType sock_type,
+                                            BluetoothSocketFlags sock_flags,
+                                            BluetoothAddress remote_addr,
+                                            int32 remote_port)
+      => (BluetoothStatus status, BluetoothConnectSocketClient&? client);
 };
 
 // Next Method ID: 24
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index 3447ad7..cbc8883 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -183,6 +183,8 @@
     "metrics/form_event_logger_base.cc",
     "metrics/form_event_logger_base.h",
     "metrics/form_events.h",
+    "pattern_provider/pattern_configuration_parser.cc",
+    "pattern_provider/pattern_configuration_parser.h",
     "pattern_provider/pattern_provider.cc",
     "pattern_provider/pattern_provider.h",
     "payments/account_info_getter.h",
@@ -390,6 +392,7 @@
     "//crypto",
     "//google_apis",
     "//net",
+    "//services/data_decoder/public/cpp:cpp",
     "//services/metrics/public/cpp:metrics_cpp",
     "//services/metrics/public/cpp:ukm_builders",
     "//services/network/public/cpp",
@@ -446,6 +449,8 @@
     "logging/stub_log_manager.h",
     "mock_autocomplete_history_manager.cc",
     "mock_autocomplete_history_manager.h",
+    "pattern_provider/test_pattern_provider.cc",
+    "pattern_provider/test_pattern_provider.h",
     "payments/test_authentication_requester.cc",
     "payments/test_authentication_requester.h",
     "payments/test_credit_card_save_manager.cc",
@@ -628,6 +633,7 @@
     "logging/log_buffer_submitter_unittest.cc",
     "logging/log_manager_unittest.cc",
     "logging/log_router_unittest.cc",
+    "pattern_provider/pattern_configuration_parser_unittest.cc",
     "pattern_provider/pattern_provider_unittest.cc",
     "payments/autofill_offer_manager_unittest.cc",
     "payments/credit_card_access_manager_unittest.cc",
@@ -727,6 +733,7 @@
     "//google_apis",
     "//google_apis:test_support",
     "//net:test_support",
+    "//services/data_decoder/public/cpp:test_support",
     "//services/metrics/public/cpp:ukm_builders",
     "//services/network:test_support",
     "//services/network/public/cpp",
diff --git a/components/autofill/core/browser/pattern_provider/DEPS b/components/autofill/core/browser/pattern_provider/DEPS
new file mode 100644
index 0000000..89e4cd8
--- /dev/null
+++ b/components/autofill/core/browser/pattern_provider/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+  "+components/grit/components_resources.h",
+  "+services/data_decoder/public/cpp:cpp",
+  "+services/data_decoder/public",
+]
diff --git a/components/autofill/core/browser/pattern_provider/pattern_configuration_parser.cc b/components/autofill/core/browser/pattern_provider/pattern_configuration_parser.cc
new file mode 100644
index 0000000..f6e4f7fa
--- /dev/null
+++ b/components/autofill/core/browser/pattern_provider/pattern_configuration_parser.cc
@@ -0,0 +1,220 @@
+// 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/autofill/core/browser/pattern_provider/pattern_configuration_parser.h"
+
+#include "base/bind.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "base/values.h"
+#include "components/autofill/core/browser/autofill_type.h"
+#include "components/autofill/core/browser/field_types.h"
+#include "components/grit/components_resources.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace autofill {
+
+namespace field_type_parsing {
+
+namespace {
+
+const char kPatternIdentifierKey[] = "pattern_identifier";
+const char kPositivePatternKey[] = "positive_pattern";
+const char kNegativePatternKey[] = "negative_pattern";
+const char kPositiveScoreKey[] = "positive_score";
+const char kMatchFieldAttributesKey[] = "match_field_attributes";
+const char kMatchFieldInputTypesKey[] = "match_field_input_types";
+const char kVersionKey[] = "version";
+
+bool ParseMatchingPattern(PatternProvider::Map& patterns,
+                          const std::string& field_type,
+                          const std::string& language,
+                          const base::Value& value) {
+  if (!value.is_dict())
+    return false;
+
+  const std::string* pattern_identifier =
+      value.FindStringKey(kPatternIdentifierKey);
+  const std::string* positive_pattern =
+      value.FindStringKey(kPositivePatternKey);
+  const std::string* negative_pattern =
+      value.FindStringKey(kNegativePatternKey);
+  base::Optional<double> positive_score =
+      value.FindDoubleKey(kPositiveScoreKey);
+  base::Optional<int> match_field_attributes =
+      value.FindIntKey(kMatchFieldAttributesKey);
+  base::Optional<int> match_field_input_types =
+      value.FindIntKey(kMatchFieldInputTypesKey);
+
+  if (!pattern_identifier || !positive_pattern || !negative_pattern ||
+      !positive_score || !match_field_attributes || !match_field_input_types)
+    return false;
+
+  autofill::MatchingPattern new_pattern;
+  new_pattern.pattern_identifier = *pattern_identifier;
+  new_pattern.positive_pattern = *positive_pattern;
+  new_pattern.positive_score = *positive_score;
+  new_pattern.negative_pattern = *negative_pattern;
+  new_pattern.match_field_attributes = match_field_attributes.value();
+  new_pattern.match_field_input_types = match_field_input_types.value();
+  new_pattern.language = language;
+
+  std::vector<MatchingPattern>* pattern_list = &patterns[field_type][language];
+  pattern_list->push_back(new_pattern);
+
+  DVLOG(2) << "Correctly parsed MatchingPattern with identifier |"
+           << new_pattern.pattern_identifier << "|.";
+
+  return true;
+}
+
+// Callback which is used once the JSON is parsed.
+// |overwrite_equal_version| should be true when loading a remote
+// configuration. If the configuration versions are equal or
+// both unspecified (i.e. set to 0) this prioritizes the remote
+// configuration over the local one.
+void OnJsonParsed(bool overwrite_equal_version,
+                  base::OnceClosure done_callback,
+                  data_decoder::DataDecoder::ValueOrError result) {
+  // Skip any processing in case of an error.
+  if (!result.value) {
+    DVLOG(1) << "Failed to parse PatternProvider configuration JSON string.";
+    std::move(done_callback).Run();
+    return;
+  }
+
+  base::Version version = ExtractVersionFromJsonObject(result.value.value());
+  base::Optional<PatternProvider::Map> patterns =
+      GetConfigurationFromJsonObject(result.value.value());
+
+  if (patterns && version.IsValid()) {
+    DVLOG(1) << "Successfully parsed PatternProvider configuration.";
+
+    PatternProvider& pattern_provider = PatternProvider::GetInstance();
+    pattern_provider.SetPatterns(std::move(patterns.value()),
+                                 std::move(version), overwrite_equal_version);
+  } else {
+    DVLOG(1) << "Failed to parse PatternProvider configuration JSON object.";
+  }
+
+  std::move(done_callback).Run();
+}
+
+}  // namespace
+
+base::Optional<PatternProvider::Map> GetConfigurationFromJsonObject(
+    const base::Value& root) {
+  PatternProvider::Map patterns;
+
+  if (!root.is_dict()) {
+    DVLOG(1) << "JSON object is not a dictionary.";
+    return base::nullopt;
+  }
+
+  for (const auto& kv : root.DictItems()) {
+    const std::string& field_type = kv.first;
+    const base::Value* field_type_dict = &kv.second;
+
+    if (!field_type_dict->is_dict()) {
+      DVLOG(1) << "|" << field_type << "| does not contain a dictionary.";
+      return base::nullopt;
+    }
+
+    for (const auto& value : field_type_dict->DictItems()) {
+      const std::string& language = value.first;
+      const base::Value* inner_list = &value.second;
+
+      if (!inner_list->is_list()) {
+        DVLOG(1) << "Language |" << language << "| in |" << field_type
+                 << "| does not contain a list.";
+        return base::nullopt;
+      }
+
+      for (const auto& matchingPatternObj : inner_list->GetList()) {
+        bool success = ParseMatchingPattern(patterns, field_type, language,
+                                            matchingPatternObj);
+        if (!success) {
+          DVLOG(1) << "Found incorrect |MatchingPattern| object in list |"
+                   << field_type << "|, language |" << language << "|.";
+          return base::nullopt;
+        }
+      }
+    }
+  }
+
+  return base::make_optional(patterns);
+}
+
+base::Version ExtractVersionFromJsonObject(base::Value& root) {
+  if (!root.is_dict())
+    return base::Version("0");
+
+  base::Optional<base::Value> version_str = root.ExtractKey(kVersionKey);
+  if (!version_str || !version_str.value().is_string())
+    return base::Version("0");
+
+  base::Version version = base::Version(version_str.value().GetString());
+  if (!version.IsValid())
+    return base::Version("0");
+
+  return version;
+}
+
+void PopulateFromJsonString(std::string json_string) {
+  data_decoder::DataDecoder::ParseJsonIsolated(
+      std::move(json_string),
+      base::BindOnce(&OnJsonParsed, true, base::DoNothing::Once()));
+}
+
+void PopulateFromResourceBundle(base::OnceClosure done_callback) {
+  if (!ui::ResourceBundle::HasSharedInstance()) {
+    VLOG(1) << "Resource Bundle unavailable to load Autofill Matching Pattern "
+               "definitions.";
+    std::move(done_callback).Run();
+    return;
+  }
+
+  ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+
+  // Load the string from the Resource Bundle on a worker thread, then
+  // securely parse the JSON in a separate process and call |OnJsonParsed|
+  // with the result.
+  base::ThreadPool::PostTaskAndReplyWithResult(
+      FROM_HERE, {base::MayBlock()},
+      base::BindOnce(&ui::ResourceBundle::LoadDataResourceString,
+                     base::Unretained(&bundle), IDR_AUTOFILL_REGEX_JSON),
+      base::BindOnce(
+          [](base::OnceClosure done_callback, std::string resource_string) {
+            data_decoder::DataDecoder::ParseJsonIsolated(
+                std::move(resource_string),
+                base::BindOnce(&OnJsonParsed, false, std::move(done_callback)));
+          },
+          std::move(done_callback)));
+}
+
+base::Optional<PatternProvider::Map>
+GetPatternsFromResourceBundleSynchronously() {
+  if (!ui::ResourceBundle::HasSharedInstance()) {
+    VLOG(1) << "Resource Bundle unavailable to load Autofill Matching Pattern "
+               "definitions.";
+    return base::nullopt;
+  }
+
+  ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+  std::string resource_string =
+      bundle.LoadDataResourceString(IDR_AUTOFILL_REGEX_JSON);
+  base::Optional<base::Value> json_object =
+      base::JSONReader::Read(resource_string);
+
+  // Discard version, since this is the only getter used in unit tests.
+  base::Version version = ExtractVersionFromJsonObject(json_object.value());
+  base::Optional<PatternProvider::Map> configuration_map =
+      GetConfigurationFromJsonObject(json_object.value());
+
+  return configuration_map;
+}
+
+}  // namespace field_type_parsing
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/pattern_provider/pattern_configuration_parser.h b/components/autofill/core/browser/pattern_provider/pattern_configuration_parser.h
new file mode 100644
index 0000000..8f72b18
--- /dev/null
+++ b/components/autofill/core/browser/pattern_provider/pattern_configuration_parser.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 COMPONENTS_AUTOFILL_CORE_BROWSER_PATTERN_PROVIDER_PATTERN_CONFIGURATION_PARSER_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_PATTERN_PROVIDER_PATTERN_CONFIGURATION_PARSER_H_
+
+#include <string>
+
+#include "base/json/json_reader.h"
+#include "base/version.h"
+#include "components/autofill/core/browser/form_parsing/autofill_parsing_utils.h"
+#include "components/autofill/core/browser/pattern_provider/pattern_provider.h"
+#include "services/data_decoder/public/cpp/data_decoder.h"
+
+namespace autofill {
+
+namespace field_type_parsing {
+
+// Tries to extract the configuration version from the JSON base::Value tree.
+// This removes the key if found, so that validation is easier later on.
+// If not found, default to version 0.
+base::Version ExtractVersionFromJsonObject(base::Value& root);
+
+// Transforms the parsed JSON base::Value tree into the map used in
+// |PatternProvider|. Requires the version key to already be extracted.
+// The root is expected to be a dictionary with keys corresponding to
+// strings representing |ServerFieldType|. Then there should be
+// second level dictionaries with keys describing the language. These
+// should point to a list of objects representing |MatchingPattern|.
+//  {
+//    "FIELD_NAME": {
+//      "language":[
+//        {MatchingPatternFields}
+//      ]
+//    }
+//  }
+//  An example can be found in the relative resources folder.
+base::Optional<PatternProvider::Map> GetConfigurationFromJsonObject(
+    const base::Value& root);
+
+// Tries to get and parse the default configuration in the resource bundle
+// into a valid map used in |PatternProvider| and swap it in for further use.
+// The callback is used as a signal for testing.
+void PopulateFromResourceBundle(
+    base::OnceClosure done_callback = base::DoNothing::Once());
+
+// Tries to parse the given JSON string into a valid map used in the
+// |PatternProvider| and swap it in for further use.
+void PopulateFromJsonString(std::string json_string);
+
+// Synchronous getter used to set up a test fixture.
+base::Optional<PatternProvider::Map>
+GetPatternsFromResourceBundleSynchronously();
+
+}  // namespace field_type_parsing
+
+}  // namespace autofill
+
+#endif
diff --git a/components/autofill/core/browser/pattern_provider/pattern_configuration_parser_unittest.cc b/components/autofill/core/browser/pattern_provider/pattern_configuration_parser_unittest.cc
new file mode 100644
index 0000000..d945187
--- /dev/null
+++ b/components/autofill/core/browser/pattern_provider/pattern_configuration_parser_unittest.cc
@@ -0,0 +1,196 @@
+// 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/autofill/core/browser/pattern_provider/pattern_configuration_parser.h"
+
+#include <stddef.h>
+
+#include "base/json/json_reader.h"
+#include "base/test/gtest_util.h"
+#include "base/version.h"
+#include "components/grit/components_resources.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace autofill {
+
+namespace field_type_parsing {
+
+// Test that the |base::Value| object of the configuration is
+// parsed to the map structure used by |PatternProvider| as
+// expected, given the input is valid.
+TEST(PatternConfigurationParserTest, WellFormedParsedCorrectly) {
+  std::string JSON_message = R"(
+    {
+      "version": "1.0",
+      "FULL_NAME": {
+        "en_us": [
+          {
+            "pattern_identifier": "Name_en",
+            "positive_pattern": "name|full name",
+            "positive_score": 2.0,
+            "negative_pattern": "company",
+            "match_field_attributes": 2,
+            "match_field_input_types": 3
+          }
+        ],
+        "fr": [
+          {
+            "pattern_identifier": "Name_fr",
+            "positive_pattern": "nom|prenom",
+            "positive_score": 2.0,
+            "negative_pattern": "compagne",
+            "match_field_attributes": 2,
+            "match_field_input_types": 3
+          }
+        ]
+      },
+      "ADDRESS": {
+        "en_us": [
+          {
+            "pattern_identifier": "Address",
+            "positive_pattern": "address",
+            "positive_score": 2.0,
+            "negative_pattern": "email",
+            "match_field_attributes": 4,
+            "match_field_input_types": 3
+          }
+        ]
+      }
+    })";
+  base::Optional<base::Value> JSON_object =
+      base::JSONReader::Read(JSON_message);
+
+  ASSERT_TRUE(JSON_object) << "Incorrectly formatted JSON string.";
+
+  base::Version version = ExtractVersionFromJsonObject(JSON_object.value());
+  base::Optional<PatternProvider::Map> optional_patterns =
+      GetConfigurationFromJsonObject(JSON_object.value());
+
+  ASSERT_TRUE(version.IsValid());
+  ASSERT_TRUE(optional_patterns);
+
+  ASSERT_EQ(base::Version("1.0"), version);
+
+  PatternProvider::Map patterns = optional_patterns.value();
+
+  ASSERT_EQ(2U, patterns.size());
+  ASSERT_TRUE(patterns.count("FULL_NAME"));
+  ASSERT_EQ(2U, patterns["FULL_NAME"].size());
+  ASSERT_TRUE(patterns["FULL_NAME"].count("en_us"));
+  ASSERT_TRUE(patterns["FULL_NAME"].count("fr"));
+
+  ASSERT_TRUE(patterns.count("ADDRESS"));
+  ASSERT_EQ(1U, patterns["ADDRESS"].size());
+  ASSERT_TRUE(patterns["ADDRESS"].count("en_us"));
+
+  // Test one |MatchingPattern| to check that they are parsed correctly.
+  MatchingPattern* pattern = &patterns["FULL_NAME"]["fr"][0];
+
+  ASSERT_EQ("Name_fr", pattern->pattern_identifier);
+  ASSERT_EQ("nom|prenom", pattern->positive_pattern);
+  ASSERT_EQ("compagne", pattern->negative_pattern);
+  ASSERT_EQ("fr", pattern->language);
+  ASSERT_NEAR(2.0, pattern->positive_score, 1e-6);
+  ASSERT_EQ(2, pattern->match_field_attributes);
+  ASSERT_EQ(3, pattern->match_field_input_types);
+}
+
+// Test that the parser does not return anything if some |MatchingPattern|
+// object is missing a property.
+TEST(PatternConfigurationParserTest, MalformedMissingProperty) {
+  std::string JSON_message = R"(
+    {
+      "version": "1.0",
+      "FULL_NAME": {
+        "en_us": [
+          {
+            "pattern_identifier": "Name_en",
+            "positive_pattern": "name|full name",
+            "positive_score": 2.0,
+            "negative_pattern": "company",
+            "match_field_attributes": 2,
+            "match_field_input_types": 3
+          }
+        ],
+        "fr": [
+          {
+            "pattern_identifier": "Name_fr",
+            "positive_pattern": "nom|prenom",
+            "negative_pattern": "compagne",
+            "match_field_attributes": 2,
+            "match_field_input_types": 3
+          }
+        ]
+      }
+    })";
+  base::Optional<base::Value> JSON_object =
+      base::JSONReader::Read(JSON_message);
+
+  ASSERT_TRUE(JSON_object) << "Incorrectly formatted JSON string.";
+
+  base::Optional<PatternProvider::Map> optional_patterns =
+      GetConfigurationFromJsonObject(JSON_object.value());
+
+  ASSERT_FALSE(optional_patterns);
+}
+
+// Test that the parser correctly sets the default version if
+// it is not present in the configuration.
+TEST(PatternConfigurationParserTest, MalformedMissingVersion) {
+  std::string JSON_message = R"(
+    {
+      "FULL_NAME": {
+        "en_us": [
+          {
+            "pattern_identifier": "Name_en",
+            "positive_pattern": "name|full name",
+            "positive_score": 2.0,
+            "negative_pattern": "company",
+            "match_field_attributes": 2,
+            "match_field_input_types": 3
+          }
+        ]
+      }
+    })";
+  base::Optional<base::Value> JSON_object =
+      base::JSONReader::Read(JSON_message);
+
+  ASSERT_TRUE(JSON_object) << "Incorrectly formatted JSON string.";
+
+  base::Version version = ExtractVersionFromJsonObject(JSON_object.value());
+
+  ASSERT_EQ(base::Version("0"), version);
+}
+
+// Test that the parser does not return anything if the inner key points
+// to a single object instead of a list.
+TEST(PatternConfigurationParserTest, MalformedNotList) {
+  std::string JSON_message = R"(
+    {
+      "FULL_NAME": {
+        "en_us": {
+          "pattern_identifier": "Name_en",
+          "positive_pattern": "name|full name",
+          "positive_score": 2.0,
+          "negative_pattern": "company",
+          "match_field_attributes": 2,
+          "match_field_input_types": 3
+        }
+      }
+    })";
+  base::Optional<base::Value> JSON_object =
+      base::JSONReader::Read(JSON_message);
+
+  ASSERT_TRUE(JSON_object) << "Incorrectly formatted JSON string.";
+
+  base::Optional<PatternProvider::Map> optional_patterns =
+      GetConfigurationFromJsonObject(JSON_object.value());
+
+  ASSERT_FALSE(optional_patterns);
+}
+
+}  // namespace field_type_parsing
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/pattern_provider/pattern_provider.cc b/components/autofill/core/browser/pattern_provider/pattern_provider.cc
index 04a2d48..a2eb5e74f 100644
--- a/components/autofill/core/browser/pattern_provider/pattern_provider.cc
+++ b/components/autofill/core/browser/pattern_provider/pattern_provider.cc
@@ -8,29 +8,36 @@
 #include <iostream>
 #include <string>
 
+#include "base/bind.h"
+#include "base/no_destructor.h"
 #include "components/autofill/core/browser/autofill_type.h"
+#include "components/autofill/core/browser/pattern_provider/pattern_configuration_parser.h"
 
 namespace autofill {
-PatternProvider::PatternProvider() {
-  auto& company_patterns = patterns_[AutofillType(COMPANY_NAME).ToString()];
-  company_patterns["EN"].push_back(GetCompanyPatternEn());
-  company_patterns["DE"].push_back(GetCompanyPatternDe());
+namespace {
+PatternProvider* g_pattern_provider = nullptr;
 }
 
-PatternProvider::~PatternProvider() {
-  patterns_.clear();
-}
+PatternProvider::PatternProvider() = default;
+PatternProvider::~PatternProvider() = default;
 
-void PatternProvider::SetPatterns(
-    const std::map<std::string,
-                   std::map<std::string, std::vector<MatchingPattern>>>&
-        patterns) {
-  patterns_ = patterns;
+void PatternProvider::SetPatterns(PatternProvider::Map patterns,
+                                  const base::Version version,
+                                  const bool overwrite_equal_version) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (!pattern_version_.IsValid() || pattern_version_ < version ||
+      (overwrite_equal_version && pattern_version_ == version)) {
+    patterns_ = patterns;
+    pattern_version_ = version;
+  }
 }
 
 const std::vector<MatchingPattern>& PatternProvider::GetMatchPatterns(
     const std::string& pattern_name,
     const std::string& page_language) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   return patterns_[pattern_name][page_language];
 }
 
@@ -41,9 +48,26 @@
   return GetMatchPatterns(pattern_name, page_language);
 }
 
-PatternProvider* PatternProvider::getInstance() {
-  static base::NoDestructor<PatternProvider> instance;
-  return instance.get();
+// static.
+PatternProvider& PatternProvider::GetInstance() {
+  if (!g_pattern_provider) {
+    static base::NoDestructor<PatternProvider> instance;
+    g_pattern_provider = instance.get();
+    field_type_parsing::PopulateFromResourceBundle();
+  }
+  return *g_pattern_provider;
+}
+
+// static.
+void PatternProvider::SetPatternProviderForTesting(
+    PatternProvider* pattern_provider) {
+  DCHECK(pattern_provider);
+  g_pattern_provider = pattern_provider;
+}
+
+// static.
+void PatternProvider::ResetPatternProvider() {
+  g_pattern_provider = nullptr;
 }
 
 }  //  namespace autofill
diff --git a/components/autofill/core/browser/pattern_provider/pattern_provider.h b/components/autofill/core/browser/pattern_provider/pattern_provider.h
index 2cb7422..c182e8bce4 100644
--- a/components/autofill/core/browser/pattern_provider/pattern_provider.h
+++ b/components/autofill/core/browser/pattern_provider/pattern_provider.h
@@ -7,24 +7,33 @@
 
 #include <string>
 
+#include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/no_destructor.h"
+#include "base/sequence_checker.h"
+#include "base/version.h"
 #include "components/autofill/core/browser/field_types.h"
 #include "components/autofill/core/browser/form_parsing/autofill_parsing_utils.h"
 #include "components/autofill/core/common/autofill_regex_constants.h"
-#include "third_party/re2/src/re2/re2.h"
 
 namespace autofill {
 
+// Base class for the Pattern Provider. This class contains the implementation
+// for providing the matching patterns. Different subclasses provide different
+// ways to load the data in for further use.
 class PatternProvider {
  public:
-  static PatternProvider* getInstance();
+  // Shorthand for the map structure used to store patterns.
+  using Map = std::map<std::string,
+                       std::map<std::string, std::vector<MatchingPattern>>>;
 
-  // Setter for loaded patterns from external storage.
-  void SetPatterns(
-      const std::map<std::string,
-                     std::map<std::string, std::vector<MatchingPattern>>>&
-          patterns);
+  // Returns a reference to the global Pattern Provider.
+  static PatternProvider& GetInstance();
+
+  // Setter for loading patterns from external storage.
+  void SetPatterns(const Map patterns,
+                   const base::Version version,
+                   const bool overwrite_equal_version);
 
   // Provides us with all patterns that can match our field type and page
   // language.
@@ -40,19 +49,38 @@
   const std::vector<MatchingPattern>& GetAllPatternsBaseOnType(
       ServerFieldType type);
 
- private:
+ protected:
   PatternProvider();
   ~PatternProvider();
 
+  // Local map to store a vector of patterns keyed by field type and
+  // page language.
+  Map patterns_;
+
+  // Version for keeping track which pattern set is in use.
+  base::Version pattern_version_;
+
+  // Sets a provider to be used for tests.
+  static void SetPatternProviderForTesting(PatternProvider* pattern_provider);
+
+  // Resets the provider pointer if the object behind it gets deleted.
+  static void ResetPatternProvider();
+
+ private:
   // Func to sort the incoming map by score.
   void SortPatternsByScore(std::vector<MatchingPattern>& patterns);
 
-  // Local map to store a vector of patterns keyed by field type and
-  // page language.
-  std::map<std::string, std::map<std::string, std::vector<MatchingPattern>>>
-      patterns_;
+  // Sequence checker to ensure thread-safety for pattern swapping.
+  // All functions accessing the |patterns_| member variable are
+  // expected to be called from the UI thread.
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  FRIEND_TEST_ALL_PREFIXES(AutofillPatternProviderPipelineTest,
+                           TestParsingEquivalent);
+  FRIEND_TEST_ALL_PREFIXES(AutofillPatternProviderPipelineTest,
+                           DefaultPatternProviderLoads);
 
   friend class base::NoDestructor<PatternProvider>;
 };
 }  // namespace autofill
-#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_PATTERN_PROVIDER_PATTERN_PROVIDER_H_
\ No newline at end of file
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_PATTERN_PROVIDER_PATTERN_PROVIDER_H_
diff --git a/components/autofill/core/browser/pattern_provider/pattern_provider_unittest.cc b/components/autofill/core/browser/pattern_provider/pattern_provider_unittest.cc
index 7259a75d..c9a3140 100644
--- a/components/autofill/core/browser/pattern_provider/pattern_provider_unittest.cc
+++ b/components/autofill/core/browser/pattern_provider/pattern_provider_unittest.cc
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/autofill/core/browser/pattern_provider/pattern_provider.h"
-
 #include <stddef.h>
 
 #include <map>
@@ -11,10 +9,46 @@
 #include <vector>
 
 #include "base/test/gtest_util.h"
+#include "base/test/task_environment.h"
+#include "components/autofill/core/browser/autofill_type.h"
+#include "components/autofill/core/browser/pattern_provider/pattern_configuration_parser.h"
+#include "components/autofill/core/browser/pattern_provider/pattern_provider.h"
+#include "components/autofill/core/browser/pattern_provider/test_pattern_provider.h"
+#include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace autofill {
 
+namespace {
+
+// Pattern Provider with custom values set for testing.
+class UnitTestPatternProvider : public PatternProvider {
+ public:
+  UnitTestPatternProvider();
+  ~UnitTestPatternProvider();
+};
+
+UnitTestPatternProvider::UnitTestPatternProvider() {
+  auto& company_patterns = patterns_[AutofillType(COMPANY_NAME).ToString()];
+  company_patterns["EN"].push_back(GetCompanyPatternEn());
+  company_patterns["DE"].push_back(GetCompanyPatternDe());
+
+  PatternProvider::SetPatternProviderForTesting(this);
+}
+
+UnitTestPatternProvider::~UnitTestPatternProvider() {
+  PatternProvider::ResetPatternProvider();
+}
+
+}  // namespace
+
+class AutofillPatternProviderTest : public testing::Test {
+ protected:
+  UnitTestPatternProvider pattern_provider_;
+
+  ~AutofillPatternProviderTest() override = default;
+};
+
 bool operator==(const MatchingPattern& mp1, const MatchingPattern& mp2) {
   return (mp1.language == mp2.language &&
           mp1.match_field_attributes == mp2.match_field_attributes &&
@@ -25,15 +59,50 @@
           mp1.positive_score == mp2.positive_score);
 }
 
-TEST(AutofillPatternProvider, Single_Match) {
+TEST_F(AutofillPatternProviderTest, Single_Match) {
   MatchingPattern kCompanyPatternEn = GetCompanyPatternEn();
   MatchingPattern kCompanyPatternDe = GetCompanyPatternDe();
-  PatternProvider* pattern_provider = PatternProvider::getInstance();
+  PatternProvider& pattern_provider = PatternProvider::GetInstance();
 
-  ASSERT_TRUE(pattern_provider->GetMatchPatterns("COMPANY_NAME", "EN").size() >
+  ASSERT_TRUE(pattern_provider.GetMatchPatterns("COMPANY_NAME", "EN").size() >
               0);
-  EXPECT_EQ(pattern_provider->GetMatchPatterns("COMPANY_NAME", "EN")[0],
+  EXPECT_EQ(pattern_provider.GetMatchPatterns("COMPANY_NAME", "EN")[0],
             kCompanyPatternEn);
 }
 
-}  // namespace autofill
\ No newline at end of file
+// Test that the default pattern provider loads without crashing.
+TEST(AutofillPatternProviderPipelineTest, DefaultPatternProviderLoads) {
+  base::test::TaskEnvironment task_environment_;
+  data_decoder::test::InProcessDataDecoder in_process_data_decoder_;
+
+  base::RunLoop run_loop;
+  field_type_parsing::PopulateFromResourceBundle(run_loop.QuitClosure());
+  run_loop.Run();
+  PatternProvider& default_pattern_provider = PatternProvider::GetInstance();
+
+  EXPECT_FALSE(default_pattern_provider.patterns_.empty());
+
+  // Call the getter to ensure sequence checks work correctly.
+  default_pattern_provider.GetMatchPatterns("EMAIL_ADDRESS", "en");
+}
+
+// Test that the TestPatternProvider class uses a PatternProvider::Map
+// equivalent to the DefaultPatternProvider. This is also an example of what is
+// needed to test the DefaultPatternProvider. Warning: If this crashes, check
+// that no state carried over from other tests using the singleton.
+TEST(AutofillPatternProviderPipelineTest, TestParsingEquivalent) {
+  base::test::TaskEnvironment task_environment_;
+  data_decoder::test::InProcessDataDecoder in_process_data_decoder_;
+
+  base::RunLoop run_loop;
+  field_type_parsing::PopulateFromResourceBundle(run_loop.QuitClosure());
+  run_loop.Run();
+  PatternProvider& default_pattern_provider = PatternProvider::GetInstance();
+
+  TestPatternProvider test_pattern_provider;
+
+  EXPECT_EQ(default_pattern_provider.patterns_,
+            test_pattern_provider.patterns_);
+}
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/pattern_provider/resources/regex_patterns.json b/components/autofill/core/browser/pattern_provider/resources/regex_patterns.json
new file mode 100644
index 0000000..95ca87f70
--- /dev/null
+++ b/components/autofill/core/browser/pattern_provider/resources/regex_patterns.json
@@ -0,0 +1,2866 @@
+{
+  "ATTENTION_IGNORED": {
+    "en": [
+      {
+        "pattern_identifier": "en_attention_ignored_preserving",
+        "positive_pattern": "attention|attn",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "REGION_IGNORED": {
+    "en": [
+      {
+        "pattern_identifier": "en_region_ignored_preserving",
+        "positive_pattern": "province|region|other",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "es": [
+      {
+        "pattern_identifier": "es_region_ignored_preserving",
+        "positive_pattern": "provincia",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "pt": [
+      {
+        "pattern_identifier": "pt_region_ignored_preserving",
+        "positive_pattern": "bairro|suburb",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "ADDRESS_NAME_IGNORED": {
+    "en": [
+      {
+        "pattern_identifier": "en_address_name_ignored_preserving",
+        "positive_pattern": "address.*nickname|address.*label",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "COMPANY": {
+    "en": [
+      {
+        "pattern_identifier": "en_company_preserving",
+        "positive_pattern": "company|business|organization|organisation",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "de": [
+      {
+        "pattern_identifier": "de_company_preserving",
+        "positive_pattern": "(?<!con)firma|firmenname",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "es": [
+      {
+        "pattern_identifier": "es_company_preserving",
+        "positive_pattern": "empresa",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "fr": [
+      {
+        "pattern_identifier": "fr_company_preserving",
+        "positive_pattern": "societe|société",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "it": [
+      {
+        "pattern_identifier": "it_company_preserving",
+        "positive_pattern": "ragione.?sociale",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "ja": [
+      {
+        "pattern_identifier": "ja_company_preserving",
+        "positive_pattern": "会社",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "ru": [
+      {
+        "pattern_identifier": "ru_company_preserving",
+        "positive_pattern": "название.?компании",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "zh": [
+      {
+        "pattern_identifier": "zh_company_preserving",
+        "positive_pattern": "单位|公司",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "fa": [
+      {
+        "pattern_identifier": "fa_company_preserving",
+        "positive_pattern": "شرکت",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "ko": [
+      {
+        "pattern_identifier": "ko_company_preserving",
+        "positive_pattern": "회사|직장",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "ADDRESS_LINE_1": {
+    "en": [
+      {
+        "pattern_identifier": "en_address_line_1_preserving",
+        "positive_pattern": "^address$|address[_-]?line(one)?|address1|addr1|street|(?:shipping|billing)address$|house.?name",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 68
+      }
+    ],
+    "de": [
+      {
+        "pattern_identifier": "de_address_line_1_preserving",
+        "positive_pattern": "strasse|straße",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 68
+      }
+    ],
+    "es": [
+      {
+        "pattern_identifier": "es_address_line_1_preserving",
+        "positive_pattern": "direccion|dirección",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 68
+      }
+    ],
+    "fr": [
+      {
+        "pattern_identifier": "fr_address_line_1_preserving",
+        "positive_pattern": "adresse",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 68
+      }
+    ],
+    "it": [
+      {
+        "pattern_identifier": "it_address_line_1_preserving",
+        "positive_pattern": "indirizzo",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 68
+      }
+    ],
+    "ja": [
+      {
+        "pattern_identifier": "ja_address_line_1_preserving",
+        "positive_pattern": "^住所$|住所1",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 68
+      }
+    ],
+    "pt": [
+      {
+        "pattern_identifier": "pt_address_line_1_preserving",
+        "positive_pattern": "morada|((?<!identificação do )endereço)",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 68
+      }
+    ],
+    "ru": [
+      {
+        "pattern_identifier": "ru_address_line_1_preserving",
+        "positive_pattern": "Адрес",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 68
+      }
+    ],
+    "zh": [
+      {
+        "pattern_identifier": "zh_address_line_1_preserving",
+        "positive_pattern": "地址",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 68
+      }
+    ],
+    "tr": [
+      {
+        "pattern_identifier": "tr_address_line_1_preserving",
+        "positive_pattern": "(\\b|_)adres(?! (başlığı(nız)?|tarifi))(\\b|_)",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 68
+      }
+    ],
+    "ko": [
+      {
+        "pattern_identifier": "ko_address_line_1_preserving",
+        "positive_pattern": "^주소.?$|주소.?1",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 68
+      }
+    ]
+  },
+  "ADDRESS_LINE_1_LABEL": {
+    "en": [
+      {
+        "pattern_identifier": "en_address_line_1_label_preserving",
+        "positive_pattern": "(^\\W*address)|(address\\W*$)|(?:shipping|billing|mailing|pick.?up|drop.?off|delivery|sender|postal|recipient|home|work|office|school|business|mail)[\\s\\-]+address|address\\s+(of|for|to|from)",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 1,
+        "match_field_input_types": 68
+      }
+    ],
+    "fr": [
+      {
+        "pattern_identifier": "fr_address_line_1_label_preserving",
+        "positive_pattern": "adresse",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 1,
+        "match_field_input_types": 68
+      }
+    ],
+    "it": [
+      {
+        "pattern_identifier": "it_address_line_1_label_preserving",
+        "positive_pattern": "indirizzo",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 1,
+        "match_field_input_types": 68
+      }
+    ],
+    "ja": [
+      {
+        "pattern_identifier": "ja_address_line_1_label_preserving",
+        "positive_pattern": "住所",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 1,
+        "match_field_input_types": 68
+      }
+    ],
+    "zh": [
+      {
+        "pattern_identifier": "zh_address_line_1_label_preserving",
+        "positive_pattern": "地址",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 1,
+        "match_field_input_types": 68
+      }
+    ],
+    "tr": [
+      {
+        "pattern_identifier": "tr_address_line_1_label_preserving",
+        "positive_pattern": "(\\b|_)adres(?! (başlığı(nız)?|tarifi))(\\b|_)",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 1,
+        "match_field_input_types": 68
+      }
+    ],
+    "ko": [
+      {
+        "pattern_identifier": "ko_address_line_1_label_preserving",
+        "positive_pattern": "주소",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 1,
+        "match_field_input_types": 68
+      }
+    ]
+  },
+  "ADDRESS_LINE_2": {
+    "en": [
+      {
+        "pattern_identifier": "en_address_line_2_preserving",
+        "positive_pattern": "address[_-]?line(2|two)|address2|addr2|street|suite|unit",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "de": [
+      {
+        "pattern_identifier": "de_address_line_2_preserving",
+        "positive_pattern": "adresszusatz|ergänzende.?angaben",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "es": [
+      {
+        "pattern_identifier": "es_address_line_2_preserving",
+        "positive_pattern": "direccion2|colonia|adicional",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "fr": [
+      {
+        "pattern_identifier": "fr_address_line_2_preserving",
+        "positive_pattern": "addresssuppl|complementnom|appartement",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "it": [
+      {
+        "pattern_identifier": "it_address_line_2_preserving",
+        "positive_pattern": "indirizzo2",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "ja": [
+      {
+        "pattern_identifier": "ja_address_line_2_preserving",
+        "positive_pattern": "住所2",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "pt": [
+      {
+        "pattern_identifier": "pt_address_line_2_preserving",
+        "positive_pattern": "complemento|addrcomplement",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "ru": [
+      {
+        "pattern_identifier": "ru_address_line_2_preserving",
+        "positive_pattern": "Улица",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "zh": [
+      {
+        "pattern_identifier": "zh_address_line_2_preserving",
+        "positive_pattern": "地址2",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "ko": [
+      {
+        "pattern_identifier": "ko_address_line_2_preserving",
+        "positive_pattern": "주소.?2",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "ADDRESS_LINE_2_LABEL": {
+    "en": [
+      {
+        "pattern_identifier": "en_address_line_2_label_preserving",
+        "positive_pattern": "address|line",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 1,
+        "match_field_input_types": 4
+      }
+    ],
+    "fr": [
+      {
+        "pattern_identifier": "fr_address_line_2_label_preserving",
+        "positive_pattern": "adresse",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 1,
+        "match_field_input_types": 4
+      }
+    ],
+    "it": [
+      {
+        "pattern_identifier": "it_address_line_2_label_preserving",
+        "positive_pattern": "indirizzo",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 1,
+        "match_field_input_types": 4
+      }
+    ],
+    "zh": [
+      {
+        "pattern_identifier": "zh_address_line_2_label_preserving",
+        "positive_pattern": "地址",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 1,
+        "match_field_input_types": 4
+      }
+    ],
+    "ko": [
+      {
+        "pattern_identifier": "ko_address_line_2_label_preserving",
+        "positive_pattern": "주소",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 1,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "ADDRESS_LINE_EXTRA": {
+    "en": [
+      {
+        "pattern_identifier": "en_address_line_extra_preserving",
+        "positive_pattern": "address.*line[3-9]|address[3-9]|addr[3-9]|street|line[3-9]",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "es": [
+      {
+        "pattern_identifier": "es_address_line_extra_preserving",
+        "positive_pattern": "municipio",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "fr": [
+      {
+        "pattern_identifier": "fr_address_line_extra_preserving",
+        "positive_pattern": "batiment|residence",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "it": [
+      {
+        "pattern_identifier": "it_address_line_extra_preserving",
+        "positive_pattern": "indirizzo[3-9]",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "ADDRESS_LOOKUP": {
+    "en": [
+      {
+        "pattern_identifier": "en_address_lookup_preserving",
+        "positive_pattern": "lookup",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "COUNTRY": {
+    "en": [
+      {
+        "pattern_identifier": "en_country_preserving",
+        "positive_pattern": "country|countries",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "es": [
+      {
+        "pattern_identifier": "es_country_preserving",
+        "positive_pattern": "país|pais",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "de": [
+      {
+        "pattern_identifier": "de_country_preserving",
+        "positive_pattern": "(\\b|_)land(\\b|_)(?!.*(mark.*))",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "ja": [
+      {
+        "pattern_identifier": "ja_country_preserving",
+        "positive_pattern": "(?<!(入|出))国",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "zh": [
+      {
+        "pattern_identifier": "zh_country_preserving",
+        "positive_pattern": "国家",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "ko": [
+      {
+        "pattern_identifier": "ko_country_preserving",
+        "positive_pattern": "국가|나라",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "tr": [
+      {
+        "pattern_identifier": "tr_country_preserving",
+        "positive_pattern": "(\\b|_)(ülke|ulce|ulke)(\\b|_)",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "fa": [
+      {
+        "pattern_identifier": "fa_country_preserving",
+        "positive_pattern": "کشور",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ]
+  },
+  "COUNTRY_LOCATION": {
+    "en": [
+      {
+        "pattern_identifier": "en_country_location_preserving",
+        "positive_pattern": "location",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 544
+      }
+    ]
+  },
+  "ZIP_CODE": {
+    "en": [
+      {
+        "pattern_identifier": "en_zip_code_preserving",
+        "positive_pattern": "zip|postal|post.*code|pcode|pin.?code",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "de": [
+      {
+        "pattern_identifier": "de_zip_code_preserving",
+        "positive_pattern": "postleitzahl",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "es": [
+      {
+        "pattern_identifier": "es_zip_code_preserving",
+        "positive_pattern": "\\bcp\\b",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "fr": [
+      {
+        "pattern_identifier": "fr_zip_code_preserving",
+        "positive_pattern": "\\bcdp\\b",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "it": [
+      {
+        "pattern_identifier": "it_zip_code_preserving",
+        "positive_pattern": "\\bcap\\b",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "ja": [
+      {
+        "pattern_identifier": "ja_zip_code_preserving",
+        "positive_pattern": "郵便番号",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "pt": [
+      {
+        "pattern_identifier": "pt_zip_code_preserving",
+        "positive_pattern": "codigo|codpos|\\bcep\\b",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "ru": [
+      {
+        "pattern_identifier": "ru_zip_code_preserving",
+        "positive_pattern": "Почтовый.?Индекс",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "hi": [
+      {
+        "pattern_identifier": "hi_zip_code_preserving",
+        "positive_pattern": "पिन.?कोड",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "ml": [
+      {
+        "pattern_identifier": "ml_zip_code_preserving",
+        "positive_pattern": "പിന്‍കോഡ്",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "zh": [
+      {
+        "pattern_identifier": "zh_zip_code_preserving",
+        "positive_pattern": "邮政编码|邮编|郵遞區號",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "tr": [
+      {
+        "pattern_identifier": "tr_zip_code_preserving",
+        "positive_pattern": "(\\b|_)posta kodu(\\b|_)",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "ko": [
+      {
+        "pattern_identifier": "ko_zip_code_preserving",
+        "positive_pattern": "우편.?번호",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ]
+  },
+  "ZIP_4": {
+    "en": [
+      {
+        "pattern_identifier": "en_zip_4_preserving",
+        "positive_pattern": "zip|^-$|post2",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "pt": [
+      {
+        "pattern_identifier": "pt_zip_4_preserving",
+        "positive_pattern": "codpos2",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ]
+  },
+  "CITY": {
+    "en": [
+      {
+        "pattern_identifier": "en_city_preserving",
+        "positive_pattern": "city|town|suburb",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "de": [
+      {
+        "pattern_identifier": "de_city_preserving",
+        "positive_pattern": "\\bort\\b|stadt",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "es": [
+      {
+        "pattern_identifier": "es_city_preserving",
+        "positive_pattern": "ciudad|provincia|localidad|poblacion",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "fr": [
+      {
+        "pattern_identifier": "fr_city_preserving",
+        "positive_pattern": "ville|commune",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "it": [
+      {
+        "pattern_identifier": "it_city_preserving",
+        "positive_pattern": "localita",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "ja": [
+      {
+        "pattern_identifier": "ja_city_preserving",
+        "positive_pattern": "市区町村",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "pt": [
+      {
+        "pattern_identifier": "pt_city_preserving",
+        "positive_pattern": "cidade",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "ru": [
+      {
+        "pattern_identifier": "ru_city_preserving",
+        "positive_pattern": "Город",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "zh": [
+      {
+        "pattern_identifier": "zh_city_preserving",
+        "positive_pattern": "市|分區",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "fa": [
+      {
+        "pattern_identifier": "fa_city_preserving",
+        "positive_pattern": "شهر",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "hi": [
+      {
+        "pattern_identifier": "hi_city_preserving",
+        "positive_pattern": "शहर|ग्राम|गाँव",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "ml": [
+      {
+        "pattern_identifier": "ml_city_preserving",
+        "positive_pattern": "നഗരം|ഗ്രാമം",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "tr": [
+      {
+        "pattern_identifier": "tr_city_preserving",
+        "positive_pattern": "((\\b|_|\\*)([İii̇]l[cç]e(miz|niz)?)(\\b|_|\\*))",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "ko": [
+      {
+        "pattern_identifier": "ko_city_preserving",
+        "positive_pattern": "^시[^도·・]|시[·・]?군[·・]?구",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ]
+  },
+  "STATE": {
+    "en": [
+      {
+        "pattern_identifier": "en_state_preserving",
+        "positive_pattern": "(?<!(united|hist|history).?)state|county|region|province|county|principality",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "ja": [
+      {
+        "pattern_identifier": "ja_state_preserving",
+        "positive_pattern": "都道府県",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "pt": [
+      {
+        "pattern_identifier": "pt_state_preserving",
+        "positive_pattern": "estado|provincia",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "ru": [
+      {
+        "pattern_identifier": "ru_state_preserving",
+        "positive_pattern": "область",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "zh": [
+      {
+        "pattern_identifier": "zh_state_preserving",
+        "positive_pattern": "省|地區",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "ml": [
+      {
+        "pattern_identifier": "ml_state_preserving",
+        "positive_pattern": "സംസ്ഥാനം",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "fa": [
+      {
+        "pattern_identifier": "fa_state_preserving",
+        "positive_pattern": "استان",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "hi": [
+      {
+        "pattern_identifier": "hi_state_preserving",
+        "positive_pattern": "राज्य",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "tr": [
+      {
+        "pattern_identifier": "tr_state_preserving",
+        "positive_pattern": "((\\b|_|\\*)(eyalet|[şs]ehir|[İii̇]l(imiz)?|kent)(\\b|_|\\*))",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ],
+    "ko": [
+      {
+        "pattern_identifier": "ko_state_preserving",
+        "positive_pattern": "^시[·・]?도",
+        "positive_score": 1.1,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 548
+      }
+    ]
+  },
+  "SEARCH_TERM": {
+    "en": [
+      {
+        "pattern_identifier": "en_search_term_preserving",
+        "positive_pattern": "^q$|search|query|qry",
+        "positive_score": 0.8,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 580
+      }
+    ],
+    "de": [
+      {
+        "pattern_identifier": "de_search_term_preserving",
+        "positive_pattern": "suche.*",
+        "positive_score": 0.8,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 580
+      }
+    ],
+    "zh": [
+      {
+        "pattern_identifier": "zh_search_term_preserving",
+        "positive_pattern": "搜索",
+        "positive_score": 0.8,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 580
+      }
+    ],
+    "ja": [
+      {
+        "pattern_identifier": "ja_search_term_preserving",
+        "positive_pattern": "探す|検索",
+        "positive_score": 0.8,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 580
+      }
+    ],
+    "fr": [
+      {
+        "pattern_identifier": "fr_search_term_preserving",
+        "positive_pattern": "recherch.*",
+        "positive_score": 0.8,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 580
+      }
+    ],
+    "pt": [
+      {
+        "pattern_identifier": "pt_search_term_preserving",
+        "positive_pattern": "busca",
+        "positive_score": 0.8,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 580
+      }
+    ],
+    "fa": [
+      {
+        "pattern_identifier": "fa_search_term_preserving",
+        "positive_pattern": "جستجو",
+        "positive_score": 0.8,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 580
+      }
+    ],
+    "ru": [
+      {
+        "pattern_identifier": "ru_search_term_preserving",
+        "positive_pattern": "искать|найти|поиск",
+        "positive_score": 0.8,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 580
+      }
+    ]
+  },
+  "PRICE": {
+    "en": [
+      {
+        "pattern_identifier": "en_price_preserving",
+        "positive_pattern": "\\bprice\\b|\\brate\\b|\\bcost\\b",
+        "positive_score": 0.95,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 868
+      }
+    ],
+    "ar": [
+      {
+        "pattern_identifier": "ar_price_preserving",
+        "positive_pattern": "قیمة‎|سعر‎",
+        "positive_score": 0.95,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 868
+      }
+    ],
+    "fa": [
+      {
+        "pattern_identifier": "fa_price_preserving",
+        "positive_pattern": "قیمت",
+        "positive_score": 0.95,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 868
+      }
+    ],
+    "fr": [
+      {
+        "pattern_identifier": "fr_price_preserving",
+        "positive_pattern": "\\bprix\\b|\\bcoût\\b|\\bcout\\b|\\btarif\\b",
+        "positive_score": 0.95,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 868
+      }
+    ]
+  },
+  "NAME_ON_CARD": {
+    "en": [
+      {
+        "pattern_identifier": "en_name_on_card_preserving",
+        "positive_pattern": "card.?(?:holder|owner)|name.*(\\b)?on(\\b)?.*card|(?:card|cc).?name|cc.?full.?name",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "de": [
+      {
+        "pattern_identifier": "de_name_on_card_preserving",
+        "positive_pattern": "karteninhaber",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "es": [
+      {
+        "pattern_identifier": "es_name_on_card_preserving",
+        "positive_pattern": "nombre.*tarjeta",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "fr": [
+      {
+        "pattern_identifier": "fr_name_on_card_preserving",
+        "positive_pattern": "nom.*carte",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "it": [
+      {
+        "pattern_identifier": "it_name_on_card_preserving",
+        "positive_pattern": "nome.*cart",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "ja": [
+      {
+        "pattern_identifier": "ja_name_on_card_preserving",
+        "positive_pattern": "名前",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "ru": [
+      {
+        "pattern_identifier": "ru_name_on_card_preserving",
+        "positive_pattern": "Имя.*карты",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "zh": [
+      {
+        "pattern_identifier": "zh_name_on_card_preserving",
+        "positive_pattern": "信用卡开户名|开户名|持卡人姓名|持卡人姓名",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "NAME_ON_CARD_CONTEXTUAL": {
+    "en": [
+      {
+        "pattern_identifier": "en_name_on_card_contextual_preserving",
+        "positive_pattern": "name",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "CARD_NUMBER": {
+    "en": [
+      {
+        "pattern_identifier": "en_card_number_preserving",
+        "positive_pattern": "(add)?(?:card|cc|acct).?(?:number|#|no|num|field)",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 404
+      }
+    ],
+    "de": [
+      {
+        "pattern_identifier": "de_card_number_preserving",
+        "positive_pattern": "(?<!telefon|haus|person|fødsels)nummer",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 404
+      }
+    ],
+    "ja": [
+      {
+        "pattern_identifier": "ja_card_number_preserving",
+        "positive_pattern": "カード番号",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 404
+      }
+    ],
+    "ru": [
+      {
+        "pattern_identifier": "ru_card_number_preserving",
+        "positive_pattern": "Номер.*карты",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 404
+      }
+    ],
+    "zh": [
+      {
+        "pattern_identifier": "zh_card_number_preserving",
+        "positive_pattern": "信用卡号|信用卡号码|信用卡卡號",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 404
+      }
+    ],
+    "ko": [
+      {
+        "pattern_identifier": "ko_card_number_preserving",
+        "positive_pattern": "카드",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 404
+      }
+    ],
+    "es": [
+      {
+        "pattern_identifier": "es_card_number_preserving",
+        "positive_pattern": "(numero|número|numéro)(?!.*(document|fono|phone|réservation))",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 404
+      }
+    ],
+    "pt": [
+      {
+        "pattern_identifier": "pt_card_number_preserving",
+        "positive_pattern": "(numero|número|numéro)(?!.*(document|fono|phone|réservation))",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 404
+      }
+    ],
+    "fr": [
+      {
+        "pattern_identifier": "fr_card_number_preserving",
+        "positive_pattern": "(numero|número|numéro)(?!.*(document|fono|phone|réservation))",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 404
+      }
+    ]
+  },
+  "CARD_CVC": {
+    "en": [
+      {
+        "pattern_identifier": "en_card_cvc_preserving",
+        "positive_pattern": "verification|card.?identification|security.?code|card.?code|security.?value|security.?number|card.?pin|c-v-v|(cvn|cvv|cvc|csc|cvd|cid|ccv)(field)?|\\bcid\\b",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 404
+      }
+    ]
+  },
+  "CARD_EXP_MONTH": {
+    "en": [
+      {
+        "pattern_identifier": "en_card_exp_month_preserving",
+        "positive_pattern": "expir|exp.*mo|exp.*date|ccmonth|cardmonth|addmonth",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ],
+    "de": [
+      {
+        "pattern_identifier": "de_card_exp_month_preserving",
+        "positive_pattern": "gueltig|gültig|monat",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ],
+    "es": [
+      {
+        "pattern_identifier": "es_card_exp_month_preserving",
+        "positive_pattern": "fecha",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ],
+    "fr": [
+      {
+        "pattern_identifier": "fr_card_exp_month_preserving",
+        "positive_pattern": "date.*exp",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ],
+    "it": [
+      {
+        "pattern_identifier": "it_card_exp_month_preserving",
+        "positive_pattern": "scadenza",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ],
+    "ja": [
+      {
+        "pattern_identifier": "ja_card_exp_month_preserving",
+        "positive_pattern": "有効期限",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ],
+    "pt": [
+      {
+        "pattern_identifier": "pt_card_exp_month_preserving",
+        "positive_pattern": "validade",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ],
+    "ru": [
+      {
+        "pattern_identifier": "ru_card_exp_month_preserving",
+        "positive_pattern": "Срок действия карты",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ],
+    "zh": [
+      {
+        "pattern_identifier": "zh_card_exp_month_preserving",
+        "positive_pattern": "月",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ]
+  },
+  "CARD_EXP_YEAR": {
+    "en": [
+      {
+        "pattern_identifier": "en_card_exp_year_preserving",
+        "positive_pattern": "exp|^/|(add)?year",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ],
+    "de": [
+      {
+        "pattern_identifier": "de_card_exp_year_preserving",
+        "positive_pattern": "ablaufdatum|gueltig|gültig|jahr",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ],
+    "es": [
+      {
+        "pattern_identifier": "es_card_exp_year_preserving",
+        "positive_pattern": "fecha",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ],
+    "it": [
+      {
+        "pattern_identifier": "it_card_exp_year_preserving",
+        "positive_pattern": "scadenza",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ],
+    "ja": [
+      {
+        "pattern_identifier": "ja_card_exp_year_preserving",
+        "positive_pattern": "有効期限",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ],
+    "pt": [
+      {
+        "pattern_identifier": "pt_card_exp_year_preserving",
+        "positive_pattern": "validade",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ],
+    "ru": [
+      {
+        "pattern_identifier": "ru_card_exp_year_preserving",
+        "positive_pattern": "Срок действия карты",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ],
+    "zh": [
+      {
+        "pattern_identifier": "zh_card_exp_year_preserving",
+        "positive_pattern": "年|有效期",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ]
+  },
+  "CARD_EXP_DATE_2_DIGIT_YEAR": {
+    "en": [
+      {
+        "pattern_identifier": "en_card_exp_date_2_digit_year_preserving",
+        "positive_pattern": "(?:exp.*date[^y\\n\\r]*|mm\\s*[-/]?\\s*)yy(?:[^y]|$)",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ]
+  },
+  "CARD_EXP_DATE_4_DIGIT_YEAR": {
+    "en": [
+      {
+        "pattern_identifier": "en_card_exp_date_4_digit_year_preserving",
+        "positive_pattern": "(?:exp.*date[^y\\n\\r]*|mm\\s*[-/]?\\s*)yyyy(?:[^y]|$)",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ]
+  },
+  "CARD_EXP_DATE": {
+    "en": [
+      {
+        "pattern_identifier": "en_card_exp_date_preserving",
+        "positive_pattern": "expir|exp.*date|^expfield$",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ],
+    "de": [
+      {
+        "pattern_identifier": "de_card_exp_date_preserving",
+        "positive_pattern": "gueltig|gültig",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ],
+    "es": [
+      {
+        "pattern_identifier": "es_card_exp_date_preserving",
+        "positive_pattern": "fecha",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ],
+    "fr": [
+      {
+        "pattern_identifier": "fr_card_exp_date_preserving",
+        "positive_pattern": "date.*exp",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ],
+    "it": [
+      {
+        "pattern_identifier": "it_card_exp_date_preserving",
+        "positive_pattern": "scadenza",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ],
+    "ja": [
+      {
+        "pattern_identifier": "ja_card_exp_date_preserving",
+        "positive_pattern": "有効期限",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ],
+    "pt": [
+      {
+        "pattern_identifier": "pt_card_exp_date_preserving",
+        "positive_pattern": "validade",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ],
+    "ru": [
+      {
+        "pattern_identifier": "ru_card_exp_date_preserving",
+        "positive_pattern": "Срок действия карты",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 948
+      }
+    ]
+  },
+  "GIFT_CARD": {
+    "en": [
+      {
+        "pattern_identifier": "en_gift_card_preserving",
+        "positive_pattern": "gift.?(card|cert)",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 788
+      }
+    ]
+  },
+  "DEBIT_GIFT_CARD": {
+    "en": [
+      {
+        "pattern_identifier": "en_debit_gift_card_preserving",
+        "positive_pattern": "(?:visa|mastercard|discover|amex|american express).*gift.?card",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 788
+      }
+    ]
+  },
+  "DEBIT_CARD": {
+    "en": [
+      {
+        "pattern_identifier": "en_debit_card_preserving",
+        "positive_pattern": "debit.*card",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 788
+      }
+    ]
+  },
+  "DAY": {
+    "en": [
+      {
+        "pattern_identifier": "en_day_preserving",
+        "positive_pattern": "day",
+        "positive_score": 1.0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 36
+      }
+    ]
+  },
+  "EMAIL_ADDRESS": {
+    "en": [
+      {
+        "pattern_identifier": "en_email_preserving",
+        "positive_pattern": "e.?mail",
+        "positive_score": 1.4,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 12
+      }
+    ],
+    "fr": [
+      {
+        "pattern_identifier": "fr_email_preserving",
+        "positive_pattern": "courriel",
+        "positive_score": 1.4,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 12
+      }
+    ],
+    "es": [
+      {
+        "pattern_identifier": "es_email_preserving",
+        "positive_pattern": "correo.*electr(o|ó)nico",
+        "positive_score": 1.4,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 12
+      }
+    ],
+    "ja": [
+      {
+        "pattern_identifier": "ja_email_preserving",
+        "positive_pattern": "メールアドレス",
+        "positive_score": 1.4,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 12
+      }
+    ],
+    "ru": [
+      {
+        "pattern_identifier": "ru_email_preserving",
+        "positive_pattern": "Электронной.?Почты",
+        "positive_score": 1.4,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 12
+      }
+    ],
+    "zh": [
+      {
+        "pattern_identifier": "zh_email_preserving",
+        "positive_pattern": "邮件|邮箱|電郵地址",
+        "positive_score": 1.4,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 12
+      }
+    ],
+    "ml": [
+      {
+        "pattern_identifier": "ml_email_preserving",
+        "positive_pattern": "ഇ-മെയില്‍|ഇലക്ട്രോണിക്.?മെയിൽ",
+        "positive_score": 1.4,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 12
+      }
+    ],
+    "fa": [
+      {
+        "pattern_identifier": "fa_email_preserving",
+        "positive_pattern": "ایمیل|پست.*الکترونیک",
+        "positive_score": 1.4,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 12
+      }
+    ],
+    "hi": [
+      {
+        "pattern_identifier": "hi_email_preserving",
+        "positive_pattern": "ईमेल|इलॅक्ट्रॉनिक.?मेल",
+        "positive_score": 1.4,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 12
+      }
+    ],
+    "tr": [
+      {
+        "pattern_identifier": "tr_email_preserving",
+        "positive_pattern": "(\\b|_)eposta(\\b|_)",
+        "positive_score": 1.4,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 12
+      }
+    ],
+    "ko": [
+      {
+        "pattern_identifier": "ko_email_preserving",
+        "positive_pattern": "(?:이메일|전자.?우편|[Ee]-?mail)(.?주소)?",
+        "positive_score": 1.4,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 12
+      }
+    ]
+  },
+  "NAME_IGNORED": {
+    "en": [
+      {
+        "pattern_identifier": "en_name_ignored_preserving",
+        "positive_pattern": "user.?name|user.?id|nickname|maiden name|title|prefix|suffix",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "tr": [
+      {
+        "pattern_identifier": "tr_name_ignored_preserving",
+        "positive_pattern": "adres başlığınız",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "de": [
+      {
+        "pattern_identifier": "de_name_ignored_preserving",
+        "positive_pattern": "vollständiger.?name",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "zh": [
+      {
+        "pattern_identifier": "zh_name_ignored_preserving",
+        "positive_pattern": "用户名",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "ko": [
+      {
+        "pattern_identifier": "ko_name_ignored_preserving",
+        "positive_pattern": "(?:사용자.?)?아이디|사용자.?ID",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "FULL_NAME": {
+    "en": [
+      {
+        "pattern_identifier": "en_full_name_preserving",
+        "positive_pattern": "^name|full.?name|your.?name|customer.?name|bill.?name|ship.?name|name.*first.*last|firstandlastname",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "es": [
+      {
+        "pattern_identifier": "es_full_name_preserving",
+        "positive_pattern": "nombre.*y.*apellidos",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "fr": [
+      {
+        "pattern_identifier": "fr_full_name_preserving",
+        "positive_pattern": "^nom(?!bre)",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "ja": [
+      {
+        "pattern_identifier": "ja_full_name_preserving",
+        "positive_pattern": "お名前|氏名",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "pt": [
+      {
+        "pattern_identifier": "pt_full_name_preserving",
+        "positive_pattern": "^nome",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "fa": [
+      {
+        "pattern_identifier": "fa_full_name_preserving",
+        "positive_pattern": "نام.*نام.*خانوادگی",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "zh": [
+      {
+        "pattern_identifier": "zh_full_name_preserving",
+        "positive_pattern": "姓名",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "tr": [
+      {
+        "pattern_identifier": "tr_full_name_preserving",
+        "positive_pattern": "(\\b|_|\\*)ad[ı]? soyad[ı]?(\\b|_|\\*)",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "ko": [
+      {
+        "pattern_identifier": "ko_full_name_preserving",
+        "positive_pattern": "성명",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "NAME_SPECIFIC": {
+    "en": [
+      {
+        "pattern_identifier": "en_name_specific_preserving",
+        "positive_pattern": "^name",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "fr": [
+      {
+        "pattern_identifier": "fr_name_specific_preserving",
+        "positive_pattern": "^nom",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "pt": [
+      {
+        "pattern_identifier": "pt_name_specific_preserving",
+        "positive_pattern": "^nome",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "FIRST_NAME": {
+    "en": [
+      {
+        "pattern_identifier": "en_first_name_preserving",
+        "positive_pattern": "first.*name|initials|fname|first$|given.*name",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "de": [
+      {
+        "pattern_identifier": "de_first_name_preserving",
+        "positive_pattern": "vorname",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "es": [
+      {
+        "pattern_identifier": "es_first_name_preserving",
+        "positive_pattern": "nombre",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "fr": [
+      {
+        "pattern_identifier": "fr_first_name_preserving",
+        "positive_pattern": "forename|prénom|prenom",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "ja": [
+      {
+        "pattern_identifier": "ja_first_name_preserving",
+        "positive_pattern": "名",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "pt": [
+      {
+        "pattern_identifier": "pt_first_name_preserving",
+        "positive_pattern": "nome",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "ru": [
+      {
+        "pattern_identifier": "ru_first_name_preserving",
+        "positive_pattern": "Имя",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "fa": [
+      {
+        "pattern_identifier": "fa_first_name_preserving",
+        "positive_pattern": "نام",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "ko": [
+      {
+        "pattern_identifier": "ko_first_name_preserving",
+        "positive_pattern": "이름",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "ml": [
+      {
+        "pattern_identifier": "ml_first_name_preserving",
+        "positive_pattern": "പേര്",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "tr": [
+      {
+        "pattern_identifier": "tr_first_name_preserving",
+        "positive_pattern": "(\\b|_|\\*)(isim|ad|ad(i|ı|iniz|ınız)?)(\\b|_|\\*)",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "hi": [
+      {
+        "pattern_identifier": "hi_first_name_preserving",
+        "positive_pattern": "नाम",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "MIDDLE_INITIAL": {
+    "en": [
+      {
+        "pattern_identifier": "en_middle_initial_preserving",
+        "positive_pattern": "middle.*initial|m\\.i\\.|mi$|\\bmi\\b",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "MIDDLE_NAME": {
+    "en": [
+      {
+        "pattern_identifier": "en_middle_name_preserving",
+        "positive_pattern": "middle.*name|mname|middle$",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "LAST_NAME": {
+    "en": [
+      {
+        "pattern_identifier": "en_last_name_preserving",
+        "positive_pattern": "last.*name|lname|surname(?!\\d)|last$|secondname|family.*name",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "de": [
+      {
+        "pattern_identifier": "de_last_name_preserving",
+        "positive_pattern": "nachname",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "es": [
+      {
+        "pattern_identifier": "es_last_name_preserving",
+        "positive_pattern": "apellidos?",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "fr": [
+      {
+        "pattern_identifier": "fr_last_name_preserving",
+        "positive_pattern": "famille|^nom(?!bre)",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "it": [
+      {
+        "pattern_identifier": "it_last_name_preserving",
+        "positive_pattern": "cognome",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "ja": [
+      {
+        "pattern_identifier": "ja_last_name_preserving",
+        "positive_pattern": "姓",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "pt": [
+      {
+        "pattern_identifier": "pt_last_name_preserving",
+        "positive_pattern": "apelidos|surename|sobrenome",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "ru": [
+      {
+        "pattern_identifier": "ru_last_name_preserving",
+        "positive_pattern": "Фамилия",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "fa": [
+      {
+        "pattern_identifier": "fa_last_name_preserving",
+        "positive_pattern": "نام.*خانوادگی",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "hi": [
+      {
+        "pattern_identifier": "hi_last_name_preserving",
+        "positive_pattern": "उपनाम",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "ml": [
+      {
+        "pattern_identifier": "ml_last_name_preserving",
+        "positive_pattern": "മറുപേര്",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "tr": [
+      {
+        "pattern_identifier": "tr_last_name_preserving",
+        "positive_pattern": "(\\b|_|\\*)(soyisim|soyad(i|ı|iniz|ınız)?)(\\b|_|\\*)",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "ko": [
+      {
+        "pattern_identifier": "ko_last_name_preserving",
+        "positive_pattern": "\\b성(?:[^명]|\\b)",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "LAST_NAME_FIRST": {
+    "es": [
+      {
+        "pattern_identifier": "es_last_name_first_preserving",
+        "positive_pattern": "(primer.*apellido)|(apellido1)|(apellido.*paterno)|surname_?1|first(\\s|_)?surname",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "LAST_NAME_SECOND": {
+    "es": [
+      {
+        "pattern_identifier": "es_last_name_second_preserving",
+        "positive_pattern": "(segund.*apellido)|(apellido2)|(apellido.*materno)|surname_?2|second(\\s|_)?surname",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "HONORIFIC_PREFIX": {
+    "en": [
+      {
+        "pattern_identifier": "en_honorific_prefix_preserving",
+        "positive_pattern": "^title:?$|(salutation(?! and given name))",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "de": [
+      {
+        "pattern_identifier": "de_honorific_prefix_preserving",
+        "positive_pattern": "anrede|titel",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "es": [
+      {
+        "pattern_identifier": "es_honorific_prefix_preserving",
+        "positive_pattern": "tratamiento|encabezamiento",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "it": [
+      {
+        "pattern_identifier": "it_honorific_prefix_preserving",
+        "positive_pattern": "titolo",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "fr": [
+      {
+        "pattern_identifier": "fr_honorific_prefix_preserving",
+        "positive_pattern": "titre",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "ru": [
+      {
+        "pattern_identifier": "ru_honorific_prefix_preserving",
+        "positive_pattern": "обраще́ние|зва́ние",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "el": [
+      {
+        "pattern_identifier": "el_honorific_prefix_preserving",
+        "positive_pattern": "προσφώνηση",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "tr": [
+      {
+        "pattern_identifier": "tr_honorific_prefix_preserving",
+        "positive_pattern": "hitap",
+        "positive_score": 0.9,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "PHONE": {
+    "en": [
+      {
+        "pattern_identifier": "en_phone_preserving",
+        "positive_pattern": "phone|mobile|contact.?number",
+        "positive_score": 1.2,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "de": [
+      {
+        "pattern_identifier": "de_phone_preserving",
+        "positive_pattern": "telefonnummer",
+        "positive_score": 1.2,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "es": [
+      {
+        "pattern_identifier": "es_phone_preserving",
+        "positive_pattern": "telefono|teléfono",
+        "positive_score": 1.2,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "fr": [
+      {
+        "pattern_identifier": "fr_phone_preserving",
+        "positive_pattern": "telfixe",
+        "positive_score": 1.2,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "ja": [
+      {
+        "pattern_identifier": "ja_phone_preserving",
+        "positive_pattern": "電話",
+        "positive_score": 1.2,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "pt": [
+      {
+        "pattern_identifier": "pt_phone_preserving",
+        "positive_pattern": "telefone|telemovel",
+        "positive_score": 1.2,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "ru": [
+      {
+        "pattern_identifier": "ru_phone_preserving",
+        "positive_pattern": "телефон",
+        "positive_score": 1.2,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "hi": [
+      {
+        "pattern_identifier": "hi_phone_preserving",
+        "positive_pattern": "मोबाइल",
+        "positive_score": 1.2,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "tr": [
+      {
+        "pattern_identifier": "tr_phone_preserving",
+        "positive_pattern": "(\\b|_|\\*)telefon(\\b|_|\\*)",
+        "positive_score": 1.2,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "zh": [
+      {
+        "pattern_identifier": "zh_phone_preserving",
+        "positive_pattern": "电话",
+        "positive_score": 1.2,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "ml": [
+      {
+        "pattern_identifier": "ml_phone_preserving",
+        "positive_pattern": "മൊബൈല്‍",
+        "positive_score": 1.2,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "ko": [
+      {
+        "pattern_identifier": "ko_phone_preserving",
+        "positive_pattern": "(?:전화|핸드폰|휴대폰|휴대전화)(?:.?번호)?",
+        "positive_score": 1.2,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ]
+  },
+  "AUGMENTED_PHONE_COUNTRY_CODE": {
+    "en": [
+      {
+        "pattern_identifier": "en_augmented_phone_country_code_preserving",
+        "positive_pattern": "^[^0-9+]*(?:\\+|00)\\s*([1-9]\\d{0,3})\\D*$",
+        "positive_score": 1.3,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "PHONE_COUNTRY_CODE": {
+    "en": [
+      {
+        "pattern_identifier": "en_phone_country_code_preserving",
+        "positive_pattern": "country.*code|ccode|_cc|phone.*code|user.*phone.*code",
+        "positive_score": 1.3,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 308
+      }
+    ]
+  },
+  "PHONE_AREA_CODE_NO_TEXT": {
+    "en": [
+      {
+        "pattern_identifier": "en_phone_area_code_no_text_preserving",
+        "positive_pattern": "^\\($",
+        "positive_score": 1.3,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ]
+  },
+  "PHONE_AREA_CODE": {
+    "en": [
+      {
+        "pattern_identifier": "en_phone_area_code_preserving",
+        "positive_pattern": "area.*code|acode|area",
+        "positive_score": 1.3,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "ko": [
+      {
+        "pattern_identifier": "ko_phone_area_code_preserving",
+        "positive_pattern": "지역.?번호",
+        "positive_score": 1.3,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ]
+  },
+  "PHONE_PREFIX_SEPARATOR": {
+    "en": [
+      {
+        "pattern_identifier": "en_phone_prefix_separator_preserving",
+        "positive_pattern": "^-$|^\\)$",
+        "positive_score": 1.3,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ]
+  },
+  "PHONE_SUFFIX_SEPARATOR": {
+    "en": [
+      {
+        "pattern_identifier": "en_phone_suffix_separator_preserving",
+        "positive_pattern": "^-$",
+        "positive_score": 1.3,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ]
+  },
+  "PHONE_PREFIX": {
+    "en": [
+      {
+        "pattern_identifier": "en_phone_prefix_preserving",
+        "positive_pattern": "prefix|exchange",
+        "positive_score": 1.3,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "fr": [
+      {
+        "pattern_identifier": "fr_phone_prefix_preserving",
+        "positive_pattern": "preselection",
+        "positive_score": 1.3,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "pt": [
+      {
+        "pattern_identifier": "pt_phone_prefix_preserving",
+        "positive_pattern": "ddd",
+        "positive_score": 1.3,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ]
+  },
+  "PHONE_SUFFIX": {
+    "en": [
+      {
+        "pattern_identifier": "en_phone_suffix_preserving",
+        "positive_pattern": "suffix",
+        "positive_score": 1.3,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ]
+  },
+  "PHONE_EXTENSION": {
+    "en": [
+      {
+        "pattern_identifier": "en_phone_extension_preserving",
+        "positive_pattern": "\\bext|ext\\b|extension",
+        "positive_score": 1.3,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ],
+    "pt": [
+      {
+        "pattern_identifier": "pt_phone_extension_preserving",
+        "positive_pattern": "ramal",
+        "positive_score": 1.3,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 276
+      }
+    ]
+  },
+  "PASSPORT": {
+    "en": [
+      {
+        "pattern_identifier": "en_passport_preserving",
+        "positive_pattern": "document.*number|passport",
+        "positive_score": 1.2,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "fr": [
+      {
+        "pattern_identifier": "fr_passport_preserving",
+        "positive_pattern": "passeport",
+        "positive_score": 1.2,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "es": [
+      {
+        "pattern_identifier": "es_passport_preserving",
+        "positive_pattern": "numero.*documento|pasaporte",
+        "positive_score": 1.2,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "ja": [
+      {
+        "pattern_identifier": "ja_passport_preserving",
+        "positive_pattern": "書類",
+        "positive_score": 1.2,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "TRAVEL_ORIGIN": {
+    "en": [
+      {
+        "pattern_identifier": "en_travel_origin_preserving",
+        "positive_pattern": "point.*of.*entry|arrival",
+        "positive_score": 1.2,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "es": [
+      {
+        "pattern_identifier": "es_travel_origin_preserving",
+        "positive_pattern": "punto.*internaci(o|ó)n|fecha.*llegada",
+        "positive_score": 1.2,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "ja": [
+      {
+        "pattern_identifier": "ja_travel_origin_preserving",
+        "positive_pattern": "入国",
+        "positive_score": 1.2,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "TRAVEL_DESTINATION": {
+    "en": [
+      {
+        "pattern_identifier": "en_travel_destination_preserving",
+        "positive_pattern": "departure",
+        "positive_score": 1.2,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "es": [
+      {
+        "pattern_identifier": "es_travel_destination_preserving",
+        "positive_pattern": "fecha.*salida|destino",
+        "positive_score": 1.2,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "ja": [
+      {
+        "pattern_identifier": "ja_travel_destination_preserving",
+        "positive_pattern": "出国",
+        "positive_score": 1.2,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "FLIGHT": {
+    "en": [
+      {
+        "pattern_identifier": "en_flight_preserving",
+        "positive_pattern": "airline|flight",
+        "positive_score": 1.2,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "es": [
+      {
+        "pattern_identifier": "es_flight_preserving",
+        "positive_pattern": "aerol(i|í)nea|n(u|ú)mero.*vuelo",
+        "positive_score": 1.2,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ],
+    "ja": [
+      {
+        "pattern_identifier": "ja_flight_preserving",
+        "positive_pattern": "便名|航空会社",
+        "positive_score": 1.2,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "UPI_VIRTUAL_PAYMENT_ADDRESS": {
+    "en": [
+      {
+        "pattern_identifier": "en_upi_virtual_payment_address_user@(IFSC/Aadhaar/Mobile/RuPay)_preserving",
+        "positive_pattern": "^[\\w.+-_]+@(\\w+\\.ifsc\\.npci|aadhaar\\.npci|mobile\\.npci|rupay\\.npci)$",
+        "positive_score": 0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      },
+      {
+        "pattern_identifier": "en_upi_virtual_payment_address_user@(bank_list)_preserving",
+        "positive_pattern": "^[\\w.+-_]+@(airtel|airtelpaymentsbank|albk|allahabadbank|allbank|andb|apb|apl|axis|axisbank|axisgo|bandhan|barodampay|birla|boi|cbin|cboi|centralbank|cmsidfc|cnrb|csbcash|csbpay|cub|dbs|dcb|dcbbank|denabank|dlb|eazypay|equitas|ezeepay|fbl|federal|finobank|hdfcbank|hsbc|icici|idbi|idbibank|idfc|idfcbank|idfcnetc|ikwik|imobile|indbank|indianbank|indianbk|indus|iob|jkb|jsb|jsbp|karb|karurvysyabank|kaypay|kbl|kbl052|kmb|kmbl|kotak|kvb|kvbank|lime|lvb|lvbank|mahb|obc|okaxis|okbizaxis|okhdfcbank|okicici|oksbi|paytm|payzapp|pingpay|pnb|pockets|psb|purz|rajgovhdfcbank|rbl|sbi|sc|scb|scbl|scmobile|sib|srcb|synd|syndbank|syndicate|tjsb|tjsp|ubi|uboi|uco|unionbank|unionbankofindia|united|upi|utbi|vijayabank|vijb|vjb|ybl|yesbank|yesbankltd)$",
+        "positive_score": 0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "INTERNATIONAL_BANK_ACCOUNT_NUMBER": {
+    "en": [
+      {
+        "pattern_identifier": "en_international_bank_account_number_preserving",
+        "positive_pattern": "^[a-zA-Z]{2}[0-9]{2}[a-zA-Z0-9]{4}[0-9]{7}([a-zA-Z0-9]?){0,16}$",
+        "positive_score": 0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "CREDIT_CARD_CVC": {
+    "en": [
+      {
+        "pattern_identifier": "en_credit_card_cvc_preserving",
+        "positive_pattern": "^\\d{3,4}$",
+        "positive_score": 0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "CREDIT_CARD_EXP_YEAR": {
+    "en": [
+      {
+        "pattern_identifier": "en_credit_card_exp_year_preserving",
+        "positive_pattern": "^[2][0][1-9][0-9]$",
+        "positive_score": 0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "URL_SEARCH_ACTION": {
+    "en": [
+      {
+        "pattern_identifier": "en_url_search_action_preserving",
+        "positive_pattern": "/search(/|((\\w*\\.\\w+)?$))",
+        "positive_score": 0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "SOCIAL_SECURITY": {
+    "en": [
+      {
+        "pattern_identifier": "en_social_security_preserving",
+        "positive_pattern": "ssn|social.?security.?(num(ber)?|#)*",
+        "positive_score": 0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  },
+  "ONE_TIME_PASSWORD": {
+    "en": [
+      {
+        "pattern_identifier": "en_one_time_password_preserving",
+        "positive_pattern": "one.?time|sms.?(code|token|password|pwd|pass)",
+        "positive_score": 0,
+        "negative_pattern": "",
+        "match_field_attributes": 3,
+        "match_field_input_types": 4
+      }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/components/autofill/core/browser/pattern_provider/test_pattern_provider.cc b/components/autofill/core/browser/pattern_provider/test_pattern_provider.cc
new file mode 100644
index 0000000..1a15523
--- /dev/null
+++ b/components/autofill/core/browser/pattern_provider/test_pattern_provider.cc
@@ -0,0 +1,24 @@
+// 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/autofill/core/browser/pattern_provider/test_pattern_provider.h"
+
+#include "components/autofill/core/browser/pattern_provider/pattern_configuration_parser.h"
+
+namespace autofill {
+
+TestPatternProvider::TestPatternProvider() {
+  base::Optional<PatternProvider::Map> patterns =
+      field_type_parsing::GetPatternsFromResourceBundleSynchronously();
+  if (patterns)
+    patterns_ = patterns.value();
+
+  PatternProvider::SetPatternProviderForTesting(this);
+}
+
+TestPatternProvider::~TestPatternProvider() {
+  PatternProvider::ResetPatternProvider();
+}
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/pattern_provider/test_pattern_provider.h b/components/autofill/core/browser/pattern_provider/test_pattern_provider.h
new file mode 100644
index 0000000..dc92323
--- /dev/null
+++ b/components/autofill/core/browser/pattern_provider/test_pattern_provider.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 COMPONENTS_AUTOFILL_CORE_BROWSER_PATTERN_PROVIDER_TEST_PATTERN_PROVIDER_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_PATTERN_PROVIDER_TEST_PATTERN_PROVIDER_H_
+
+#include "components/autofill/core/browser/pattern_provider/pattern_provider.h"
+
+namespace autofill {
+
+// The pattern provider to be used in tests. Loads the MatchingPattern
+// configuration synchronously from the Resource Bundle and sets itself as the
+// global PatternProvider.
+class TestPatternProvider : public PatternProvider {
+ public:
+  TestPatternProvider();
+  ~TestPatternProvider();
+};
+
+}  // namespace autofill
+
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_PATTERN_PROVIDER_TEST_PATTERN_PROVIDER_H_
diff --git a/components/client_hints/browser/client_hints.cc b/components/client_hints/browser/client_hints.cc
index b3766ce..42e16bc5 100644
--- a/components/client_hints/browser/client_hints.cc
+++ b/components/client_hints/browser/client_hints.cc
@@ -110,9 +110,8 @@
   if (expiration_duration <= base::TimeDelta::FromSeconds(0))
     return;
 
-  std::unique_ptr<base::ListValue> expiration_times_list =
-      std::make_unique<base::ListValue>();
-  expiration_times_list->Reserve(client_hints.size());
+  base::Value::ListStorage expiration_times_list;
+  expiration_times_list.reserve(client_hints.size());
 
   // Use wall clock since the expiration time would be persisted across embedder
   // restarts.
@@ -120,12 +119,12 @@
       (base::Time::Now() + expiration_duration).ToDoubleT();
 
   for (const auto& entry : client_hints)
-    expiration_times_list->AppendInteger(static_cast<int>(entry));
+    expiration_times_list.push_back(base::Value(static_cast<int>(entry)));
 
   auto expiration_times_dictionary = std::make_unique<base::DictionaryValue>();
-  expiration_times_dictionary->SetList("client_hints",
-                                       std::move(expiration_times_list));
-  expiration_times_dictionary->SetDouble("expiration_time", expiration_time);
+  expiration_times_dictionary->SetKey(
+      "client_hints", base::Value(std::move(expiration_times_list)));
+  expiration_times_dictionary->SetDoubleKey("expiration_time", expiration_time);
 
   // TODO(tbansal): crbug.com/735518. Disable updates to client hints settings
   // when cookies are disabled for |primary_origin|.
diff --git a/components/domain_reliability/quic_error_mapping.cc b/components/domain_reliability/quic_error_mapping.cc
index 7d104df..bf31314 100644
--- a/components/domain_reliability/quic_error_mapping.cc
+++ b/components/domain_reliability/quic_error_mapping.cc
@@ -384,6 +384,7 @@
     {quic::QUIC_SILENT_IDLE_TIMEOUT, "quic.silent_idle_timeout"},
     {quic::QUIC_HTTP_RECEIVE_SPDY_SETTING, "quic.http_receive_spdy_setting"},
     {quic::QUIC_MISSING_WRITE_KEYS, "quic.missing_write_keys"},
+    {quic::QUIC_HTTP_RECEIVE_SPDY_FRAME, "quic.http_receive_spdy_frame"},
 
     // QUIC_INVALID_APPLICATION_CLOSE_DATA was code 101. The code has been
     // deprecated, but to keep the assert below happy, there needs to be
diff --git a/components/federated_learning/floc_blocklist_service.cc b/components/federated_learning/floc_blocklist_service.cc
index 21a7f26..f33713f9 100644
--- a/components/federated_learning/floc_blocklist_service.cc
+++ b/components/federated_learning/floc_blocklist_service.cc
@@ -90,12 +90,14 @@
 }
 
 bool FlocBlocklistService::IsBlocklistFileReady() const {
-  return blocklist_file_path_.has_value();
+  return first_file_ready_seen_;
 }
 
-void FlocBlocklistService::OnBlocklistFileReady(
-    const base::FilePath& file_path) {
+void FlocBlocklistService::OnBlocklistFileReady(const base::FilePath& file_path,
+                                                const base::Version& version) {
   blocklist_file_path_ = file_path;
+  blocklist_version_ = version;
+  first_file_ready_seen_ = true;
 
   for (auto& observer : observers_)
     observer.OnBlocklistFileReady();
@@ -103,13 +105,20 @@
 
 void FlocBlocklistService::FilterByBlocklist(
     const FlocId& unfiltered_floc,
+    const base::Optional<base::Version>& version_to_validate,
     FilterByBlocklistCallback callback) {
   DCHECK(unfiltered_floc.IsValid());
-  DCHECK(blocklist_file_path_.has_value());
+  DCHECK(first_file_ready_seen_);
+  if (version_to_validate &&
+      version_to_validate.value().CompareTo(blocklist_version_) != 0) {
+    std::move(callback).Run(FlocId());
+    return;
+  }
+
   base::PostTaskAndReplyWithResult(
       background_task_runner_.get(), FROM_HERE,
       base::BindOnce(&FilterByBlocklistOnBackgroundThread, unfiltered_floc,
-                     blocklist_file_path_.value()),
+                     blocklist_file_path_),
       std::move(callback));
 }
 
diff --git a/components/federated_learning/floc_blocklist_service.h b/components/federated_learning/floc_blocklist_service.h
index b7305d41..9cd4ae4 100644
--- a/components/federated_learning/floc_blocklist_service.h
+++ b/components/federated_learning/floc_blocklist_service.h
@@ -13,6 +13,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/optional.h"
+#include "base/version.h"
 #include "components/federated_learning/floc_id.h"
 
 namespace base {
@@ -47,11 +48,14 @@
   bool IsBlocklistFileReady() const;
 
   // Virtual for testing.
-  virtual void OnBlocklistFileReady(const base::FilePath& file_path);
+  virtual void OnBlocklistFileReady(const base::FilePath& file_path,
+                                    const base::Version& version);
 
   // Virtual for testing.
-  virtual void FilterByBlocklist(const FlocId& unfiltered_floc,
-                                 FilterByBlocklistCallback callback);
+  virtual void FilterByBlocklist(
+      const FlocId& unfiltered_floc,
+      const base::Optional<base::Version>& version_to_validate,
+      FilterByBlocklistCallback callback);
 
   void SetBackgroundTaskRunnerForTesting(
       scoped_refptr<base::SequencedTaskRunner> background_task_runner);
@@ -66,7 +70,9 @@
 
   base::ObserverList<Observer>::Unchecked observers_;
 
-  base::Optional<base::FilePath> blocklist_file_path_;
+  bool first_file_ready_seen_ = false;
+  base::FilePath blocklist_file_path_;
+  base::Version blocklist_version_;
 
   base::WeakPtrFactory<FlocBlocklistService> weak_ptr_factory_;
 };
diff --git a/components/federated_learning/floc_blocklist_service_unittest.cc b/components/federated_learning/floc_blocklist_service_unittest.cc
index 054b0e4..c2dc6549 100644
--- a/components/federated_learning/floc_blocklist_service_unittest.cc
+++ b/components/federated_learning/floc_blocklist_service_unittest.cc
@@ -21,6 +21,10 @@
 
 namespace federated_learning {
 
+namespace {
+
+base::Version kDummyVersion = base::Version("1.0.0");
+
 class CopyingFileOutputStream
     : public google::protobuf::io::CopyingOutputStream {
  public:
@@ -41,6 +45,8 @@
   base::File file_;
 };
 
+}  // namespace
+
 class FlocBlocklistServiceTest : public ::testing::Test {
  public:
   FlocBlocklistServiceTest()
@@ -86,7 +92,7 @@
   base::FilePath InitializeBlocklistFile(
       const std::vector<uint64_t>& blocklist) {
     base::FilePath file_path = CreateBlocklistFile(blocklist);
-    service()->OnBlocklistFileReady(file_path);
+    service()->OnBlocklistFileReady(file_path, kDummyVersion);
     EXPECT_TRUE(blocklist_file_path().has_value());
     return file_path;
   }
@@ -95,7 +101,10 @@
 
   FlocBlocklistService* service() { return service_.get(); }
 
-  const base::Optional<base::FilePath>& blocklist_file_path() {
+  base::Optional<base::FilePath> blocklist_file_path() {
+    if (!service()->first_file_ready_seen_)
+      return base::nullopt;
+
     return service()->blocklist_file_path_;
   }
 
@@ -108,7 +117,7 @@
       run_loop.Quit();
     });
 
-    service()->FilterByBlocklist(unfiltered_floc, std::move(cb));
+    service()->FilterByBlocklist(unfiltered_floc, kDummyVersion, std::move(cb));
     background_task_runner_->RunPendingTasks();
     run_loop.Run();
 
@@ -167,7 +176,7 @@
 
 TEST_F(FlocBlocklistServiceTest, NonExistentBlocklist_Blocked) {
   base::FilePath file_path = GetUniqueTemporaryPath();
-  service()->OnBlocklistFileReady(file_path);
+  service()->OnBlocklistFileReady(file_path, kDummyVersion);
   EXPECT_EQ(FlocId(), FilterByBlocklist(FlocId(3)));
 }
 
diff --git a/components/federated_learning/floc_sorting_lsh_clusters_service.cc b/components/federated_learning/floc_sorting_lsh_clusters_service.cc
index e9bc065..4d93529 100644
--- a/components/federated_learning/floc_sorting_lsh_clusters_service.cc
+++ b/components/federated_learning/floc_sorting_lsh_clusters_service.cc
@@ -116,29 +116,46 @@
   observers_.RemoveObserver(observer);
 }
 
-void FlocSortingLshClustersService::SetBackgroundTaskRunnerForTesting(
-    scoped_refptr<base::SequencedTaskRunner> background_task_runner) {
-  background_task_runner_ = background_task_runner;
+bool FlocSortingLshClustersService::IsSortingLshClustersFileReady() const {
+  return first_file_ready_seen_;
+}
+
+void FlocSortingLshClustersService::OnSortingLshClustersFileReady(
+    const base::FilePath& file_path,
+    const base::Version& version) {
+  sorting_lsh_clusters_file_path_ = file_path;
+  sorting_lsh_clusters_version_ = version;
+  first_file_ready_seen_ = true;
+
+  for (auto& observer : observers_)
+    observer.OnSortingLshClustersFileReady();
 }
 
 void FlocSortingLshClustersService::ApplySortingLsh(
     const FlocId& raw_floc_id,
     ApplySortingLshCallback callback) {
   DCHECK(raw_floc_id.IsValid());
-  DCHECK(sorting_lsh_clusters_file_path_.has_value());
+  DCHECK(first_file_ready_seen_);
+
   base::PostTaskAndReplyWithResult(
       background_task_runner_.get(), FROM_HERE,
       base::BindOnce(&ApplySortingLshOnBackgroundThread, raw_floc_id,
-                     sorting_lsh_clusters_file_path_.value()),
-      std::move(callback));
+                     sorting_lsh_clusters_file_path_),
+      base::BindOnce(&FlocSortingLshClustersService::DidApplySortingLsh,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback),
+                     sorting_lsh_clusters_version_));
 }
 
-void FlocSortingLshClustersService::OnSortingLshClustersFileReady(
-    const base::FilePath& file_path) {
-  sorting_lsh_clusters_file_path_ = file_path;
+void FlocSortingLshClustersService::SetBackgroundTaskRunnerForTesting(
+    scoped_refptr<base::SequencedTaskRunner> background_task_runner) {
+  background_task_runner_ = background_task_runner;
+}
 
-  for (auto& observer : observers_)
-    observer.OnSortingLshClustersFileReady();
+void FlocSortingLshClustersService::DidApplySortingLsh(
+    ApplySortingLshCallback callback,
+    base::Version version,
+    FlocId floc_id) {
+  std::move(callback).Run(std::move(floc_id), std::move(version));
 }
 
 }  // namespace federated_learning
diff --git a/components/federated_learning/floc_sorting_lsh_clusters_service.h b/components/federated_learning/floc_sorting_lsh_clusters_service.h
index 8db8863..57e6d3f 100644
--- a/components/federated_learning/floc_sorting_lsh_clusters_service.h
+++ b/components/federated_learning/floc_sorting_lsh_clusters_service.h
@@ -13,6 +13,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/optional.h"
+#include "base/version.h"
 #include "components/federated_learning/floc_id.h"
 
 namespace base {
@@ -28,7 +29,8 @@
 // File reading and parsing is posted to |background_task_runner_|.
 class FlocSortingLshClustersService {
  public:
-  using ApplySortingLshCallback = base::OnceCallback<void(FlocId)>;
+  using ApplySortingLshCallback =
+      base::OnceCallback<void(FlocId, base::Optional<base::Version>)>;
 
   class Observer {
    public:
@@ -46,24 +48,34 @@
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
 
+  bool IsSortingLshClustersFileReady() const;
+
+  // Virtual for testing.
+  virtual void OnSortingLshClustersFileReady(const base::FilePath& file_path,
+                                             const base::Version& version);
+
+  // Virtual for testing.
+  virtual void ApplySortingLsh(const FlocId& raw_floc_id,
+                               ApplySortingLshCallback callback);
+
   void SetBackgroundTaskRunnerForTesting(
       scoped_refptr<base::SequencedTaskRunner> background_task_runner);
 
-  void ApplySortingLsh(const FlocId& raw_floc_id,
-                       ApplySortingLshCallback callback);
-
-  // Virtual for testing.
-  virtual void OnSortingLshClustersFileReady(const base::FilePath& file_path);
-
  private:
   friend class FlocSortingLshClustersServiceTest;
 
+  void DidApplySortingLsh(ApplySortingLshCallback callback,
+                          base::Version version,
+                          FlocId floc_id);
+
   // Runner for tasks that do not influence user experience.
   scoped_refptr<base::SequencedTaskRunner> background_task_runner_;
 
   base::ObserverList<Observer>::Unchecked observers_;
 
-  base::Optional<base::FilePath> sorting_lsh_clusters_file_path_;
+  bool first_file_ready_seen_ = false;
+  base::FilePath sorting_lsh_clusters_file_path_;
+  base::Version sorting_lsh_clusters_version_;
 
   base::WeakPtrFactory<FlocSortingLshClustersService> weak_ptr_factory_;
 };
diff --git a/components/federated_learning/floc_sorting_lsh_clusters_service_unittest.cc b/components/federated_learning/floc_sorting_lsh_clusters_service_unittest.cc
index 82b6640..f7d94e5f1 100644
--- a/components/federated_learning/floc_sorting_lsh_clusters_service_unittest.cc
+++ b/components/federated_learning/floc_sorting_lsh_clusters_service_unittest.cc
@@ -23,6 +23,8 @@
 
 namespace {
 
+base::Version kDummyVersion = base::Version("1.2.3");
+
 class CopyingFileOutputStream
     : public google::protobuf::io::CopyingOutputStream {
  public:
@@ -94,7 +96,7 @@
       const std::vector<uint32_t>& sorting_lsh_clusters) {
     base::FilePath file_path =
         CreateTestSortingLshClustersFile(sorting_lsh_clusters);
-    service()->OnSortingLshClustersFileReady(file_path);
+    service()->OnSortingLshClustersFileReady(file_path, kDummyVersion);
     EXPECT_TRUE(sorting_lsh_clusters_file_path().has_value());
     return file_path;
   }
@@ -103,7 +105,10 @@
 
   FlocSortingLshClustersService* service() { return service_.get(); }
 
-  const base::Optional<base::FilePath>& sorting_lsh_clusters_file_path() {
+  base::Optional<base::FilePath> sorting_lsh_clusters_file_path() {
+    if (!service()->first_file_ready_seen_)
+      return base::nullopt;
+
     return service()->sorting_lsh_clusters_file_path_;
   }
 
@@ -111,10 +116,11 @@
     FlocId result;
 
     base::RunLoop run_loop;
-    auto cb = base::BindLambdaForTesting([&](FlocId floc_id) {
-      result = floc_id;
-      run_loop.Quit();
-    });
+    auto cb = base::BindLambdaForTesting(
+        [&](FlocId floc_id, base::Optional<base::Version> version) {
+          result = floc_id;
+          run_loop.Quit();
+        });
 
     service()->ApplySortingLsh(floc_id, std::move(cb));
     background_task_runner_->RunPendingTasks();
@@ -216,11 +222,12 @@
   base::FilePath file_path = InitializeSortingLshClustersFile({0});
 
   base::RunLoop run_loop;
-  auto cb = base::BindLambdaForTesting([&](FlocId floc_id) {
-    // Since the file has been deleted, expect an invalid floc id.
-    EXPECT_EQ(FlocId(), floc_id);
-    run_loop.Quit();
-  });
+  auto cb = base::BindLambdaForTesting(
+      [&](FlocId floc_id, base::Optional<base::Version> version) {
+        // Since the file has been deleted, expect an invalid floc id.
+        EXPECT_EQ(FlocId(), floc_id);
+        run_loop.Quit();
+      });
 
   service()->ApplySortingLsh(FlocId(0), std::move(cb));
   base::DeleteFile(file_path);
diff --git a/components/feedback/BUILD.gn b/components/feedback/BUILD.gn
index 3fa9e8bf..1def69187 100644
--- a/components/feedback/BUILD.gn
+++ b/components/feedback/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("//testing/libfuzzer/fuzzer_test.gni")
+
 static_library("feedback") {
   sources = [
     "feedback_common.cc",
@@ -76,3 +78,9 @@
     "//testing/gtest",
   ]
 }
+
+fuzzer_test("redaction_tool_fuzzer") {
+  sources = [ "redaction_tool_fuzzer.cc" ]
+  deps = [ ":feedback" ]
+  dict = "redaction_tool_fuzzer.dict"
+}
diff --git a/components/feedback/redaction_tool_fuzzer.cc b/components/feedback/redaction_tool_fuzzer.cc
new file mode 100644
index 0000000..16e5858
--- /dev/null
+++ b/components/feedback/redaction_tool_fuzzer.cc
@@ -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.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <fuzzer/FuzzedDataProvider.h>
+
+#include "components/feedback/redaction_tool.h"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  FuzzedDataProvider provider(data, size);
+
+  int first_party_extension_id_count = provider.ConsumeIntegralInRange(-1, 50);
+  // This is the storage for the strings inside first_party_extension_ids. This
+  // is to make sure the char *'s we pass to the RedactionTool constructor are
+  // deleted correctly -- they must be deleted after redactor is destructed, but
+  // not leaked.
+  std::vector<std::string> first_party_extension_id_store;
+  // The first_party_extension_ids we pass to the RedactionTool constructor.
+  // This owns the array but not the pointed-to strings. Note that if
+  // first_party_extension_id_count is -1, this is not set so we pass nullptr to
+  // the constructor; that's deliberate.
+  std::unique_ptr<const char*[]> first_party_extension_ids;
+  if (first_party_extension_id_count >= 0) {
+    first_party_extension_id_store.reserve(first_party_extension_id_count);
+    first_party_extension_ids =
+        std::make_unique<const char*[]>(first_party_extension_id_count + 1);
+    for (int i = 0; i < first_party_extension_id_count; ++i) {
+      constexpr int kArbitraryMaxNameLength = 4096;
+      first_party_extension_id_store.emplace_back(
+          provider.ConsumeRandomLengthString(kArbitraryMaxNameLength));
+      first_party_extension_ids[i] = first_party_extension_id_store[i].c_str();
+    }
+    first_party_extension_ids[first_party_extension_id_count] = nullptr;
+  }
+
+  feedback::RedactionTool redactor(first_party_extension_ids.get());
+  redactor.Redact(provider.ConsumeRemainingBytesAsString());
+  return 0;
+}
diff --git a/components/feedback/redaction_tool_fuzzer.dict b/components/feedback/redaction_tool_fuzzer.dict
new file mode 100644
index 0000000..3810445
--- /dev/null
+++ b/components/feedback/redaction_tool_fuzzer.dict
@@ -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.
+
+# Literals from kCustomPatternsWithContext strings
+CellID="Cell ID:"
+LocAC="Location area code:"
+SSID1="ssid"
+SSIDHex="SSID - hexdump"
+SSID1="SSID"
+Serial1="serial number"
+Serial2="Serial Number"
+GAIA="gaia_id"
+GAIAId="id:"
+GAIAEmail=", email"
+UUID="UUID="
+UUIDEnd="xxx"
+VolumeLabel="LABEL="
+VolumeLabel2="/media/removable/"
+
+# Literals from kCustomPatternsWithoutContext, and other things that look like
+# URLS and Emails
+URL1="http://"
+URL2="https://"
+URL3="ftp://"
+URL4="chrome://"
+URL5="chrome-extension://"
+URL6="android://"
+URL7="rtsp://"
+URLHost="foo.com"
+URLPort=":80"
+URLQuery="?"
+URLFragment="#"
+
+# Email Symbols
+EMailAt="@"
+EMailDot="."
+EMailExample="a@b.c"
+
+# MAC Symbols
+MACColon=":"
+MACExample="10:fd:b5:ec:b1:3e"
diff --git a/components/metrics/generate_expired_histograms_array.gni b/components/metrics/generate_expired_histograms_array.gni
index a1f7435..47fef83 100644
--- a/components/metrics/generate_expired_histograms_array.gni
+++ b/components/metrics/generate_expired_histograms_array.gni
@@ -6,6 +6,9 @@
 # produce an output file and a source_set to build it.
 #
 # Parameters:
+#   inputs:
+#     List of file name to read. Each file should be a .xml file with
+#     histogram descriptions.
 #
 #   namespace (optional):
 #     Namespace in which the generated code should be scoped. If left empty,
@@ -27,6 +30,8 @@
     script = "//tools/metrics/histograms/generate_expired_histograms_array.py"
     outputs = [ header_filename ]
 
+    inputs = invoker.inputs
+
     major_branch_date_filepath = invoker.major_branch_date_filepath
     milestone_filepath = invoker.milestone_filepath
 
@@ -37,10 +42,10 @@
     }
 
     args += [
-      "-o" + rebase_path(root_gen_dir, root_build_dir),
-      "-H" + rebase_path(header_filename, root_gen_dir),
-      "-d" + rebase_path(major_branch_date_filepath, root_build_dir),
-      "-m" + rebase_path(milestone_filepath, root_build_dir),
-    ]
+              "-o" + rebase_path(root_gen_dir, root_build_dir),
+              "-H" + rebase_path(header_filename, root_gen_dir),
+              "-d" + rebase_path(major_branch_date_filepath, root_build_dir),
+              "-m" + rebase_path(milestone_filepath, root_build_dir),
+            ] + rebase_path(inputs, root_build_dir)
   }
 }
diff --git a/components/performance_manager/performance_manager.cc b/components/performance_manager/performance_manager.cc
index e71417f..b12e130 100644
--- a/components/performance_manager/performance_manager.cc
+++ b/components/performance_manager/performance_manager.cc
@@ -75,8 +75,14 @@
       PerformanceManagerTabHelper::FromWebContents(wc);
   if (!helper)
     return nullptr;
-
-  return helper->GetFrameNode(rfh)->GetWeakPtr();
+  auto* frame_node = helper->GetFrameNode(rfh);
+  if (!frame_node) {
+    // This should only happen if GetFrameNodeForRenderFrameHost is called
+    // before the RenderFrameCreate notification is dispatched.
+    DCHECK(!rfh->IsRenderFrameCreated());
+    return nullptr;
+  }
+  return frame_node->GetWeakPtr();
 }
 
 // static
diff --git a/components/performance_manager/performance_manager_unittest.cc b/components/performance_manager/performance_manager_unittest.cc
index af8ac21..762a319 100644
--- a/components/performance_manager/performance_manager_unittest.cc
+++ b/components/performance_manager/performance_manager_unittest.cc
@@ -8,13 +8,17 @@
 
 #include "base/run_loop.h"
 #include "base/test/bind_test_util.h"
+#include "components/performance_manager/public/graph/frame_node.h"
 #include "components/performance_manager/public/graph/page_node.h"
+#include "components/performance_manager/public/render_frame_host_proxy.h"
 #include "components/performance_manager/public/web_contents_proxy.h"
 #include "components/performance_manager/test_support/performance_manager_test_harness.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/web_contents.h"
+#include "content/public/test/navigation_simulator.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
 
 namespace performance_manager {
 
@@ -41,42 +45,60 @@
   DISALLOW_COPY_AND_ASSIGN(PerformanceManagerTest);
 };
 
-TEST_F(PerformanceManagerTest, GetPageNodeForWebContents) {
+TEST_F(PerformanceManagerTest, NodeAccessors) {
   auto contents = CreateTestWebContents();
+  content::RenderFrameHost* rfh = contents->GetMainFrame();
+  ASSERT_TRUE(rfh);
 
   base::WeakPtr<PageNode> page_node =
       PerformanceManager::GetPageNodeForWebContents(contents.get());
 
+  // FrameNode's and ProcessNode's don't exist until an observer fires on
+  // navigation. Verify that looking them up before that returns null instead
+  // of crashing.
+  EXPECT_FALSE(PerformanceManager::GetFrameNodeForRenderFrameHost(rfh));
+
+  // Simulate a committed navigation to create the nodes.
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      contents.get(), GURL("https://www.example.com/"));
+  base::WeakPtr<FrameNode> frame_node =
+      PerformanceManager::GetFrameNodeForRenderFrameHost(rfh);
+
   // Post a task to the Graph and make it call a function on the UI thread that
-  // will ensure that |page_node| is really associated with |contents|.
+  // will ensure that the nodes are really associated with the content objects.
 
   base::RunLoop run_loop;
-  auto check_wc_on_main_thread =
-      base::BindLambdaForTesting([&](const WebContentsProxy& wc_proxy) {
+  auto check_proxies_on_main_thread =
+      base::BindLambdaForTesting([&](const WebContentsProxy& wc_proxy,
+                                     const RenderFrameHostProxy& rfh_proxy) {
         EXPECT_EQ(contents.get(), wc_proxy.Get());
+        EXPECT_EQ(rfh, rfh_proxy.Get());
         run_loop.Quit();
       });
 
   auto call_on_graph_cb = base::BindLambdaForTesting([&]() {
     EXPECT_TRUE(page_node.get());
+    EXPECT_TRUE(frame_node.get());
     content::GetUIThreadTaskRunner({})->PostTask(
-        FROM_HERE, base::BindOnce(std::move(check_wc_on_main_thread),
-                                  page_node->GetContentsProxy()));
+        FROM_HERE, base::BindOnce(std::move(check_proxies_on_main_thread),
+                                  page_node->GetContentsProxy(),
+                                  frame_node->GetRenderFrameHostProxy()));
   });
 
   PerformanceManager::CallOnGraph(FROM_HERE, call_on_graph_cb);
 
-  // Wait for |check_wc_on_main_thread| to be called.
+  // Wait for |check_proxies_on_main_thread| to be called.
   run_loop.Run();
 
   contents.reset();
 
-  // After deleting |contents| the corresponding PageNode WeakPtr should be
+  // After deleting |contents| the corresponding WeakPtr's should be
   // invalid.
   base::RunLoop run_loop_after_contents_reset;
   auto quit_closure = run_loop_after_contents_reset.QuitClosure();
   auto call_on_graph_cb_2 = base::BindLambdaForTesting([&]() {
     EXPECT_FALSE(page_node.get());
+    EXPECT_FALSE(frame_node.get());
     std::move(quit_closure).Run();
   });
 
diff --git a/components/permissions/android/permission_prompt_android.cc b/components/permissions/android/permission_prompt_android.cc
index e7e561c..2bacb24 100644
--- a/components/permissions/android/permission_prompt_android.cc
+++ b/components/permissions/android/permission_prompt_android.cc
@@ -10,6 +10,7 @@
 #include "components/infobars/core/infobar_manager.h"
 #include "components/permissions/android/permission_dialog_delegate.h"
 #include "components/permissions/permission_request.h"
+#include "components/permissions/permission_uma_util.h"
 #include "components/permissions/permissions_client.h"
 #include "components/resources/android/theme_resources.h"
 #include "components/strings/grit/components_strings.h"
@@ -64,6 +65,13 @@
   return TabSwitchingBehavior::kKeepPromptAlive;
 }
 
+permissions::PermissionPromptDisposition
+PermissionPromptAndroid::GetPromptDisposition() const {
+  return permission_infobar_
+             ? permissions::PermissionPromptDisposition::MINI_INFOBAR
+             : permissions::PermissionPromptDisposition::MODAL_DIALOG;
+}
+
 void PermissionPromptAndroid::Closing() {
   delegate_->Closing();
 }
diff --git a/components/permissions/android/permission_prompt_android.h b/components/permissions/android/permission_prompt_android.h
index e1ee71e..a2069f2 100644
--- a/components/permissions/android/permission_prompt_android.h
+++ b/components/permissions/android/permission_prompt_android.h
@@ -33,6 +33,8 @@
   // permissions::PermissionPrompt:
   void UpdateAnchorPosition() override;
   TabSwitchingBehavior GetTabSwitchingBehavior() override;
+  permissions::PermissionPromptDisposition GetPromptDisposition()
+      const override;
 
   void Closing();
   void Accept();
diff --git a/components/permissions/notification_permission_ui_selector.cc b/components/permissions/notification_permission_ui_selector.cc
index 46bdd77..5261001 100644
--- a/components/permissions/notification_permission_ui_selector.cc
+++ b/components/permissions/notification_permission_ui_selector.cc
@@ -6,6 +6,19 @@
 
 namespace permissions {
 
+// static
+bool NotificationPermissionUiSelector::ShouldSuppressAnimation(
+    QuietUiReason reason) {
+  switch (reason) {
+    case QuietUiReason::kEnabledInPrefs:
+      return false;
+    case QuietUiReason::kTriggeredByCrowdDeny:
+    case QuietUiReason::kTriggeredDueToAbusiveRequests:
+    case QuietUiReason::kTriggeredDueToAbusiveContent:
+      return true;
+  }
+}
+
 NotificationPermissionUiSelector::Decision::Decision(
     base::Optional<QuietUiReason> quiet_ui_reason,
     base::Optional<WarningReason> warning_reason)
diff --git a/components/permissions/notification_permission_ui_selector.h b/components/permissions/notification_permission_ui_selector.h
index 444dec5..91e62fb 100644
--- a/components/permissions/notification_permission_ui_selector.h
+++ b/components/permissions/notification_permission_ui_selector.h
@@ -62,6 +62,10 @@
 
   virtual ~NotificationPermissionUiSelector() {}
 
+  // Determines whether animations should be suppressed because we're very
+  // confident the user does not want notifications (e.g. they're abusive).
+  static bool ShouldSuppressAnimation(QuietUiReason reason);
+
   // Determines the UI to use for the given |request|, and invokes |callback|
   // when done, either synchronously or asynchronously. The |callback| is
   // guaranteed never to be invoked after |this| goes out of scope. Only one
diff --git a/components/permissions/permission_prompt.h b/components/permissions/permission_prompt.h
index de0be84..f327492d 100644
--- a/components/permissions/permission_prompt.h
+++ b/components/permissions/permission_prompt.h
@@ -17,6 +17,8 @@
 }
 
 namespace permissions {
+enum class PermissionPromptDisposition;
+
 class PermissionRequest;
 
 // This class is the platform-independent interface through which the permission
@@ -74,6 +76,9 @@
   // Get the behavior of this prompt when the user switches away from the
   // associated tab.
   virtual TabSwitchingBehavior GetTabSwitchingBehavior() = 0;
+
+  // Get the type of prompt UI shown for metrics.
+  virtual PermissionPromptDisposition GetPromptDisposition() const = 0;
 };
 
 }  // namespace permissions
diff --git a/components/permissions/permission_request_manager.cc b/components/permissions/permission_request_manager.cc
index c61e7fd..9b12bf4 100644
--- a/components/permissions/permission_request_manager.cc
+++ b/components/permissions/permission_request_manager.cc
@@ -732,18 +732,9 @@
 
 PermissionPromptDisposition
 PermissionRequestManager::DetermineCurrentRequestUIDispositionForUMA() {
-#if defined(OS_ANDROID)
-  return ShouldCurrentRequestUseQuietUI()
-             ? PermissionPromptDisposition::MINI_INFOBAR
-             : PermissionPromptDisposition::MODAL_DIALOG;
-#else
-  return !ShouldCurrentRequestUseQuietUI()
-             ? PermissionPromptDisposition::ANCHORED_BUBBLE
-             : ReasonForUsingQuietUi() == QuietUiReason::kTriggeredByCrowdDeny
-                   ? PermissionPromptDisposition::LOCATION_BAR_RIGHT_STATIC_ICON
-                   : PermissionPromptDisposition::
-                         LOCATION_BAR_RIGHT_ANIMATED_ICON;
-#endif
+  if (view_)
+    return view_->GetPromptDisposition();
+  return PermissionPromptDisposition::NONE_VISIBLE;
 }
 
 void PermissionRequestManager::LogWarningToConsole(const char* message) {
diff --git a/components/permissions/permission_uma_util.cc b/components/permissions/permission_uma_util.cc
index 45cc8445..422f18b 100644
--- a/components/permissions/permission_uma_util.cc
+++ b/components/permissions/permission_uma_util.cc
@@ -161,6 +161,8 @@
   switch (ui_disposition) {
     case PermissionPromptDisposition::ANCHORED_BUBBLE:
       return "AnchoredBubble";
+    case PermissionPromptDisposition::LOCATION_BAR_LEFT_CHIP:
+      return "LocationBarLeftChip";
     case PermissionPromptDisposition::LOCATION_BAR_RIGHT_ANIMATED_ICON:
       return "LocationBarRightAnimatedIcon";
     case PermissionPromptDisposition::LOCATION_BAR_RIGHT_STATIC_ICON:
@@ -169,6 +171,8 @@
       return "MiniInfobar";
     case PermissionPromptDisposition::MODAL_DIALOG:
       return "ModalDialog";
+    case PermissionPromptDisposition::NONE_VISIBLE:
+      return "NoneVisible";
     case PermissionPromptDisposition::NOT_APPLICABLE:
       return "NotApplicable";
   }
diff --git a/components/permissions/permission_uma_util.h b/components/permissions/permission_uma_util.h
index a8a4040e..964033b 100644
--- a/components/permissions/permission_uma_util.h
+++ b/components/permissions/permission_uma_util.h
@@ -69,7 +69,7 @@
 // Enum used in UKMs and UMAs, do not re-order or change values. Deprecated
 // items should only be commented out. New items should be added at the end,
 // and the "PermissionPromptDisposition" histogram suffix needs to be updated to
-// match (tools/metrics/histograms/histograms.xml).
+// match (tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml).
 enum class PermissionPromptDisposition {
   // Not all permission actions will have an associated permission prompt (e.g.
   // changing permission via the settings page).
@@ -92,6 +92,14 @@
   // Only used on Android, an initially-collapsed infobar at the bottom of the
   // page.
   MINI_INFOBAR = 5,
+
+  // Only used on desktop, a chip on the left-hand side of the location bar that
+  // shows a bubble when clicked.
+  LOCATION_BAR_LEFT_CHIP = 6,
+
+  // There was no UI being shown. This is usually because the user closed an
+  // inactive tab that had a pending permission request.
+  NONE_VISIBLE = 7,
 };
 
 enum class AdaptiveTriggers {
diff --git a/components/permissions/test/mock_permission_prompt.cc b/components/permissions/test/mock_permission_prompt.cc
index b87746ea..2af4bfbc 100644
--- a/components/permissions/test/mock_permission_prompt.cc
+++ b/components/permissions/test/mock_permission_prompt.cc
@@ -7,6 +7,7 @@
 #include "base/bind.h"
 #include "base/run_loop.h"
 #include "build/build_config.h"
+#include "components/permissions/permission_uma_util.h"
 #include "components/permissions/test/mock_permission_prompt_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -32,6 +33,14 @@
 #endif
 }
 
+PermissionPromptDisposition MockPermissionPrompt::GetPromptDisposition() const {
+#if defined(OS_ANDROID)
+  return PermissionPromptDisposition::MODAL_DIALOG;
+#else
+  return PermissionPromptDisposition::ANCHORED_BUBBLE;
+#endif
+}
+
 MockPermissionPrompt::MockPermissionPrompt(MockPermissionPromptFactory* factory,
                                            Delegate* delegate)
     : factory_(factory), delegate_(delegate) {
diff --git a/components/permissions/test/mock_permission_prompt.h b/components/permissions/test/mock_permission_prompt.h
index 2ae804b..220dd9a4 100644
--- a/components/permissions/test/mock_permission_prompt.h
+++ b/components/permissions/test/mock_permission_prompt.h
@@ -21,6 +21,7 @@
   // PermissionPrompt:
   void UpdateAnchorPosition() override;
   TabSwitchingBehavior GetTabSwitchingBehavior() override;
+  PermissionPromptDisposition GetPromptDisposition() const override;
 
   bool IsVisible();
 
diff --git a/components/resources/autofill_regex_resources.grdp b/components/resources/autofill_regex_resources.grdp
new file mode 100644
index 0000000..eb901de
--- /dev/null
+++ b/components/resources/autofill_regex_resources.grdp
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<grit-part>
+  <include name="IDR_AUTOFILL_REGEX_JSON" file="../autofill/core/browser/pattern_provider/resources/regex_patterns.json" type="BINDATA" compress="gzip" />
+</grit-part>
diff --git a/components/resources/components_resources.grd b/components/resources/components_resources.grd
index 1d62db6..be607c11 100644
--- a/components/resources/components_resources.grd
+++ b/components/resources/components_resources.grd
@@ -14,6 +14,7 @@
   <release seq="1">
     <includes>
       <part file="about_ui_resources.grdp" />
+      <part file="autofill_regex_resources.grdp" />
       <part file="dom_distiller_resources.grdp" />
       <part file="flags_ui_resources.grdp" />
       <part file="management_resources.grdp" />
diff --git a/components/system_media_controls/linux/system_media_controls_linux.cc b/components/system_media_controls/linux/system_media_controls_linux.cc
index c07a782..14d5a0b9 100644
--- a/components/system_media_controls/linux/system_media_controls_linux.cc
+++ b/components/system_media_controls/linux/system_media_controls_linux.cc
@@ -153,7 +153,7 @@
   // org.mpris.MediaPlayer2 interface properties.
   auto set_property = [&](const std::string& property_name, auto&& value) {
     properties_->SetProperty(kMprisAPIInterfaceName, property_name,
-                             std::move(value), false);
+                             std::forward<decltype(value)>(value), false);
   };
   set_property("CanQuit", DbusBoolean(false));
   set_property("CanRaise", DbusBoolean(false));
@@ -171,7 +171,7 @@
   auto set_player_property = [&](const std::string& property_name,
                                  auto&& value) {
     properties_->SetProperty(kMprisAPIPlayerInterfaceName, property_name,
-                             std::move(value), false);
+                             std::forward<decltype(value)>(value), false);
   };
   set_player_property("PlaybackStatus", DbusString("Stopped"));
   set_player_property("Rate", DbusDouble(1.0));
diff --git a/components/viz/common/features.cc b/components/viz/common/features.cc
index 956b3b48c..53957212 100644
--- a/components/viz/common/features.cc
+++ b/components/viz/common/features.cc
@@ -42,6 +42,10 @@
                                        base::FEATURE_ENABLED_BY_DEFAULT};
 #endif
 
+// Uses glClear to composite solid color quads whenever possible.
+const base::Feature kFastSolidColorDraw{"FastSolidColorDraw",
+                                        base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Viz for WebView architecture.
 const base::Feature kVizForWebView{"VizForWebView",
                                    base::FEATURE_DISABLED_BY_DEFAULT};
@@ -127,6 +131,10 @@
 }
 #endif
 
+bool IsUsingFastPathForSolidColorQuad() {
+  return base::FeatureList::IsEnabled(kFastSolidColorDraw);
+}
+
 bool IsUsingVizForWebView() {
   // Viz for WebView requires shared images to be enabled.
   if (!base::FeatureList::IsEnabled(kEnableSharedImageForWebview))
diff --git a/components/viz/common/features.h b/components/viz/common/features.h
index 72491fe..b7c7376 100644
--- a/components/viz/common/features.h
+++ b/components/viz/common/features.h
@@ -20,6 +20,7 @@
 #if defined(OS_ANDROID)
 VIZ_COMMON_EXPORT extern const base::Feature kDynamicColorGamut;
 #endif
+VIZ_COMMON_EXPORT extern const base::Feature kFastSolidColorDraw;
 VIZ_COMMON_EXPORT extern const base::Feature kVizForWebView;
 VIZ_COMMON_EXPORT extern const base::Feature kVizFrameSubmissionForWebView;
 VIZ_COMMON_EXPORT extern const base::Feature kUsePreferredIntervalForVideo;
@@ -38,6 +39,7 @@
 #if defined(OS_ANDROID)
 VIZ_COMMON_EXPORT bool IsDynamicColorGamutEnabled();
 #endif
+VIZ_COMMON_EXPORT bool IsUsingFastPathForSolidColorQuad();
 VIZ_COMMON_EXPORT bool IsUsingVizForWebView();
 VIZ_COMMON_EXPORT bool IsUsingVizFrameSubmissionForWebView();
 VIZ_COMMON_EXPORT bool IsUsingPreferredIntervalForVideo();
diff --git a/components/viz/service/display/dc_layer_overlay.cc b/components/viz/service/display/dc_layer_overlay.cc
index 740c080..b294b34b 100644
--- a/components/viz/service/display/dc_layer_overlay.cc
+++ b/components/viz/service/display/dc_layer_overlay.cc
@@ -6,6 +6,7 @@
 
 #include <limits>
 
+#include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
@@ -19,6 +20,7 @@
 #include "components/viz/service/display/display_resource_provider.h"
 #include "components/viz/service/display/overlay_processor_interface.h"
 #include "gpu/GLES2/gl2extchromium.h"
+#include "gpu/config/gpu_finch_features.h"
 #include "ui/gfx/geometry/insets.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/rect_conversions.h"
@@ -71,7 +73,8 @@
     const YUVVideoDrawQuad* quad,
     const std::vector<gfx::Rect>& backdrop_filter_rects,
     bool has_overlay_support,
-    int current_frame_processed_overlay_count,
+    int allowed_yuv_overlay_count,
+    int processed_yuv_overlay_count,
     DisplayResourceProvider* resource_provider) {
   // Note: Do not override this value based on base::Feature values. It is the
   // result after the GPU blocklist has been consulted.
@@ -97,7 +100,7 @@
     return DC_LAYER_FAILED_COMPLEX_TRANSFORM;
   }
 
-  if (current_frame_processed_overlay_count > 0)
+  if (processed_yuv_overlay_count >= allowed_yuv_overlay_count)
     return DC_LAYER_FAILED_TOO_MANY_OVERLAYS;
 
   // Rounded corner on overlays are not supported.
@@ -320,8 +323,13 @@
 
 DCLayerOverlayProcessor::DCLayerOverlayProcessor(
     const DebugRendererSettings* debug_settings,
+    int allowed_yuv_overlay_count,
     bool skip_initialization_for_testing)
     : has_overlay_support_(skip_initialization_for_testing),
+      use_overlay_damage_list_(base::FeatureList::IsEnabled(
+          features::kDirectCompositionUseOverlayDamageList)),
+      allowed_yuv_overlay_count_(
+          use_overlay_damage_list_ ? allowed_yuv_overlay_count : 1),
       debug_settings_(debug_settings),
       viz_task_runner_(skip_initialization_for_testing
                            ? nullptr
@@ -406,6 +414,7 @@
     gfx::Rect* damage_rect,
     DCLayerOverlayList* dc_layer_overlays) {
   gfx::Rect this_frame_underlay_rect;
+  processed_yuv_overlay_count_ = 0;
 
   // Which render passes have backdrop filters.
   base::flat_set<AggregatedRenderPassId> render_pass_has_backdrop_filters;
@@ -451,11 +460,13 @@
     DCLayerResult result;
     switch (it->material) {
       case DrawQuad::Material::kYuvVideoContent:
-        result =
-            ValidateYUVQuad(YUVVideoDrawQuad::MaterialCast(*it),
-                            backdrop_filter_rects, has_overlay_support_,
-                            candidate_index_list.size(), resource_provider);
+        result = ValidateYUVQuad(
+            YUVVideoDrawQuad::MaterialCast(*it), backdrop_filter_rects,
+            has_overlay_support_, allowed_yuv_overlay_count_,
+            processed_yuv_overlay_count_, resource_provider);
         yuv_quads_in_quad_list++;
+        if (result == DC_LAYER_SUCCESS)
+          processed_yuv_overlay_count_++;
         break;
       case DrawQuad::Material::kTextureContent:
         result = ValidateTextureQuad(TextureDrawQuad::MaterialCast(*it),
@@ -483,11 +494,17 @@
 
     candidate_index_list.push_back(index);
   }
+  // A YUV quad might be rejected later due to not allowed as an underlay.
+  // Recount the YUV overlays when they are added to the overlay list
+  // successfully.
+  processed_yuv_overlay_count_ = 0;
 
+  // TODO(magchen@): Revisit this code if allowed_yuv_overlay_count_ > 1.
   // We might not save power if there are more than one videos and only one is
   // promoted to overlay. Skip overlays for this frame unless there are
   // protected video or texture overlays.
-  if (candidate_index_list.size() > 0 && yuv_quads_in_quad_list > 1 &&
+  if (candidate_index_list.size() > 0 &&
+      yuv_quads_in_quad_list > allowed_yuv_overlay_count_ &&
       !has_protected_video_or_texture_overlays) {
     candidate_index_list.clear();
     // In this case, there is only one candidate in the list.
@@ -516,6 +533,12 @@
     // skipped if they're not underlay compatible.
     const bool requires_overlay = IsProtectedVideo(it);
 
+    // TODO(magchen@): Since we reject underlays here, the max number of YUV
+    // overlays we can promote might not be accurate. We should allow all YUV
+    // quads to be put into candidate_index_list, but only
+    // |allowed_yuv_overlay_count_| YUV quads should be promoted to
+    // overlays/underlays from that list.
+
     // Skip quad if it's an underlay and underlays are not allowed.
     if (!is_overlay && !requires_overlay) {
       DCLayerResult result = IsUnderlayAllowed(it);
@@ -539,16 +562,23 @@
   // found in this frame, the previous overlay rects would have been handled
   // above and |previous_frame_overlay_rect_union_| becomes empty.
   damage_rect->Union(previous_frame_overlay_rect_union_);
+  damage_rect->Intersect(gfx::ToEnclosingRect(display_rect));
+
   previous_frame_overlay_rect_union_ = current_frame_overlay_rect_union_;
   current_frame_overlay_rect_union_ = gfx::Rect();
   previous_frame_processed_overlay_count_ =
       current_frame_processed_overlay_count_;
   current_frame_processed_overlay_count_ = 0;
-
-  damage_rect->Intersect(gfx::ToEnclosingRect(display_rect));
   previous_display_rect_ = display_rect;
   previous_frame_underlay_rect_ = this_frame_underlay_rect;
 
+  if (!dc_layer_overlays->empty()) {
+    base::UmaHistogramExactLinear(
+        "GPU.DirectComposition.DCLayer.YUVOverlayCount",
+        /*sample=*/processed_yuv_overlay_count_,
+        /*value_max=*/10);
+  }
+
   if (debug_settings_->show_dc_layer_debug_borders &&
       dc_layer_overlays->size() > 0) {
     InsertDebugBorderDrawQuad(dc_layer_overlays, root_render_pass, display_rect,
@@ -576,6 +606,7 @@
     case DrawQuad::Material::kYuvVideoContent:
       FromYUVQuad(YUVVideoDrawQuad::MaterialCast(*it),
                   render_pass->transform_to_root_target, &dc_layer);
+      processed_yuv_overlay_count_++;
       break;
     case DrawQuad::Material::kTextureContent:
       FromTextureQuad(TextureDrawQuad::MaterialCast(*it),
@@ -624,7 +655,6 @@
 
   dc_layer_overlays->push_back(dc_layer);
 
-  // Only allow one overlay unless it's hardware protected video.
   current_frame_processed_overlay_count_++;
 }
 
diff --git a/components/viz/service/display/dc_layer_overlay.h b/components/viz/service/display/dc_layer_overlay.h
index 0db594e..bc35e03 100644
--- a/components/viz/service/display/dc_layer_overlay.h
+++ b/components/viz/service/display/dc_layer_overlay.h
@@ -79,8 +79,14 @@
  public:
   // When |skip_initialization_for_testing| is true, object will be isolated
   // for unit tests.
+  // allowed_yuv_overlay_count will be limited to 1 if
+  // |use_overlay_damage_list_| is not supported. This new method produces an
+  // empty root damage rect when the overlay quads are the only damages in the
+  // frames. If |use_overlay_damage_list_| is false, we should not allowed more
+  // than one YUV overlays since non-empty damage rect won't save any power.
   explicit DCLayerOverlayProcessor(
       const DebugRendererSettings* debug_settings,
+      int allowed_yuv_overlay_count,
       bool skip_initialization_for_testing = false);
   virtual ~DCLayerOverlayProcessor();
 
@@ -137,6 +143,11 @@
                                  gfx::Rect* damage_rect);
 
   bool has_overlay_support_;
+  const bool use_overlay_damage_list_;
+  // TODO(magchen@): We are going to support more than one YUV overlay.
+  const int allowed_yuv_overlay_count_;
+  int processed_yuv_overlay_count_ = 0;
+
   // Reference to the global viz singleton.
   const DebugRendererSettings* const debug_settings_;
 
diff --git a/components/viz/service/display/gl_renderer.cc b/components/viz/service/display/gl_renderer.cc
index e5179c0..31fa3d2 100644
--- a/components/viz/service/display/gl_renderer.cc
+++ b/components/viz/service/display/gl_renderer.cc
@@ -32,6 +32,7 @@
 #include "cc/paint/render_surface_filters.h"
 #include "cc/raster/scoped_gpu_raster.h"
 #include "components/viz/common/display/renderer_settings.h"
+#include "components/viz/common/features.h"
 #include "components/viz/common/frame_sinks/copy_output_request.h"
 #include "components/viz/common/gpu/context_provider.h"
 #include "components/viz/common/quads/compositor_frame.h"
@@ -77,6 +78,7 @@
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gfx/geometry/size_conversions.h"
+#include "ui/gfx/geometry/vector2d.h"
 #include "ui/gfx/rrect_f.h"
 #include "ui/gfx/skia_util.h"
 
@@ -218,6 +220,37 @@
   gpu::gles2::GLES2Interface* gl_;
 };
 
+void AccumulateDrawRects(const gfx::Rect& quad_rect,
+                         const gfx::Transform& target_transform,
+                         std::vector<gfx::Rect>* drawn_rects) {
+  gfx::RectF quad_rect_f(quad_rect);
+
+  // If the transform is not axis aligned then assume the largest possible
+  // bounds the quad can take in the render target. In this case, we take the
+  // sum of 2 sides.
+  if (!target_transform.Preserves2dAxisAlignment()) {
+    // Increase the length of each side to |width + height|.
+    const int total_length = quad_rect.width() + quad_rect.height();
+    quad_rect_f.set_height(total_length);
+    quad_rect_f.set_width(total_length);
+
+    // Ensure that the increase is equally distributed on either sides of the
+    // quad such that the position of the center of the quad does not change.
+    const float delta_x = -(quad_rect.height() / 2.f);
+    const float delta_y = -(quad_rect.width() / 2.f);
+    quad_rect_f.Offset(gfx::Vector2d(delta_x, delta_y));
+
+    // Apply only the scale and translation component.
+    const gfx::Vector2dF& translate = target_transform.To2dTranslation();
+    const gfx::Vector2dF& scale = target_transform.Scale2d();
+    quad_rect_f.Scale(scale.x(), scale.y());
+    quad_rect_f.Offset(translate.x(), translate.y());
+  } else {
+    target_transform.TransformRect(&quad_rect_f);
+  }
+  drawn_rects->push_back(gfx::ToRoundedRect(quad_rect_f));
+}
+
 // Smallest unit that impact anti-aliasing output. We use this to
 // determine when anti-aliasing is unnecessary.
 const float kAntiAliasingEpsilon = 1.0f / 1024.0f;
@@ -420,6 +453,8 @@
   prefer_draw_to_copy_ = output_surface_->context_provider()
                              ->GetGpuFeatureInfo()
                              .IsWorkaroundEnabled(gpu::PREFER_DRAW_TO_COPY);
+  use_fast_path_solid_color_quad_ =
+      features::IsUsingFastPathForSolidColorQuad();
   InitializeSharedObjects();
 }
 
@@ -509,6 +544,9 @@
     gl_->GenQueriesEXT(1, &occlusion_query_);
     gl_->BeginQueryEXT(GL_SAMPLES_PASSED_ARB, occlusion_query_);
   }
+
+  // For each render pass, reset the drawn region.
+  drawn_rects_.clear();
 }
 
 void GLRenderer::ClearFramebuffer() {
@@ -1286,6 +1324,10 @@
   ChooseRPDQProgram(params, CurrentRenderPassColorSpace());
   UpdateRPDQUniforms(params);
   DrawRPDQ(*params);
+
+  AccumulateDrawRects(params->quad->visible_rect,
+                      params->quad->shared_quad_state->quad_to_target_transform,
+                      &drawn_rects_);
 }
 
 bool GLRenderer::InitializeRPDQParameters(
@@ -2120,41 +2162,84 @@
     color = color_f.toSkColor();
   }
 
-  SetShaderColor(color, opacity);
-  if (current_program_->rounded_corner_rect_location() != -1) {
-    SetShaderRoundedCorner(
-        quad->shared_quad_state->rounded_corner_bounds,
-        current_frame()->window_matrix * current_frame()->projection_matrix);
-  }
+  // Try using glClear to draw the solid color quad if possible. This is much
+  // more performant than executing the shader pipeline.
+  if (CanUseFastSolidColorDraw(quad) && !use_aa) {
+    // Pre-multiply the alpha and opacity to get the correct blending in case of
+    // transparent buffers. glClear does not have any alpha blending stage.
+    Float4 result = PremultipliedColor(color, opacity);
+    SkRGBA4f<kPremul_SkAlphaType> color_f_premul;
+    std::copy(result.data, result.data + 4, color_f_premul.vec());
 
-  if (current_program_->tint_color_matrix_location() != -1) {
-    auto matrix = cc::DebugColors::TintCompositedContentColorTransformMatrix();
-    gl_->UniformMatrix4fv(current_program_->tint_color_matrix_location(), 1,
-                          false, matrix.data());
-  }
+    gfx::RectF quad_rect_in_target_f(quad->visible_rect);
 
-  if (use_aa) {
-    gl_->Uniform3fv(current_program_->edge_location(), 8, edge);
-  }
+    device_transform.TransformRect(&quad_rect_in_target_f);
+    gfx::Rect quad_rect_in_target = gfx::ToRoundedRect(quad_rect_in_target_f);
 
-  // Enable blending when the quad properties require it or if we decided
-  // to use antialiasing.
-  SetBlendEnabled(quad->ShouldDrawWithBlending() || use_aa);
-  ApplyBlendModeUsingBlendFunc(quad->shared_quad_state->blend_mode);
+    // If we are using partial swap, make sure the new scissor rect is within
+    // the partial swap bounds.
+    if (!scissor_rect_.IsEmpty() && is_scissor_enabled_)
+      quad_rect_in_target.Intersect(scissor_rect_);
 
-  // Antialising requires a normalized quad, but this could lead to floating
-  // point precision errors, so only normalize when antialising is on.
-  if (use_aa) {
-    DrawQuadGeometryWithAA(quad, &local_quad, tile_rect);
+    gl_->Enable(GL_SCISSOR_TEST);
+    gl_->Scissor(quad_rect_in_target.x(), quad_rect_in_target.y(),
+                 quad_rect_in_target.width(), quad_rect_in_target.height());
+
+    gl_->ClearColor(color_f_premul.fR, color_f_premul.fG, color_f_premul.fB,
+                    color_f_premul.fA);
+    gl_->Clear(GL_COLOR_BUFFER_BIT);
+
+    // Restore GL scissor state.
+    if (is_scissor_enabled_)
+      gl_->Enable(GL_SCISSOR_TEST);
+    else
+      gl_->Disable(GL_SCISSOR_TEST);
+
+    gl_->Scissor(scissor_rect_.x(), scissor_rect_.y(), scissor_rect_.width(),
+                 scissor_rect_.height());
   } else {
-    PrepareGeometry(SHARED_BINDING);
-    SetShaderQuadF(local_quad);
-    SetShaderMatrix(current_frame()->projection_matrix *
-                    quad->shared_quad_state->quad_to_target_transform);
-    gl_->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr);
-    num_triangles_drawn_ += 2;
+    SetShaderColor(color, opacity);
+    if (current_program_->rounded_corner_rect_location() != -1) {
+      SetShaderRoundedCorner(
+          quad->shared_quad_state->rounded_corner_bounds,
+          current_frame()->window_matrix * current_frame()->projection_matrix);
+    }
+
+    if (current_program_->tint_color_matrix_location() != -1) {
+      auto matrix =
+          cc::DebugColors::TintCompositedContentColorTransformMatrix();
+      gl_->UniformMatrix4fv(current_program_->tint_color_matrix_location(), 1,
+                            false, matrix.data());
+    }
+
+    if (use_aa) {
+      gl_->Uniform3fv(current_program_->edge_location(), 8, edge);
+    }
+
+    // Enable blending when the quad properties require it or if we decided
+    // to use antialiasing.
+    SetBlendEnabled(quad->ShouldDrawWithBlending() || use_aa);
+    ApplyBlendModeUsingBlendFunc(quad->shared_quad_state->blend_mode);
+
+    // Antialiasing requires a normalized quad, but this could lead to floating
+    // point precision errors, so only normalize when antialiasing is on.
+    if (use_aa) {
+      DrawQuadGeometryWithAA(quad, &local_quad, tile_rect);
+    } else {
+      PrepareGeometry(SHARED_BINDING);
+      SetShaderQuadF(local_quad);
+      SetShaderMatrix(current_frame()->projection_matrix *
+                      quad->shared_quad_state->quad_to_target_transform);
+      gl_->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr);
+      num_triangles_drawn_ += 2;
+    }
+    RestoreBlendFuncToDefault(quad->shared_quad_state->blend_mode);
   }
-  RestoreBlendFuncToDefault(quad->shared_quad_state->blend_mode);
+
+  // Add the quad to the region that has been drawn.
+  AccumulateDrawRects(quad->visible_rect,
+                      quad->shared_quad_state->quad_to_target_transform,
+                      &drawn_rects_);
 }
 
 void GLRenderer::DrawTileQuad(const TileDrawQuad* quad,
@@ -2193,6 +2278,10 @@
                       clip_region);
   else
     DrawContentQuadNoAA(quad, resource_id, clip_region);
+
+  AccumulateDrawRects(quad->visible_rect,
+                      quad->shared_quad_state->quad_to_target_transform,
+                      &drawn_rects_);
 }
 
 void GLRenderer::DrawContentQuadAA(const ContentDrawQuadBase* quad,
@@ -2633,6 +2722,11 @@
         quad->shared_quad_state->quad_to_target_transform, tile_rect,
         region_quad, uvs);
   }
+
+  // Track the region in the current target surface that has been drawn to.
+  AccumulateDrawRects(quad->visible_rect,
+                      quad->shared_quad_state->quad_to_target_transform,
+                      &drawn_rects_);
 }
 
 void GLRenderer::DrawStreamVideoQuad(const StreamVideoDrawQuad* quad,
@@ -2708,6 +2802,10 @@
         quad->shared_quad_state->quad_to_target_transform, tile_rect,
         region_quad, uvs);
   }
+
+  AccumulateDrawRects(quad->visible_rect,
+                      quad->shared_quad_state->quad_to_target_transform,
+                      &drawn_rects_);
 }
 
 void GLRenderer::FlushTextureQuadCache(BoundGeometry flush_binding) {
@@ -2905,6 +3003,11 @@
   quad_rect_matrix.matrix().asColMajorf(m.data);
   draw_cache_.matrix_data.push_back(m);
 
+  // Track the region in the current target surface that has been drawn to.
+  AccumulateDrawRects(quad->visible_rect,
+                      quad->shared_quad_state->quad_to_target_transform,
+                      &drawn_rects_);
+
   if (clip_region) {
     DCHECK_EQ(quad->rect, quad->visible_rect);
     gfx::QuadF scaled_region;
@@ -4244,6 +4347,67 @@
   return is_root_render_pass && !output_color_matrix.isIdentity();
 }
 
+bool GLRenderer::CanUseFastSolidColorDraw(
+    const SolidColorDrawQuad* quad) const {
+  const SharedQuadState* sqs = quad->shared_quad_state;
+
+  if (!use_fast_path_solid_color_quad_)
+    return false;
+
+  // Rounded corners require blending with the background, which is not possible
+  // with the glClear draw method.
+  if (!sqs->rounded_corner_bounds.IsEmpty())
+    return false;
+
+  // 3D transforms need vertex computation in 3D and cannot be handled using
+  // glClear().
+  if (!sqs->quad_to_target_transform.IsFlat())
+    return false;
+
+  // glClear ignores stencil buffer.
+  if (stencil_shadow_)
+    return false;
+
+  // Any non axis aligned transform cannot be handled by glClear.
+  if (!sqs->quad_to_target_transform.Preserves2dAxisAlignment())
+    return false;
+
+  // If no blending is needed for the quad, then fast draw can be safely used.
+  if (!quad->ShouldDrawWithBlending() && SkColorGetA(quad->color) == 255)
+    return true;
+
+  // It is safe to use glClearColor with alpha blending when the render
+  // pass has transparent background because the blending happens against
+  // (0, 0, 0, 0) which is the same as replacing the destination color & alpha.
+  // However, if the render pass does not have a transparent background, using
+  // glClear with a color that has alpha or opacity, would end up punching an
+  // unwanted hole in the frame buffer.
+  if (!current_frame()->current_render_pass->has_transparent_background)
+    return false;
+
+  // If the color has any alpha and blending is needed, ensure the blend mode
+  // allows replacing destination color & alpha.
+  const bool is_translucent =
+      SkColorGetA(quad->color) != 255 || quad->shared_quad_state->opacity < 1.f;
+  if (is_translucent &&
+      !(quad->shared_quad_state->blend_mode == SkBlendMode::kSrc ||
+        quad->shared_quad_state->blend_mode == SkBlendMode::kSrcOver)) {
+    return false;
+  }
+
+  gfx::RectF quad_rect_in_target(quad->visible_rect);
+  sqs->quad_to_target_transform.TransformRect(&quad_rect_in_target);
+  const gfx::Rect quad_rect_in_target_rounded =
+      gfx::ToRoundedRect(quad_rect_in_target);
+
+  // If the quad does not intersect with any region that has already been drawn
+  // to, then blending is not an issue and fast draw path can be used.
+  for (const auto& rect : drawn_rects_)
+    if (quad_rect_in_target_rounded.Intersects(rect))
+      return false;
+  return true;
+}
+
 void GLRenderer::AllocateRenderPassResourceIfNeeded(
     const AggregatedRenderPassId& render_pass_id,
     const RenderPassRequirements& requirements) {
diff --git a/components/viz/service/display/gl_renderer.h b/components/viz/service/display/gl_renderer.h
index 7e3373fe..ed6efb8 100644
--- a/components/viz/service/display/gl_renderer.h
+++ b/components/viz/service/display/gl_renderer.h
@@ -372,6 +372,10 @@
 
   bool HasOutputColorMatrix() const;
 
+  // Returns true if the given solid color draw quad can be safely drawn using
+  // the glClear function call.
+  bool CanUseFastSolidColorDraw(const SolidColorDrawQuad* quad) const;
+
   // A map from RenderPass id to the texture used to draw the RenderPass from.
   base::flat_map<AggregatedRenderPassId, ScopedRenderPassTexture>
       render_pass_textures_;
@@ -454,6 +458,7 @@
   bool use_timer_query_ = false;
   bool use_occlusion_query_ = false;
   bool use_swap_with_bounds_ = false;
+  bool use_fast_path_solid_color_quad_ = false;
 
   // If true, tints all the composited content to red.
   bool tint_gl_composited_content_ = true;
@@ -477,6 +482,9 @@
   // quad type as string.
   base::queue<std::pair<unsigned, std::string>> timer_queries_;
 
+  // Keeps track of areas that have been drawn to in the current render pass.
+  std::vector<gfx::Rect> drawn_rects_;
+
   // This may be null if the compositor is run on a thread without a
   // MessageLoop.
   scoped_refptr<base::SingleThreadTaskRunner> current_task_runner_;
diff --git a/components/viz/service/display/gl_renderer_unittest.cc b/components/viz/service/display/gl_renderer_unittest.cc
index 3dfb5b542..d01c4ec2 100644
--- a/components/viz/service/display/gl_renderer_unittest.cc
+++ b/components/viz/service/display/gl_renderer_unittest.cc
@@ -28,6 +28,7 @@
 #include "cc/test/resource_provider_test_utils.h"
 #include "components/viz/client/client_resource_provider.h"
 #include "components/viz/common/display/renderer_settings.h"
+#include "components/viz/common/features.h"
 #include "components/viz/common/frame_sinks/copy_output_request.h"
 #include "components/viz/common/frame_sinks/copy_output_result.h"
 #include "components/viz/common/quads/texture_draw_quad.h"
@@ -1376,8 +1377,13 @@
     EXPECT_FILTER_CALL(GL_LINEAR);
     EXPECT_CALL(*gl, DrawElements(_, _, _, _));
 
-    // stream video, solid color and debug draw quads
-    EXPECT_CALL(*gl, DrawElements(_, _, _, _)).Times(3);
+    if (features::IsUsingFastPathForSolidColorQuad()) {
+      // stream video and debug draw quads
+      EXPECT_CALL(*gl, DrawElements(_, _, _, _)).Times(2);
+    } else {
+      // stream video, solid color, and debug draw quads
+      EXPECT_CALL(*gl, DrawElements(_, _, _, _)).Times(3);
+    }
   }
 
   gfx::Size viewport_size(100, 100);
@@ -1534,8 +1540,15 @@
   Expectation first_render_pass =
       EXPECT_CALL(*mock_gl, DrawElements(_, _, _, _)).Times(1);
 
-  // The second render pass is the root one, clearing should be prevented.
-  EXPECT_CALL(*mock_gl, Clear(clear_bits)).Times(0).After(first_render_pass);
+  if (features::IsUsingFastPathForSolidColorQuad()) {
+    // The second render pass is the root one, clearing should be prevented. The
+    // one call is expected due to the solid color draw quad which uses glClear
+    // to draw the quad.
+    EXPECT_CALL(*mock_gl, Clear(clear_bits)).Times(1).After(first_render_pass);
+  } else {
+    // The second render pass is the root one, clearing should be prevented.
+    EXPECT_CALL(*mock_gl, Clear(clear_bits)).Times(0).After(first_render_pass);
+  }
 
   EXPECT_CALL(*mock_gl, DrawElements(_, _, _, _))
       .Times(AnyNumber())
@@ -1553,7 +1566,20 @@
  public:
   ScissorTestOnClearCheckingGLES2Interface() = default;
 
-  void Clear(GLbitfield) override { EXPECT_FALSE(scissor_enabled_); }
+  void ClearColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a) override {
+    // RGBA - {0, 0, 0, 0} is used to clear the buffer before drawing onto the
+    // render target. Any other color means a solid color draw quad is being
+    // drawn.
+    if (features::IsUsingFastPathForSolidColorQuad())
+      is_drawing_solid_color_quad_ = !(r == 0 && g == 0 && b == 0 && a == 0);
+  }
+
+  void Clear(GLbitfield bits) override {
+    // GL clear is also used to draw solid color draw quads.
+    if ((bits & GL_COLOR_BUFFER_BIT) && is_drawing_solid_color_quad_)
+      return;
+    EXPECT_FALSE(scissor_enabled_);
+  }
 
   void Enable(GLenum cap) override {
     if (cap == GL_SCISSOR_TEST)
@@ -1567,6 +1593,7 @@
 
  private:
   bool scissor_enabled_ = false;
+  bool is_drawing_solid_color_quad_ = false;
 };
 
 TEST_F(GLRendererTest, ScissorTestWhenClearing) {
@@ -1930,7 +1957,10 @@
   }
 
   void DrawBlackFrame(const gfx::Size& viewport_size) {
-    EXPECT_CALL(*gl_, DrawElements(_, _, _, _)).Times(1);
+    // The feature enables a faster path to draw solid color quads that does not
+    // use GL draw calls but instead uses glClear.
+    if (!features::IsUsingFastPathForSolidColorQuad())
+      EXPECT_CALL(*gl_, DrawElements(_, _, _, _)).Times(1);
 
     AggregatedRenderPassId root_pass_id{1};
     AggregatedRenderPass* root_pass = cc::AddRenderPass(
@@ -1962,6 +1992,7 @@
   DrawBlackFrame(viewport_size);
 
   EXPECT_CALL(*gl_, DrawElements(_, _, _, _)).Times(1);
+
   AggregatedRenderPassId root_pass_id{1};
   AggregatedRenderPass* root_pass = cc::AddRenderPass(
       &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size),
@@ -1969,6 +2000,11 @@
   root_pass->damage_rect = gfx::Rect(0, 0, 25, 25);
   cc::AddQuad(root_pass, quad_rect, SK_ColorGREEN);
 
+  // Add rounded corners to the solid color draw quad so that the fast path
+  // of drawing using glClear is not used.
+  root_pass->shared_quad_state_list.front()->rounded_corner_bounds =
+      gfx::RRectF(gfx::RectF(quad_rect), 2.f);
+
   renderer_->DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_);
   DrawFrame(renderer_.get(), viewport_size);
 }
@@ -1992,6 +2028,11 @@
       gfx::Rect(0, 0, 40, 40);
   root_pass->quad_list.front()->visible_rect = gfx::Rect(20, 20, 20, 20);
 
+  // Add rounded corners to the solid color draw quad so that the fast path
+  // of drawing using glClear is not used.
+  root_pass->shared_quad_state_list.front()->rounded_corner_bounds =
+      gfx::RRectF(gfx::RectF(quad_rect), 1.f);
+
   renderer_->DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_);
   DrawFrame(renderer_.get(), viewport_size);
   // DrawElements should not be called because the visible rect is outside the
@@ -2482,7 +2523,9 @@
 class MockDCLayerOverlayProcessor : public DCLayerOverlayProcessor {
  public:
   MockDCLayerOverlayProcessor()
-      : DCLayerOverlayProcessor(&debug_settings_, true) {}
+      : DCLayerOverlayProcessor(&debug_settings_,
+                                /*allowed_yuv_overlay_count=*/1,
+                                true) {}
   ~MockDCLayerOverlayProcessor() override = default;
   MOCK_METHOD5(Process,
                void(DisplayResourceProvider* resource_provider,
@@ -3046,6 +3089,358 @@
   DrawFrame(&renderer, viewport_size);
 }
 
+class FastSolidColorMockGLES2Interface : public TestGLES2Interface {
+ public:
+  FastSolidColorMockGLES2Interface() = default;
+
+  MOCK_METHOD1(Enable, void(GLenum cap));
+  MOCK_METHOD1(Disable, void(GLenum cap));
+  MOCK_METHOD4(ClearColor,
+               void(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha));
+  MOCK_METHOD4(Scissor, void(GLint x, GLint y, GLsizei width, GLsizei height));
+};
+
+class GLRendererFastSolidColorTest : public GLRendererTest {
+ public:
+  void SetUp() override {
+    feature_list_.InitAndEnableFeature(features::kFastSolidColorDraw);
+    GLRendererTest::SetUp();
+
+    auto gl_owned = std::make_unique<FastSolidColorMockGLES2Interface>();
+    gl_owned->set_have_post_sub_buffer(true);
+    gl_ = gl_owned.get();
+
+    auto provider = TestContextProvider::Create(std::move(gl_owned));
+    provider->BindToCurrentThread();
+
+    output_surface_ = FakeOutputSurface::Create3d(std::move(provider));
+    output_surface_->BindToClient(&output_surface_client_);
+
+    resource_provider_ = std::make_unique<DisplayResourceProvider>(
+        DisplayResourceProvider::kGpu, output_surface_->context_provider(),
+        nullptr);
+
+    settings_.partial_swap_enabled = true;
+    settings_.slow_down_compositing_scale_factor = 1;
+    settings_.allow_antialiasing = true;
+
+    fake_renderer_ = std::make_unique<FakeRendererGL>(
+        &settings_, &debug_settings_, output_surface_.get(),
+        resource_provider_.get());
+    fake_renderer_->Initialize();
+    EXPECT_TRUE(fake_renderer_->use_partial_swap());
+    fake_renderer_->SetVisible(true);
+  }
+
+  void TearDown() override {
+    resource_provider_.reset();
+    fake_renderer_.reset();
+    output_surface_.reset();
+    gl_ = nullptr;
+
+    GLRendererTest::TearDown();
+  }
+
+  FastSolidColorMockGLES2Interface* gl_ptr() { return gl_; }
+
+  FakeOutputSurface* output_surface() { return output_surface_.get(); }
+
+ protected:
+  void AddExpectations(bool use_fast_path,
+                       const gfx::Rect& scissor_rect,
+                       SkColor color = SK_ColorBLACK,
+                       bool enable_stencil = false) {
+    auto* gl = gl_ptr();
+
+    InSequence seq;
+
+    // Restore GL state method calls
+    EXPECT_CALL(*gl, Disable(GL_DEPTH_TEST));
+    EXPECT_CALL(*gl, Disable(GL_CULL_FACE));
+    EXPECT_CALL(*gl, Disable(GL_STENCIL_TEST));
+    EXPECT_CALL(*gl, Enable(GL_BLEND));
+    EXPECT_CALL(*gl, Disable(GL_SCISSOR_TEST));
+    EXPECT_CALL(*gl, Scissor(0, 0, 0, 0));
+
+    if (!enable_stencil)
+      EXPECT_CALL(*gl, ClearColor(0, 0, 0, 0));
+
+    if (use_fast_path) {
+      EXPECT_CALL(*gl, Enable(GL_SCISSOR_TEST));
+      EXPECT_CALL(*gl, Scissor(scissor_rect.x(), scissor_rect.y(),
+                               scissor_rect.width(), scissor_rect.height()));
+
+      SkColor4f color_f = SkColor4f::FromColor(color);
+      EXPECT_CALL(*gl,
+                  ClearColor(color_f.fR, color_f.fG, color_f.fB, color_f.fA));
+
+      EXPECT_CALL(*gl, Disable(GL_SCISSOR_TEST));
+      EXPECT_CALL(*gl, Scissor(0, 0, 0, 0));
+    }
+
+    if (enable_stencil) {
+      EXPECT_CALL(*gl, Enable(GL_STENCIL_TEST));
+      EXPECT_CALL(*gl, Disable(GL_BLEND));
+    }
+
+    EXPECT_CALL(*gl, Disable(GL_BLEND));
+  }
+
+  void RunTest(const gfx::Size& viewport_size) {
+    fake_renderer_->DecideRenderPassAllocationsForFrame(
+        render_passes_in_draw_order_);
+    DrawFrame(fake_renderer_.get(), viewport_size);
+
+    auto* gl = gl_ptr();
+    ASSERT_TRUE(gl);
+    Mock::VerifyAndClearExpectations(gl);
+  }
+
+ private:
+  FastSolidColorMockGLES2Interface* gl_ = nullptr;
+  std::unique_ptr<FakeRendererGL> fake_renderer_;
+  std::unique_ptr<FakeOutputSurface> output_surface_;
+  std::unique_ptr<DisplayResourceProvider> resource_provider_;
+  cc::FakeOutputSurfaceClient output_surface_client_;
+  RendererSettings settings_;
+  base::test::ScopedFeatureList feature_list_;
+};
+
+TEST_F(GLRendererFastSolidColorTest, RoundedCorners) {
+  gfx::Size viewport_size(500, 500);
+  gfx::Rect root_pass_output_rect(400, 400);
+  gfx::Rect root_pass_damage_rect(10, 20, 300, 200);
+  gfx::Rect quad_rect(0, 50, 100, 100);
+
+  AggregatedRenderPassId root_pass_id{1};
+  AggregatedRenderPass* root_pass = cc::AddRenderPassWithDamage(
+      &render_passes_in_draw_order_, root_pass_id, root_pass_output_rect,
+      root_pass_damage_rect, gfx::Transform(), cc::FilterOperations());
+  root_pass->damage_rect = root_pass_damage_rect;
+  cc::AddQuad(root_pass, quad_rect, SK_ColorRED);
+
+  root_pass->shared_quad_state_list.front()->rounded_corner_bounds =
+      gfx::RRectF(gfx::RectF(quad_rect), 5.f);
+
+  // Fast Solid color draw quads should not be executed.
+  AddExpectations(false /*use_fast_path*/, gfx::Rect());
+
+  RunTest(viewport_size);
+}
+
+TEST_F(GLRendererFastSolidColorTest, Transform3DSlowPath) {
+  gfx::Size viewport_size(500, 500);
+  gfx::Rect root_pass_damage_rect(10, 20, 300, 200);
+  gfx::Rect quad_rect(0, 50, 100, 100);
+
+  AggregatedRenderPassId root_pass_id{1};
+  AggregatedRenderPass* root_pass = cc::AddRenderPass(
+      &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size),
+      gfx::Transform(), cc::FilterOperations());
+  root_pass->damage_rect = root_pass_damage_rect;
+  cc::AddQuad(root_pass, quad_rect, SK_ColorRED);
+
+  gfx::Transform tm_3d;
+  tm_3d.RotateAboutYAxis(30.0);
+  ASSERT_FALSE(tm_3d.IsFlat());
+
+  root_pass->shared_quad_state_list.front()->quad_to_target_transform = tm_3d;
+
+  AddExpectations(false /*use_fast_path*/, gfx::Rect());
+
+  RunTest(viewport_size);
+}
+
+TEST_F(GLRendererFastSolidColorTest, NonTransform3DFastPath) {
+  gfx::Size viewport_size(500, 500);
+  gfx::Rect root_pass_damage_rect(10, 20, 300, 200);
+  gfx::Rect quad_rect(0, 0, 200, 200);
+
+  AggregatedRenderPassId root_pass_id{1};
+  AggregatedRenderPass* root_pass = cc::AddRenderPass(
+      &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size),
+      gfx::Transform(), cc::FilterOperations());
+  root_pass->damage_rect = root_pass_damage_rect;
+  cc::AddQuad(root_pass, quad_rect, SK_ColorRED);
+
+  gfx::Transform tm_non_3d;
+  tm_non_3d.Translate(10.f, 10.f);
+  ASSERT_TRUE(tm_non_3d.IsFlat());
+
+  root_pass->shared_quad_state_list.front()->quad_to_target_transform =
+      tm_non_3d;
+
+  AddExpectations(true /*use_fast_path*/, gfx::Rect(10, 290, 200, 200),
+                  SK_ColorRED);
+
+  RunTest(viewport_size);
+}
+
+TEST_F(GLRendererFastSolidColorTest, NonAxisAlignSlowPath) {
+  gfx::Size viewport_size(500, 500);
+  gfx::Rect root_pass_damage_rect(10, 20, 300, 200);
+  gfx::Rect quad_rect(0, 0, 200, 200);
+
+  AggregatedRenderPassId root_pass_id{1};
+  AggregatedRenderPass* root_pass = cc::AddRenderPass(
+      &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size),
+      gfx::Transform(), cc::FilterOperations());
+  root_pass->damage_rect = root_pass_damage_rect;
+  cc::AddQuad(root_pass, quad_rect, SK_ColorRED);
+
+  gfx::Transform tm_non_axis_align;
+  tm_non_axis_align.RotateAboutZAxis(45.0);
+  ASSERT_TRUE(tm_non_axis_align.IsFlat());
+
+  root_pass->shared_quad_state_list.front()->quad_to_target_transform =
+      tm_non_axis_align;
+
+  AddExpectations(false /*use_fast_path*/, gfx::Rect());
+
+  RunTest(viewport_size);
+}
+
+TEST_F(GLRendererFastSolidColorTest, StencilSlowPath) {
+  gfx::Size viewport_size(500, 500);
+  gfx::Rect root_pass_damage_rect(10, 20, 300, 200);
+  gfx::Rect quad_rect(0, 0, 200, 200);
+
+  AggregatedRenderPassId root_pass_id{1};
+  AggregatedRenderPass* root_pass = cc::AddRenderPass(
+      &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size),
+      gfx::Transform(), cc::FilterOperations());
+  root_pass->damage_rect = root_pass_damage_rect;
+  root_pass->has_transparent_background = false;
+
+  cc::AddQuad(root_pass, quad_rect, SK_ColorRED);
+
+  AddExpectations(false /*use_fast_path*/, gfx::Rect(), SK_ColorRED,
+                  true /*enable_stencil*/);
+  output_surface()->set_has_external_stencil_test(true);
+
+  RunTest(viewport_size);
+}
+
+TEST_F(GLRendererFastSolidColorTest, NeedsBlendingSlowPath) {
+  gfx::Size viewport_size(500, 500);
+  gfx::Rect root_pass_damage_rect(2, 3, 300, 200);
+  gfx::Rect full_quad_rect(0, 0, 50, 50);
+  gfx::Rect quad_rect_1(0, 0, 20, 20);
+  gfx::Rect quad_rect_2(20, 0, 20, 20);
+  gfx::Rect quad_rect_3(0, 20, 20, 20);
+
+  AggregatedRenderPassId root_pass_id{1};
+  AggregatedRenderPass* root_pass = cc::AddRenderPass(
+      &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size),
+      gfx::Transform(), cc::FilterOperations());
+  root_pass->damage_rect = root_pass_damage_rect;
+
+  cc::AddQuad(root_pass, quad_rect_1, SK_ColorRED);
+  root_pass->quad_list.back()->needs_blending = true;
+
+  cc::AddQuad(root_pass, quad_rect_2, SK_ColorBLUE);
+  root_pass->shared_quad_state_list.back()->opacity = 0.5f;
+
+  cc::AddQuad(root_pass, quad_rect_3, SK_ColorGREEN);
+  root_pass->shared_quad_state_list.back()->blend_mode = SkBlendMode::kDstIn;
+
+  cc::AddQuad(root_pass, full_quad_rect, SK_ColorBLACK);
+
+  // The first solid color quad would use a fast path, but the other quads that
+  // require blending will use the slower method.
+  AddExpectations(true /*use_fast_path*/, gfx::Rect(0, 450, 50, 50),
+                  SK_ColorBLACK, false /*enable_stencil*/);
+
+  RunTest(viewport_size);
+}
+
+TEST_F(GLRendererFastSolidColorTest, NeedsBlendingFastPath) {
+  gfx::Size viewport_size(500, 500);
+  gfx::Rect root_pass_damage_rect(2, 3, 300, 200);
+  gfx::Rect quad_rect_1(0, 0, 20, 20);
+  gfx::Rect quad_rect_2(20, 0, 20, 20);
+  gfx::Rect quad_rect_3(0, 20, 20, 20);
+
+  AggregatedRenderPassId root_pass_id{1};
+  AggregatedRenderPass* root_pass = cc::AddRenderPass(
+      &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size),
+      gfx::Transform(), cc::FilterOperations());
+  root_pass->damage_rect = root_pass_damage_rect;
+
+  cc::AddQuad(root_pass, quad_rect_1, SK_ColorRED);
+  root_pass->quad_list.back()->needs_blending = true;
+
+  cc::AddQuad(root_pass, quad_rect_2, SK_ColorBLUE);
+  root_pass->shared_quad_state_list.back()->opacity = 0.5f;
+
+  cc::AddQuad(root_pass, quad_rect_3, SK_ColorGREEN);
+  root_pass->shared_quad_state_list.back()->blend_mode = SkBlendMode::kDstIn;
+
+  auto* gl = gl_ptr();
+
+  // The quads here despite having blend requirements can still use fast path
+  // because they do not intersect with any other quad that has already been
+  // drawn onto the render target.
+  InSequence seq;
+
+  // // Restore GL state method calls
+  EXPECT_CALL(*gl, Disable(GL_DEPTH_TEST));
+  EXPECT_CALL(*gl, Disable(GL_CULL_FACE));
+  EXPECT_CALL(*gl, Disable(GL_STENCIL_TEST));
+  EXPECT_CALL(*gl, Enable(GL_BLEND));
+  EXPECT_CALL(*gl, Disable(GL_SCISSOR_TEST));
+  EXPECT_CALL(*gl, Scissor(0, 0, 0, 0));
+  EXPECT_CALL(*gl, ClearColor(0, 0, 0, 0));
+
+  // Fast path draw used for green quad.
+  EXPECT_CALL(*gl, Enable(GL_SCISSOR_TEST));
+  EXPECT_CALL(*gl, Scissor(0, 460, 20, 20));
+  EXPECT_CALL(*gl, ClearColor(0, 1, 0, 1));
+  EXPECT_CALL(*gl, Disable(GL_SCISSOR_TEST));
+  EXPECT_CALL(*gl, Scissor(0, 0, 0, 0));
+
+  // Fast path draw used for blue quad.
+  EXPECT_CALL(*gl, Enable(GL_SCISSOR_TEST));
+  EXPECT_CALL(*gl, Scissor(20, 480, 20, 20));
+  EXPECT_CALL(*gl, ClearColor(0, 0, 0.5f, 0.5f));
+  EXPECT_CALL(*gl, Disable(GL_SCISSOR_TEST));
+  EXPECT_CALL(*gl, Scissor(0, 0, 0, 0));
+
+  // Fast path draw used for red quad.
+  EXPECT_CALL(*gl, Enable(GL_SCISSOR_TEST));
+  EXPECT_CALL(*gl, Scissor(0, 480, 20, 20));
+  EXPECT_CALL(*gl, ClearColor(1, 0, 0, 1));
+  EXPECT_CALL(*gl, Disable(GL_SCISSOR_TEST));
+  EXPECT_CALL(*gl, Scissor(0, 0, 0, 0));
+
+  EXPECT_CALL(*gl, Disable(GL_BLEND));
+
+  RunTest(viewport_size);
+}
+
+TEST_F(GLRendererFastSolidColorTest, AntiAliasSlowPath) {
+  gfx::Size viewport_size(500, 500);
+  gfx::Rect root_pass_damage_rect(10, 20, 300, 200);
+  gfx::Rect quad_rect(0, 0, 200, 200);
+
+  AggregatedRenderPassId root_pass_id{1};
+  AggregatedRenderPass* root_pass = cc::AddRenderPass(
+      &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size),
+      gfx::Transform(), cc::FilterOperations());
+  root_pass->damage_rect = root_pass_damage_rect;
+  cc::AddQuad(root_pass, quad_rect, SK_ColorRED);
+
+  gfx::Transform tm_aa;
+  tm_aa.Translate(0.1f, 0.1f);
+  ASSERT_TRUE(tm_aa.IsFlat());
+
+  root_pass->shared_quad_state_list.front()->quad_to_target_transform = tm_aa;
+
+  AddExpectations(false /*use_fast_path*/, gfx::Rect());
+
+  RunTest(viewport_size);
+}
+
 class PartialSwapMockGLES2Interface : public TestGLES2Interface {
  public:
   PartialSwapMockGLES2Interface() = default;
@@ -3057,6 +3452,13 @@
 };
 
 class GLRendererPartialSwapTest : public GLRendererTest {
+ public:
+  void SetUp() override {
+    // Force enable fast solid color draw path.
+    scoped_feature_list_.InitAndEnableFeature(features::kFastSolidColorDraw);
+    GLRendererTest::SetUp();
+  }
+
  protected:
   void RunTest(bool partial_swap, bool set_draw_rectangle) {
     auto gl_owned = std::make_unique<PartialSwapMockGLES2Interface>();
@@ -3095,15 +3497,30 @@
     EXPECT_CALL(*gl, Disable(GL_DEPTH_TEST)).Times(1);
     EXPECT_CALL(*gl, Disable(GL_CULL_FACE)).Times(1);
     EXPECT_CALL(*gl, Disable(GL_STENCIL_TEST)).Times(1);
-    EXPECT_CALL(*gl, Disable(GL_BLEND)).Times(2);
     EXPECT_CALL(*gl, Enable(GL_BLEND)).Times(1);
-    EXPECT_CALL(*gl, Disable(GL_SCISSOR_TEST)).Times(1);
-    EXPECT_CALL(*gl, Scissor(0, 0, 0, 0)).Times(1);
-    if (set_draw_rectangle) {
-      EXPECT_CALL(*gl, Enable(GL_SCISSOR_TEST)).Times(1);
-      EXPECT_CALL(*gl, Scissor(0, 0, 100, 100)).Times(1);
+
+    if (output_surface->capabilities().supports_dc_layers) {
+      EXPECT_CALL(*gl, Disable(GL_SCISSOR_TEST)).Times(1);
+      EXPECT_CALL(*gl, Scissor(0, 0, 0, 0)).Times(1);
+
+      // Root render pass requires a scissor if the output surface supports
+      // dc layers.
+      EXPECT_CALL(*gl, Enable(GL_SCISSOR_TEST)).Times(3);
+      EXPECT_CALL(*gl, Scissor(0, 0, 100, 100)).Times(3);
+    } else {
+      EXPECT_CALL(*gl, Disable(GL_SCISSOR_TEST)).Times(2);
+      EXPECT_CALL(*gl, Scissor(0, 0, 0, 0)).Times(2);
+      if (set_draw_rectangle) {
+        EXPECT_CALL(*gl, Enable(GL_SCISSOR_TEST)).Times(2);
+        EXPECT_CALL(*gl, Scissor(0, 0, 100, 100)).Times(2);
+      } else {
+        EXPECT_CALL(*gl, Enable(GL_SCISSOR_TEST)).Times(1);
+        EXPECT_CALL(*gl, Scissor(0, 0, 100, 100)).Times(1);
+      }
     }
 
+    EXPECT_CALL(*gl, Disable(GL_BLEND)).Times(1);
+
     AggregatedRenderPassId root_pass_id{1};
     AggregatedRenderPass* root_pass = cc::AddRenderPass(
         &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size),
@@ -3136,19 +3553,34 @@
       gfx::Rect output_rectangle =
           partial_swap ? root_pass_damage_rect : gfx::Rect(viewport_size);
 
+      // The scissor is flipped, so subtract the y coord and height from the
+      // bottom of the GL viewport.
+      gfx::Rect scissor_rect(output_rectangle.x(),
+                             viewport_size.height() - output_rectangle.y() -
+                                 output_rectangle.height(),
+                             output_rectangle.width(),
+                             output_rectangle.height());
+
+      // Drawing the solid color quad using glClear and scissor rect.
+      EXPECT_CALL(*gl, Enable(GL_SCISSOR_TEST));
+      EXPECT_CALL(*gl, Scissor(scissor_rect.x(), scissor_rect.y(),
+                               scissor_rect.width(), scissor_rect.height()));
+
       if (partial_swap || set_draw_rectangle) {
         EXPECT_CALL(*gl, Enable(GL_SCISSOR_TEST));
-        // The scissor is flipped, so subtract the y coord and height from the
-        // bottom of the GL viewport.
-        EXPECT_CALL(
-            *gl, Scissor(output_rectangle.x(),
-                         viewport_size.height() - output_rectangle.y() -
-                             output_rectangle.height(),
-                         output_rectangle.width(), output_rectangle.height()));
+        EXPECT_CALL(*gl, Scissor(scissor_rect.x(), scissor_rect.y(),
+                                 scissor_rect.width(), scissor_rect.height()));
       }
 
-      // The quad doesn't need blending.
-      EXPECT_CALL(*gl, Disable(GL_BLEND));
+      // Restore GL state after solid color draw quad.
+      if (partial_swap || set_draw_rectangle) {
+        EXPECT_CALL(*gl, Enable(GL_SCISSOR_TEST));
+        EXPECT_CALL(*gl, Scissor(scissor_rect.x(), scissor_rect.y(),
+                                 scissor_rect.width(), scissor_rect.height()));
+      } else {
+        EXPECT_CALL(*gl, Disable(GL_SCISSOR_TEST));
+        EXPECT_CALL(*gl, Scissor(0, 0, 0, 0));
+      }
 
       // Blending is disabled at the end of the frame.
       EXPECT_CALL(*gl, Disable(GL_BLEND));
@@ -3164,6 +3596,9 @@
       Mock::VerifyAndClearExpectations(gl);
     }
   }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 TEST_F(GLRendererPartialSwapTest, PartialSwap) {
@@ -3236,7 +3671,8 @@
 
   auto processor = std::make_unique<OverlayProcessorWin>(
       output_surface.get(),
-      std::make_unique<DCLayerOverlayProcessor>(&debug_settings_, true));
+      std::make_unique<DCLayerOverlayProcessor>(
+          &debug_settings_, /*allowed_yuv_overlay_count=*/1, true));
 
   RendererSettings settings;
   settings.partial_swap_enabled = true;
diff --git a/components/viz/service/display/overlay_dc_unittest.cc b/components/viz/service/display/overlay_dc_unittest.cc
index c466c78..0a2c6d11 100644
--- a/components/viz/service/display/overlay_dc_unittest.cc
+++ b/components/viz/service/display/overlay_dc_unittest.cc
@@ -101,10 +101,11 @@
 class DCTestOverlayProcessor : public OverlayProcessorWin {
  public:
   explicit DCTestOverlayProcessor(OutputSurface* output_surface)
-      : OverlayProcessorWin(
-            output_surface,
-            std::make_unique<DCLayerOverlayProcessor>(&debug_settings_, true)) {
-  }
+      : OverlayProcessorWin(output_surface,
+                            std::make_unique<DCLayerOverlayProcessor>(
+                                &debug_settings_,
+                                /*allowed_yuv_overlay_count=*/1,
+                                true)) {}
   DebugRendererSettings debug_settings_;
 };
 
diff --git a/components/viz/service/display/overlay_processor_interface.cc b/components/viz/service/display/overlay_processor_interface.cc
index 659dfb7..d111caf9b 100644
--- a/components/viz/service/display/overlay_processor_interface.cc
+++ b/components/viz/service/display/overlay_processor_interface.cc
@@ -92,8 +92,8 @@
                                                enable_ca_overlay);
 #elif defined(OS_WIN)
   return std::make_unique<OverlayProcessorWin>(
-      output_surface,
-      std::make_unique<DCLayerOverlayProcessor>(debug_settings));
+      output_surface, std::make_unique<DCLayerOverlayProcessor>(
+                          debug_settings, /*allowed_yuv_overlay_count=*/1));
 #elif defined(USE_OZONE)
   if (!features::IsUsingOzonePlatform())
     return std::make_unique<OverlayProcessorStub>();
diff --git a/components/webxr/BUILD.gn b/components/webxr/BUILD.gn
new file mode 100644
index 0000000..719ac8a
--- /dev/null
+++ b/components/webxr/BUILD.gn
@@ -0,0 +1,17 @@
+source_set("webxr") {
+  defines = []
+  sources = [
+    "mailbox_to_surface_bridge_impl.cc",
+    "mailbox_to_surface_bridge_impl.h",
+  ]
+
+  deps = [
+    "//components/viz/common:common",
+    "//content/public/browser:browser",
+    "//device/vr/android:vr_android",
+    "//gpu/command_buffer/common:common",
+    "//gpu/ipc/common:common",
+    "//services/viz/public/cpp/gpu:gpu",
+    "//ui/gl:gl",
+  ]
+}
diff --git a/components/webxr/DEPS b/components/webxr/DEPS
new file mode 100644
index 0000000..c717a6fc
--- /dev/null
+++ b/components/webxr/DEPS
@@ -0,0 +1,12 @@
+include_rules = [
+  "+components/viz/common/gpu/context_provider.h",
+  "+content/public/browser",
+  "+device/vr/android/mailbox_to_surface_bridge.h",
+  "+gpu/GLES2/gl2extchromium.h",
+  "+gpu/command_buffer/client",
+  "+gpu/command_buffer/common",
+  "+gpu/ipc",
+  "+services/viz/public/cpp/gpu/context_provider_command_buffer.h",
+  "+ui/gfx",
+  "+ui/gl/android",
+]
\ No newline at end of file
diff --git a/components/webxr/OWNERS b/components/webxr/OWNERS
new file mode 100644
index 0000000..0890a7e8
--- /dev/null
+++ b/components/webxr/OWNERS
@@ -0,0 +1,5 @@
+alcooper@chromium.org
+bialpio@chromium.org
+
+# TEAM: xr-dev@chromium.org
+# COMPONENT: Blink>WebXR
diff --git a/chrome/browser/android/vr/mailbox_to_surface_bridge.cc b/components/webxr/mailbox_to_surface_bridge_impl.cc
similarity index 84%
rename from chrome/browser/android/vr/mailbox_to_surface_bridge.cc
rename to components/webxr/mailbox_to_surface_bridge_impl.cc
index 8c1e0f2..6cdd4c5d 100644
--- a/chrome/browser/android/vr/mailbox_to_surface_bridge.cc
+++ b/components/webxr/mailbox_to_surface_bridge_impl.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/android/vr/mailbox_to_surface_bridge.h"
+#include "components/webxr/mailbox_to_surface_bridge_impl.h"
 
 #include <memory>
 #include <string>
@@ -28,6 +28,7 @@
 #include "gpu/ipc/common/gpu_surface_tracker.h"
 #include "services/viz/public/cpp/gpu/context_provider_command_buffer.h"
 #include "ui/gfx/color_space.h"
+#include "ui/gfx/transform.h"
 #include "ui/gl/android/surface_texture.h"
 
 #include <android/native_window_jni.h>
@@ -43,8 +44,10 @@
     attribute vec4 a_Position;
     attribute vec2 a_TexCoordinate;
     varying highp vec2 v_TexCoordinate;
+    uniform mat4 u_UvTransform;
     void main() {
-      v_TexCoordinate = a_TexCoordinate;
+      highp vec4 uv_in = vec4(a_TexCoordinate.x, a_TexCoordinate.y, 0, 1);
+      v_TexCoordinate = (u_UvTransform * uv_in).xy;
       gl_Position = a_Position;
     }
 );
@@ -134,7 +137,7 @@
 
 GLuint ConsumeTexture(gpu::gles2::GLES2Interface* gl,
                       const gpu::MailboxHolder& mailbox) {
-  TRACE_EVENT0("gpu", "MailboxToSurfaceBridge::ConsumeTexture");
+  TRACE_EVENT0("gpu", "MailboxToSurfaceBridgeImpl::ConsumeTexture");
   gl->WaitSyncTokenCHROMIUM(mailbox.sync_token.GetConstData());
 
   return gl->CreateAndTexStorage2DSharedImageCHROMIUM(mailbox.mailbox.name);
@@ -142,13 +145,13 @@
 
 }  // namespace
 
-namespace vr {
+namespace webxr {
 
-MailboxToSurfaceBridge::MailboxToSurfaceBridge() {
+MailboxToSurfaceBridgeImpl::MailboxToSurfaceBridgeImpl() {
   DVLOG(1) << __FUNCTION__;
 }
 
-MailboxToSurfaceBridge::~MailboxToSurfaceBridge() {
+MailboxToSurfaceBridgeImpl::~MailboxToSurfaceBridgeImpl() {
   if (surface_handle_) {
     // Unregister from the surface tracker to avoid a resource leak.
     gpu::GpuSurfaceTracker* tracker = gpu::GpuSurfaceTracker::Get();
@@ -158,17 +161,17 @@
   DVLOG(1) << __FUNCTION__;
 }
 
-bool MailboxToSurfaceBridge::IsConnected() {
+bool MailboxToSurfaceBridgeImpl::IsConnected() {
   return context_provider_ && gl_ && context_support_;
 }
 
-bool MailboxToSurfaceBridge::IsGpuWorkaroundEnabled(int32_t workaround) {
+bool MailboxToSurfaceBridgeImpl::IsGpuWorkaroundEnabled(int32_t workaround) {
   DCHECK(IsConnected());
 
   return context_provider_->GetGpuFeatureInfo().IsWorkaroundEnabled(workaround);
 }
 
-void MailboxToSurfaceBridge::OnContextAvailableOnUiThread(
+void MailboxToSurfaceBridgeImpl::OnContextAvailableOnUiThread(
     scoped_refptr<viz::ContextProvider> provider) {
   DVLOG(1) << __FUNCTION__;
   // Must save a reference to the viz::ContextProvider to keep it alive,
@@ -180,11 +183,11 @@
   gl_thread_task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(
-          &MailboxToSurfaceBridge::BindContextProviderToCurrentThread,
+          &MailboxToSurfaceBridgeImpl::BindContextProviderToCurrentThread,
           base::Unretained(this)));
 }
 
-void MailboxToSurfaceBridge::BindContextProviderToCurrentThread() {
+void MailboxToSurfaceBridgeImpl::BindContextProviderToCurrentThread() {
   auto result = context_provider_->BindToCurrentThread();
   if (result != gpu::ContextResult::kSuccess) {
     DLOG(ERROR) << "Failed to init viz::ContextProvider";
@@ -210,7 +213,7 @@
   }
 }
 
-void MailboxToSurfaceBridge::CreateSurface(
+void MailboxToSurfaceBridgeImpl::CreateSurface(
     gl::SurfaceTexture* surface_texture) {
   ANativeWindow* window = surface_texture->CreateSurface();
   gpu::GpuSurfaceTracker* tracker = gpu::GpuSurfaceTracker::Get();
@@ -225,7 +228,7 @@
   ANativeWindow_release(window);
 }
 
-void MailboxToSurfaceBridge::CreateAndBindContextProvider(
+void MailboxToSurfaceBridgeImpl::CreateAndBindContextProvider(
     base::OnceClosure on_bound_callback) {
   gl_thread_task_runner_ = base::ThreadTaskRunnerHandle::Get();
   on_context_bound_ = std::move(on_bound_callback);
@@ -234,7 +237,7 @@
   // until the context becomes available. So pass it on to the callback, so that
   // it stays alive, and is destroyed on the same thread once done.
   auto callback =
-      base::BindOnce(&MailboxToSurfaceBridge::OnContextAvailableOnUiThread,
+      base::BindOnce(&MailboxToSurfaceBridgeImpl::OnContextAvailableOnUiThread,
                      weak_ptr_factory_.GetWeakPtr());
 
   content::GetUIThreadTaskRunner({})->PostTask(
@@ -272,7 +275,7 @@
                      surface_handle_, std::move(callback)));
 }
 
-void MailboxToSurfaceBridge::ResizeSurface(int width, int height) {
+void MailboxToSurfaceBridgeImpl::ResizeSurface(int width, int height) {
   surface_width_ = width;
   surface_height_ = height;
 
@@ -289,8 +292,14 @@
   gl_->Viewport(0, 0, surface_width_, surface_height_);
 }
 
-bool MailboxToSurfaceBridge::CopyMailboxToSurfaceAndSwap(
+bool MailboxToSurfaceBridgeImpl::CopyMailboxToSurfaceAndSwap(
     const gpu::MailboxHolder& mailbox) {
+  return CopyMailboxToSurfaceAndSwap(mailbox, gfx::Transform());
+}
+
+bool MailboxToSurfaceBridgeImpl::CopyMailboxToSurfaceAndSwap(
+    const gpu::MailboxHolder& mailbox,
+    const gfx::Transform& uv_transform) {
   if (!IsConnected()) {
     // We may not have a context yet, i.e. due to surface initialization
     // being incomplete. This is not an error, but we obviously can't draw
@@ -316,26 +325,28 @@
   GLuint sourceTexture = ConsumeTexture(gl_, mailbox);
   gl_->BeginSharedImageAccessDirectCHROMIUM(
       sourceTexture, GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM);
-  DrawQuad(sourceTexture);
+  DrawQuad(sourceTexture, uv_transform);
   gl_->EndSharedImageAccessDirectCHROMIUM(sourceTexture);
   gl_->DeleteTextures(1, &sourceTexture);
   gl_->SwapBuffers(swap_id_++);
   return true;
 }
 
-void MailboxToSurfaceBridge::GenSyncToken(gpu::SyncToken* out_sync_token) {
+void MailboxToSurfaceBridgeImpl::GenSyncToken(gpu::SyncToken* out_sync_token) {
   TRACE_EVENT0("gpu", __FUNCTION__);
   DCHECK(IsConnected());
   gl_->GenSyncTokenCHROMIUM(out_sync_token->GetData());
 }
 
-void MailboxToSurfaceBridge::WaitSyncToken(const gpu::SyncToken& sync_token) {
+void MailboxToSurfaceBridgeImpl::WaitSyncToken(
+    const gpu::SyncToken& sync_token) {
   TRACE_EVENT0("gpu", __FUNCTION__);
   DCHECK(IsConnected());
   gl_->WaitSyncTokenCHROMIUM(sync_token.GetConstData());
 }
 
-void MailboxToSurfaceBridge::WaitForClientGpuFence(gfx::GpuFence* gpu_fence) {
+void MailboxToSurfaceBridgeImpl::WaitForClientGpuFence(
+    gfx::GpuFence* gpu_fence) {
   TRACE_EVENT0("gpu", __FUNCTION__);
   DCHECK(IsConnected());
   GLuint id = gl_->CreateClientGpuFenceCHROMIUM(gpu_fence->AsClientGpuFence());
@@ -343,7 +354,7 @@
   gl_->DestroyGpuFenceCHROMIUM(id);
 }
 
-void MailboxToSurfaceBridge::CreateGpuFence(
+void MailboxToSurfaceBridgeImpl::CreateGpuFence(
     const gpu::SyncToken& sync_token,
     base::OnceCallback<void(std::unique_ptr<gfx::GpuFence>)> callback) {
   TRACE_EVENT0("gpu", __FUNCTION__);
@@ -354,7 +365,7 @@
   gl_->DestroyGpuFenceCHROMIUM(id);
 }
 
-gpu::MailboxHolder MailboxToSurfaceBridge::CreateSharedImage(
+gpu::MailboxHolder MailboxToSurfaceBridgeImpl::CreateSharedImage(
     gpu::GpuMemoryBufferImplAndroidHardwareBuffer* buffer,
     const gfx::ColorSpace& color_space,
     uint32_t usage) {
@@ -375,7 +386,7 @@
   return mailbox_holder;
 }
 
-void MailboxToSurfaceBridge::DestroySharedImage(
+void MailboxToSurfaceBridgeImpl::DestroySharedImage(
     const gpu::MailboxHolder& mailbox_holder) {
   TRACE_EVENT0("gpu", __FUNCTION__);
   DCHECK(IsConnected());
@@ -385,12 +396,12 @@
   sii->DestroySharedImage(mailbox_holder.sync_token, mailbox_holder.mailbox);
 }
 
-void MailboxToSurfaceBridge::DestroyContext() {
+void MailboxToSurfaceBridgeImpl::DestroyContext() {
   gl_ = nullptr;
   context_provider_ = nullptr;
 }
 
-void MailboxToSurfaceBridge::InitializeRenderer() {
+void MailboxToSurfaceBridgeImpl::InitializeRenderer() {
   GLuint vertex_shader_handle =
       CompileShader(gl_, GL_VERTEX_SHADER, kQuadCopyVertex);
   if (!vertex_shader_handle) {
@@ -421,6 +432,8 @@
       gl_->GetAttribLocation(program_handle, "a_TexCoordinate");
   GLuint texUniform_handle =
       gl_->GetUniformLocation(program_handle, "u_Texture");
+  uniform_uv_transform_handle_ =
+      gl_->GetUniformLocation(program_handle, "u_UvTransform");
 
   GLuint vertexBuffer = 0;
   gl_->GenBuffers(1, &vertexBuffer);
@@ -464,7 +477,8 @@
   gl_->Uniform1i(texUniform_handle, 0);
 }
 
-void MailboxToSurfaceBridge::DrawQuad(unsigned int texture_handle) {
+void MailboxToSurfaceBridgeImpl::DrawQuad(unsigned int texture_handle,
+                                          const gfx::Transform& uv_transform) {
   DCHECK(IsConnected());
 
   // We're redrawing over the entire viewport, but it's generally more
@@ -475,6 +489,11 @@
   // it's not supported on older devices such as Nexus 5X.
   gl_->Clear(GL_COLOR_BUFFER_BIT);
 
+  float uv_transform_floats[16];
+  uv_transform.matrix().asColMajorf(uv_transform_floats);
+  gl_->UniformMatrix4fv(uniform_uv_transform_handle_, 1, GL_FALSE,
+                        &uv_transform_floats[0]);
+
   // Configure texture. This is a 1:1 pixel copy since the surface
   // size is resized to match the source canvas, so we can use
   // GL_NEAREST.
@@ -482,8 +501,17 @@
   gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
   gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
   gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-  gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+  if (uv_transform.IsIdentity()) {
+    gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+  } else {
+    gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+  }
   gl_->DrawArrays(GL_TRIANGLE_FAN, 0, 4);
 }
 
-}  // namespace vr
+std::unique_ptr<device::MailboxToSurfaceBridge>
+MailboxToSurfaceBridgeFactoryImpl::Create() const {
+  return std::make_unique<MailboxToSurfaceBridgeImpl>();
+}
+
+}  // namespace webxr
diff --git a/components/webxr/mailbox_to_surface_bridge_impl.h b/components/webxr/mailbox_to_surface_bridge_impl.h
new file mode 100644
index 0000000..d611c65
--- /dev/null
+++ b/components/webxr/mailbox_to_surface_bridge_impl.h
@@ -0,0 +1,124 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_WEBXR_MAILBOX_TO_SURFACE_BRIDGE_IMPL_H_
+#define COMPONENTS_WEBXR_MAILBOX_TO_SURFACE_BRIDGE_IMPL_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/single_thread_task_runner.h"
+#include "device/vr/android/mailbox_to_surface_bridge.h"
+#include "gpu/command_buffer/common/sync_token.h"
+#include "gpu/ipc/common/surface_handle.h"
+#include "ui/gfx/buffer_format_util.h"
+#include "ui/gfx/gpu_fence.h"
+#include "ui/gl/android/scoped_java_surface.h"
+
+namespace gpu {
+class ContextSupport;
+
+namespace gles2 {
+class GLES2Interface;
+}
+}  // namespace gpu
+
+namespace viz {
+class ContextProvider;
+}
+
+namespace webxr {
+
+class MailboxToSurfaceBridgeImpl : public device::MailboxToSurfaceBridge {
+ public:
+  // It's OK to create an object instance and pass it to a different thread,
+  // i.e. to enable dependency injection for a unit test, but all methods on it
+  // must be called consistently on a single GL thread. This is verified by
+  // DCHECKs.
+  MailboxToSurfaceBridgeImpl();
+  ~MailboxToSurfaceBridgeImpl() override;
+
+  bool IsConnected() override;
+
+  bool IsGpuWorkaroundEnabled(int32_t workaround) override;
+
+  void CreateSurface(gl::SurfaceTexture*) override;
+
+  void CreateAndBindContextProvider(base::OnceClosure callback) override;
+
+  // All other public methods below must be called on the GL thread
+  // (except when marked otherwise).
+
+  void ResizeSurface(int width, int height) override;
+
+  bool CopyMailboxToSurfaceAndSwap(const gpu::MailboxHolder& mailbox) override;
+
+  bool CopyMailboxToSurfaceAndSwap(const gpu::MailboxHolder& mailbox,
+                                   const gfx::Transform& uv_transform) override;
+
+  void GenSyncToken(gpu::SyncToken* out_sync_token) override;
+
+  void WaitSyncToken(const gpu::SyncToken& sync_token) override;
+
+  void WaitForClientGpuFence(gfx::GpuFence*) override;
+
+  void CreateGpuFence(const gpu::SyncToken& sync_token,
+                      base::OnceCallback<void(std::unique_ptr<gfx::GpuFence>)>
+                          callback) override;
+
+  gpu::MailboxHolder CreateSharedImage(
+      gpu::GpuMemoryBufferImplAndroidHardwareBuffer* buffer,
+      const gfx::ColorSpace& color_space,
+      uint32_t usage) override;
+
+  void DestroySharedImage(const gpu::MailboxHolder& mailbox_holder) override;
+
+ private:
+  void BindContextProviderToCurrentThread();
+  void OnContextAvailableOnUiThread(
+      scoped_refptr<viz::ContextProvider> provider);
+  void InitializeRenderer();
+  void DestroyContext();
+  void DrawQuad(unsigned int textureHandle, const gfx::Transform& uv_transform);
+
+  scoped_refptr<viz::ContextProvider> context_provider_;
+  std::unique_ptr<gl::ScopedJavaSurface> surface_;
+  gpu::gles2::GLES2Interface* gl_ = nullptr;
+  gpu::ContextSupport* context_support_ = nullptr;
+  int surface_handle_ = gpu::kNullSurfaceHandle;
+  // TODO(https://crbug.com/836524): shouldn't have both of these closures
+  // in the same class like this.
+  base::OnceClosure on_context_bound_;
+
+  int surface_width_ = 0;
+  int surface_height_ = 0;
+
+  // If true, surface width/height is the intended size that should be applied
+  // to the surface once it's ready for use.
+  bool needs_resize_ = false;
+
+  // A swap ID which is passed to GL swap. Incremented each call.
+  uint64_t swap_id_ = 0;
+
+  // Uniform handle for the UV transform used by DrawQuad.
+  uint32_t uniform_uv_transform_handle_ = 0;
+
+  // A task runner for the GL thread
+  scoped_refptr<base::SingleThreadTaskRunner> gl_thread_task_runner_;
+
+  // Must be last.
+  base::WeakPtrFactory<MailboxToSurfaceBridgeImpl> weak_ptr_factory_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(MailboxToSurfaceBridgeImpl);
+};
+
+class MailboxToSurfaceBridgeFactoryImpl
+    : public device::MailboxToSurfaceBridgeFactory {
+ public:
+  std::unique_ptr<device::MailboxToSurfaceBridge> Create() const override;
+};
+
+}  // namespace webxr
+
+#endif  // COMPONENTS_WEBXR_MAILBOX_TO_SURFACE_BRIDGE_H_
diff --git a/content/browser/DEPS b/content/browser/DEPS
index ddf5db3..a0177bb 100644
--- a/content/browser/DEPS
+++ b/content/browser/DEPS
@@ -50,7 +50,8 @@
   "+device/fido/hid",
   "+device/gamepad", # For gamepad API
   "+device/nfc",
-  "+device/vr",  # For WebVR API
+  "+device/vr/public", # For WebXR API
+  "+device/vr/buildflags/buildflags.h",  # For WebXR API
   # This can only be used on POSIX, in particular it mustn't be used on Windows
   # in the browser DLL.
   "+gin/v8_initializer.h",
diff --git a/content/browser/back_forward_cache_browsertest.cc b/content/browser/back_forward_cache_browsertest.cc
index 7569387..057747f 100644
--- a/content/browser/back_forward_cache_browsertest.cc
+++ b/content/browser/back_forward_cache_browsertest.cc
@@ -2044,10 +2044,10 @@
 
 IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                        DoesNotCacheIfAcquiredWakeLock) {
-  ASSERT_TRUE(embedded_test_server()->Start());
+  ASSERT_TRUE(CreateHttpsServer()->Start());
 
   // 1) Navigate to a page with WakeLock usage.
-  GURL url(embedded_test_server()->GetURL("/back_forward_cache/empty.html"));
+  GURL url(https_server()->GetURL("a.com", "/back_forward_cache/empty.html"));
   EXPECT_TRUE(NavigateToURL(shell(), url));
 
   RenderFrameHostImpl* rfh_a = current_frame_host();
@@ -2055,18 +2055,18 @@
 
   // Acquire WakeLock.
   EXPECT_EQ("DONE", EvalJs(rfh_a, R"(
-  new Promise(async resolve => {
-    try {
-      await navigator.wakeLock.request('screen');
-      resolve('DONE');
-    } catch (error) {
-      resolve('error: request failed');
-    }
-  });
+    new Promise(async resolve => {
+      try {
+        await navigator.wakeLock.request('screen');
+        resolve('DONE');
+      } catch (error) {
+        resolve('error: request failed');
+      }
+    });
   )"));
 
   // 2) Navigate away.
-  shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
+  shell()->LoadURL(https_server()->GetURL("b.com", "/title1.html"));
 
   // The page uses WakeLock so it should be deleted.
   deleted.WaitUntilDeleted();
@@ -2081,55 +2081,78 @@
       blink::scheduler::WebSchedulerTrackedFeature::kWakeLock, FROM_HERE);
 }
 
-// TODO(yuzus): By releasing wakelock, the page should become cacheable again.
-// Fix and re-enable the rest of this test.
-IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
-                       DISABLED_CacheIfReleasedWakeLock) {
-  ASSERT_TRUE(embedded_test_server()->Start());
+IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, CacheIfReleasedWakeLock) {
+  ASSERT_TRUE(CreateHttpsServer()->Start());
 
   // 1) Navigate to a page with WakeLock usage.
-  GURL url(embedded_test_server()->GetURL(
-      "/back_forward_cache/page_with_wakelock.html"));
+  GURL url(https_server()->GetURL("a.com", "/back_forward_cache/empty.html"));
   EXPECT_TRUE(NavigateToURL(shell(), url));
-
+  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
   RenderFrameHostImpl* rfh_a = current_frame_host();
-  RenderFrameDeletedObserver deleted(current_frame_host());
+  // Acquire and release WakeLock.
+  EXPECT_EQ("DONE", EvalJs(rfh_a, R"(
+    new Promise(async resolve => {
+      try {
+        const lock = await navigator.wakeLock.request('screen');
+        await lock.release();
+        resolve('DONE');
+      } catch (error) {
+        resolve('error: request failed');
+      }
+    });
+  )"));
 
-  // Acquire WakeLock.
-  EXPECT_EQ("DONE", EvalJs(rfh_a, "requestWakeLock()"));
   // 2) Navigate away.
-  shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
-
-  // The page uses WakeLock so it should be deleted.
-  deleted.WaitUntilDeleted();
+  shell()->LoadURL(https_server()->GetURL("b.com", "/title1.html"));
+  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+  EXPECT_TRUE(rfh_a->IsInBackForwardCache());
 
   // 3) Go back to the page with WakeLock.
   web_contents()->GetController().GoBack();
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+  EXPECT_EQ(current_frame_host(), rfh_a);
+
+  // This time the page is restored from cache because WakeLock is released.
+  ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored,
+                FROM_HERE);
+}
+
+IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
+                       DoesNotCacheIfAnyWakeLockHeld) {
+  ASSERT_TRUE(CreateHttpsServer()->Start());
+
+  // 1) Navigate to a page with WakeLock usage.
+  GURL url(https_server()->GetURL("/back_forward_cache/empty.html"));
+  EXPECT_TRUE(NavigateToURL(shell(), url));
+  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+  RenderFrameHostImpl* rfh_a = current_frame_host();
+  // Acquire and release WakeLock.
+  EXPECT_EQ("DONE", EvalJs(rfh_a, R"(
+    new Promise(async resolve => {
+      try {
+         const lock1 = await navigator.wakeLock.request('screen');
+         const lock2 = await navigator.wakeLock.request('screen');
+         await lock1.release();
+         resolve('DONE');
+      } catch (error) {
+         resolve('error: request failed');
+      }
+    });
+  )"));
+
+  // 2) Navigate away.
+  shell()->LoadURL(https_server()->GetURL("b.com", "/title1.html"));
+  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+
+  // 3) Go back to the page with WakeLock.
+  web_contents()->GetController().GoBack();
+  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+
   ExpectNotRestored(
       {BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
       FROM_HERE);
   ExpectBlocklistedFeature(
       blink::scheduler::WebSchedulerTrackedFeature::kWakeLock, FROM_HERE);
-
-  // Release WakeLock.
-  EXPECT_EQ("DONE", EvalJs(current_frame_host(), "releaseWakeLock()"));
-
-  // 4) Navigate away.
-  web_contents()->GetController().GoBack();
-  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
-
-  EXPECT_TRUE(rfh_a->IsInBackForwardCache());
-
-  // 5) Go back to the page with WakeLock.
-  web_contents()->GetController().GoBack();
-  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
-  EXPECT_EQ(current_frame_host(), rfh_a);
-  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
-
-  // This time the page is restored from cache because WakeLock is released.
-  ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored,
-                FROM_HERE);
 }
 
 IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
diff --git a/content/browser/loader/navigation_url_loader_impl.cc b/content/browser/loader/navigation_url_loader_impl.cc
index 3a0f7d0..b2d3918 100644
--- a/content/browser/loader/navigation_url_loader_impl.cc
+++ b/content/browser/loader/navigation_url_loader_impl.cc
@@ -206,16 +206,8 @@
   new_request->trusted_params->client_security_state =
       request_info->client_security_state.Clone();
   new_request->is_main_frame = request_info->is_main_frame;
-
-  net::RequestPriority net_priority = net::HIGHEST;
-  if (!request_info->is_main_frame &&
-      base::FeatureList::IsEnabled(features::kLowPriorityIframes)) {
-    net_priority = net::LOWEST;
-  }
-  new_request->priority = net_priority;
-
+  new_request->priority = net::HIGHEST;
   new_request->render_frame_id = frame_tree_node_id;
-
   new_request->request_initiator =
       request_info->common_params->initiator_origin;
   new_request->referrer = request_info->common_params->referrer->url;
diff --git a/content/browser/loader/navigation_url_loader_impl_unittest.cc b/content/browser/loader/navigation_url_loader_impl_unittest.cc
index 0393112..0af4d37 100644
--- a/content/browser/loader/navigation_url_loader_impl_unittest.cc
+++ b/content/browser/loader/navigation_url_loader_impl_unittest.cc
@@ -280,25 +280,6 @@
     }
   }
 
-  net::RequestPriority NavigateAndReturnRequestPriority(const GURL& url,
-                                                        bool is_main_frame) {
-    TestNavigationURLLoaderDelegate delegate;
-    base::test::ScopedFeatureList scoped_feature_list_;
-
-    scoped_feature_list_.InitAndEnableFeature(features::kLowPriorityIframes);
-
-    std::unique_ptr<NavigationURLLoader> loader = CreateTestLoader(
-        url,
-        base::StringPrintf("%s: %s", net::HttpRequestHeaders::kOrigin,
-                           url.GetOrigin().spec().c_str()),
-        "GET", &delegate, NavigationDownloadPolicy(), is_main_frame);
-    delegate.WaitForRequestRedirected();
-    loader->FollowRedirect({}, {}, {}, blink::PreviewsTypes::PREVIEWS_OFF);
-    delegate.WaitForResponseStarted();
-
-    return most_recent_resource_request_.value().priority;
-  }
-
   net::RedirectInfo NavigateAndReturnRedirectInfo(const GURL& url,
                                                   bool upgrade_if_insecure,
                                                   bool expect_request_fail) {
@@ -328,16 +309,6 @@
   base::Optional<network::ResourceRequest> most_recent_resource_request_;
 };
 
-TEST_F(NavigationURLLoaderImplTest, RequestPriority) {
-  ASSERT_TRUE(http_test_server_.Start());
-  const GURL url = http_test_server_.GetURL("/redirect301-to-echo");
-
-  EXPECT_EQ(net::HIGHEST,
-            NavigateAndReturnRequestPriority(url, true /* is_main_frame */));
-  EXPECT_EQ(net::LOWEST,
-            NavigateAndReturnRequestPriority(url, false /* is_main_frame */));
-}
-
 TEST_F(NavigationURLLoaderImplTest, IsolationInfoOfMainFrameNavigation) {
   ASSERT_TRUE(http_test_server_.Start());
 
diff --git a/content/browser/loader/quic_transport_browsertest.cc b/content/browser/loader/quic_transport_browsertest.cc
index a99a891..dc494a2 100644
--- a/content/browser/loader/quic_transport_browsertest.cc
+++ b/content/browser/loader/quic_transport_browsertest.cc
@@ -276,8 +276,7 @@
   ASSERT_TRUE(WaitForTitle(ASCIIToUTF16("PASS"), {ASCIIToUTF16("FAIL")}));
 }
 
-// Flaky on many platforms (see crbug/1064434).
-IN_PROC_BROWSER_TEST_F(QuicTransportBrowserTest, DISABLED_ReceiveStream) {
+IN_PROC_BROWSER_TEST_F(QuicTransportBrowserTest, ReceiveStream) {
   ASSERT_TRUE(embedded_test_server()->Start());
   ASSERT_TRUE(
       NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html")));
diff --git a/content/browser/renderer_host/agent_scheduling_group_host.cc b/content/browser/renderer_host/agent_scheduling_group_host.cc
index e773a359..da6b9f8 100644
--- a/content/browser/renderer_host/agent_scheduling_group_host.cc
+++ b/content/browser/renderer_host/agent_scheduling_group_host.cc
@@ -240,12 +240,14 @@
 }
 
 void AgentSchedulingGroupHost::CreateFrame(mojom::CreateFrameParamsPtr params) {
-  SetUpMojoIfNeeded();
+  DCHECK(process_.IsInitializedAndNotDead());
+  DCHECK(mojo_remote_.is_bound());
   mojo_remote_.get()->CreateFrame(std::move(params));
 }
 
 void AgentSchedulingGroupHost::CreateView(mojom::CreateViewParamsPtr params) {
-  SetUpMojoIfNeeded();
+  DCHECK(process_.IsInitializedAndNotDead());
+  DCHECK(mojo_remote_.is_bound());
   mojo_remote_.get()->CreateView(std::move(params));
 }
 
diff --git a/content/browser/renderer_host/clipboard_host_impl.cc b/content/browser/renderer_host/clipboard_host_impl.cc
index 29887892..ec20fc6 100644
--- a/content/browser/renderer_host/clipboard_host_impl.cc
+++ b/content/browser/renderer_host/clipboard_host_impl.cc
@@ -88,13 +88,16 @@
       clipboard_(ui::Clipboard::GetForCurrentThread()) {
   // |render_frame_host| may be null in unit tests.
   if (render_frame_host) {
-    render_frame_routing_id_ = render_frame_host->GetRoutingID();
-    render_process_id_ = render_frame_host->GetProcess()->GetID();
+    render_frame_routing_id_ =
+        GlobalFrameRoutingId(render_frame_host->GetProcess()->GetID(),
+                             render_frame_host->GetRoutingID());
     clipboard_writer_ = std::make_unique<ui::ScopedClipboardWriter>(
         ui::ClipboardBuffer::kCopyPaste,
         std::make_unique<ui::ClipboardDataEndpoint>(
             render_frame_host->GetLastCommittedOrigin()));
   } else {
+    render_frame_routing_id_ = GlobalFrameRoutingId(
+        ChildProcessHost::kInvalidUniqueID, MSG_ROUTING_NONE);
     clipboard_writer_ = std::make_unique<ui::ScopedClipboardWriter>(
         ui::ClipboardBuffer::kCopyPaste);
   }
@@ -384,7 +387,7 @@
     std::string data) {
   // May not have a RenderFrameHost in tests.
   RenderFrameHostImpl* render_frame_host =
-      RenderFrameHostImpl::FromID(render_process_id_, render_frame_routing_id_);
+      RenderFrameHostImpl::FromID(render_frame_routing_id_);
   if (render_frame_host) {
     render_frame_host->IsClipboardPasteAllowed(
         data_type, data,
@@ -416,7 +419,7 @@
 std::unique_ptr<ui::ClipboardDataEndpoint>
 ClipboardHostImpl::CreateDataEndpoint() {
   RenderFrameHostImpl* render_frame_host =
-      RenderFrameHostImpl::FromID(render_process_id_, render_frame_routing_id_);
+      RenderFrameHostImpl::FromID(render_frame_routing_id_);
   if (render_frame_host) {
     return std::make_unique<ui::ClipboardDataEndpoint>(
         render_frame_host->GetLastCommittedOrigin());
diff --git a/content/browser/renderer_host/clipboard_host_impl.h b/content/browser/renderer_host/clipboard_host_impl.h
index 8ef374e..df05d9a5 100644
--- a/content/browser/renderer_host/clipboard_host_impl.h
+++ b/content/browser/renderer_host/clipboard_host_impl.h
@@ -182,8 +182,7 @@
 
   mojo::Receiver<blink::mojom::ClipboardHost> receiver_;
   ui::Clipboard* const clipboard_;  // Not owned
-  int render_frame_routing_id_ = MSG_ROUTING_NONE;
-  int render_process_id_ = ChildProcessHost::kInvalidUniqueID;
+  GlobalFrameRoutingId render_frame_routing_id_;
   std::unique_ptr<ui::ScopedClipboardWriter> clipboard_writer_;
 
   // Outstanding is allowed requests per clipboard contents.  Maps a clipboard
diff --git a/content/browser/service_worker/service_worker_storage.cc b/content/browser/service_worker/service_worker_storage.cc
index 3526c3bb..2750b392 100644
--- a/content/browser/service_worker/service_worker_storage.cc
+++ b/content/browser/service_worker/service_worker_storage.cc
@@ -1064,12 +1064,6 @@
     disk_cache_->Disable();
 }
 
-void ServiceWorkerStorage::PurgeResources(const ResourceList& resources) {
-  if (!has_checked_for_stale_resources_)
-    DeleteStaleResources();
-  StartPurgingResources(resources);
-}
-
 void ServiceWorkerStorage::PurgeResources(
     const std::vector<int64_t>& resource_ids) {
   if (!has_checked_for_stale_resources_)
diff --git a/content/browser/service_worker/service_worker_storage.h b/content/browser/service_worker/service_worker_storage.h
index 5d2093bb..b109a02b 100644
--- a/content/browser/service_worker/service_worker_storage.h
+++ b/content/browser/service_worker/service_worker_storage.h
@@ -278,12 +278,11 @@
 
   void Disable();
 
-  // Schedules deleting |resources| from the disk cache and removing their keys
-  // as purgeable resources from the service worker database. It's OK to call
-  // this for resources that don't have purgeable resource keys, like
+  // Schedules deleting `resource_ids` from the disk cache and removing their
+  // keys as purgeable resources from the service worker database. It's OK to
+  // call this for resources that don't have purgeable resource keys, like
   // uncommitted resources, as long as the caller does its own cleanup to remove
   // the uncommitted resource keys.
-  void PurgeResources(const ResourceList& resources);
   void PurgeResources(const std::vector<int64_t>& resource_ids);
 
   // Applies |policy_updates|.
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index cecc4ef..7888dca 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -13722,34 +13722,24 @@
         features::kReloadHiddenTabsWithCrashedSubframes);
   }
 
- private:
-  base::test::ScopedFeatureList feature_list_;
-};
-
-// Verify the feature where hidden tabs with crashed subframes are marked for
-// reload. This avoids showing crashed subframes if a hidden tab is eventually
-// shown. See https://crbug.com/841572.
-// crbug.com/1010119, fails on Win. crbug.com/1015971, fails on Linux.
-// crbug.com/1049885, fails on Android and Mac.
-#if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_CHROMEOS) || \
-    defined(OS_ANDROID) || defined(OS_MACOSX)
-#define MAYBE_ReloadHiddenTabWithCrashedSubframe \
-  DISABLED_ReloadHiddenTabWithCrashedSubframe
-#else
-#define MAYBE_ReloadHiddenTabWithCrashedSubframe \
-  ReloadHiddenTabWithCrashedSubframe
-#endif
-IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTestWithSadFrameTabReload,
-                       MAYBE_ReloadHiddenTabWithCrashedSubframe) {
-  auto crash_process = [](FrameTreeNode* ftn) {
+  void CrashProcess(FrameTreeNode* ftn) {
     RenderProcessHost* process = ftn->current_frame_host()->GetProcess();
     RenderProcessHostWatcher crash_observer(
         process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
     process->Shutdown(0);
     crash_observer.Wait();
     EXPECT_FALSE(ftn->current_frame_host()->IsRenderFrameLive());
-  };
+  }
 
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+// Verify the feature where hidden tabs with a visible crashed subframe are
+// marked for reload. This avoids showing crashed subframes if a hidden tab is
+// eventually shown. See https://crbug.com/841572.
+IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTestWithSadFrameTabReload,
+                       ReloadHiddenTabWithCrashedSubframeInViewport) {
   GURL main_url(embedded_test_server()->GetURL(
       "a.com", "/cross_site_iframe_factory.html?a(b)"));
   EXPECT_TRUE(NavigateToURL(shell(), main_url));
@@ -13764,16 +13754,18 @@
   // Kill the b.com subframe's process.  This should mark the hidden
   // WebContents for reload.
   {
-    SCOPED_TRACE("In-viewport sad frame on a hidden tab");
     base::HistogramTester histograms;
-    crash_process(root->child_at(0));
+    CrashProcess(root->child_at(0));
     histograms.ExpectUniqueSample(
         "Stability.ChildFrameCrash.TabMarkedForReload", true, 1);
     histograms.ExpectUniqueSample(
         "Stability.ChildFrameCrash.TabMarkedForReload.Visibility",
         blink::mojom::FrameVisibility::kRenderedInViewport, 1);
+  }
 
-    // Show the WebContents.  This should trigger a reload of the main frame.
+  // Show the WebContents.  This should trigger a reload of the main frame.
+  {
+    base::HistogramTester histograms;
     web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
     EXPECT_TRUE(WaitForLoadStop(web_contents()));
     histograms.ExpectUniqueSample(
@@ -13784,76 +13776,124 @@
   // Both frames should now have live renderer processes.
   EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
   EXPECT_TRUE(root->child_at(0)->current_frame_host()->IsRenderFrameLive());
+}
 
-  // Next, try the same with a crashed subframe that's scrolled out of view.
-  // This should also trigger a reload.
+// Verify the feature where hidden tabs with crashed subframes are marked for
+// reload. This avoids showing crashed subframes if a hidden tab is eventually
+// shown. Similar to the test above, except that the crashed subframe is
+// scrolled out of view.
+IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTestWithSadFrameTabReload,
+                       ReloadHiddenTabWithCrashedSubframeOutOfView) {
+  // Set WebContents to VISIBLE to avoid hitting the |!did_first_set_visible_|
+  // case when we hide it later.
+  web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
+
+  // Navigate to a page with an OOPIF that's scrolled out of view.
   GURL out_of_view_url(
       embedded_test_server()->GetURL("a.com", "/iframe_out_of_view.html"));
   EXPECT_TRUE(NavigateToURL(shell(), out_of_view_url));
   EXPECT_EQ("LOADED", EvalJsWithManualReply(shell(), "notifyWhenLoaded();"));
   NavigateIframeToURL(web_contents(), "test_iframe",
                       embedded_test_server()->GetURL("b.com", "/title1.html"));
-  web_contents()->UpdateWebContentsVisibility(Visibility::HIDDEN);
+
+  // Verify the OOPIF isn't visible at the moment.
+  FrameTreeNode* root = web_contents()->GetFrameTree()->root();
+  RenderFrameProxyHost* proxy_to_parent =
+      root->child_at(0)->render_manager()->GetProxyToParent();
+  CrossProcessFrameConnector* connector =
+      proxy_to_parent->cross_process_frame_connector();
+  EXPECT_FALSE(connector->IsVisible());
+  EXPECT_EQ(blink::mojom::FrameVisibility::kRenderedOutOfViewport,
+            connector->visibility());
+
+  // Hide the WebContents and crash the OOPIF.
   {
-    SCOPED_TRACE("Out-of-viewport sad frame on a hidden tab");
     base::HistogramTester histograms;
-    crash_process(root->child_at(0));
+    web_contents()->UpdateWebContentsVisibility(Visibility::HIDDEN);
+    CrashProcess(root->child_at(0));
     histograms.ExpectUniqueSample(
         "Stability.ChildFrameCrash.TabMarkedForReload", true, 1);
     histograms.ExpectUniqueSample(
         "Stability.ChildFrameCrash.TabMarkedForReload.Visibility",
         blink::mojom::FrameVisibility::kRenderedOutOfViewport, 1);
+  }
 
+  EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
+  EXPECT_FALSE(root->child_at(0)->current_frame_host()->IsRenderFrameLive());
+
+  // Show the tab and ensure that it reloads.
+  {
+    base::HistogramTester histograms;
     web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
     EXPECT_TRUE(WaitForLoadStop(web_contents()));
     histograms.ExpectUniqueSample(
         "Navigation.LoadIfNecessaryType",
         NavigationControllerImpl::NeedsReloadType::kCrashedSubframe, 1);
   }
+
   EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
   EXPECT_TRUE(root->child_at(0)->current_frame_host()->IsRenderFrameLive());
+}
 
-  // Now, load a page with where an iframe is hidden with "display:none".
-  // Ensure that we do not mark the tab for reload in that case.
+// Verify that hidden tabs with a crashed subframe are not marked for reload
+// when the crashed subframe is hidden with "display:none".
+IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTestWithSadFrameTabReload,
+                       DoNotReloadHiddenTabWithHiddenCrashedSubframe) {
+  // Set WebContents to VISIBLE to avoid hitting the |!did_first_set_visible_|
+  // case when we hide it later.
+  web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
+
   GURL hidden_iframe_url(
       embedded_test_server()->GetURL("a.com", "/page_with_hidden_iframe.html"));
   EXPECT_TRUE(NavigateToURL(shell(), hidden_iframe_url));
   NavigateIframeToURL(web_contents(), "test_iframe",
                       embedded_test_server()->GetURL("b.com", "/title1.html"));
+  FrameTreeNode* root = web_contents()->GetFrameTree()->root();
   RenderFrameProxyHost* proxy_to_parent =
       root->child_at(0)->render_manager()->GetProxyToParent();
   EXPECT_TRUE(proxy_to_parent->cross_process_frame_connector()->IsHidden());
 
+  // Crashing a hidden OOPIF shouldn't mark the tab for reload.
   web_contents()->UpdateWebContentsVisibility(Visibility::HIDDEN);
-  {
-    SCOPED_TRACE("display:none sad frame on a hidden tab");
-    base::HistogramTester histograms;
-    crash_process(root->child_at(0));
-    histograms.ExpectUniqueSample(
-        "Stability.ChildFrameCrash.TabMarkedForReload", false, 1);
-  }
-  web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
-  EXPECT_TRUE(WaitForLoadStop(web_contents()));
-  EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
-  EXPECT_FALSE(root->child_at(0)->current_frame_host()->IsRenderFrameLive());
+  base::HistogramTester histograms;
+  CrashProcess(root->child_at(0));
+  histograms.ExpectUniqueSample("Stability.ChildFrameCrash.TabMarkedForReload",
+                                false, 1);
 
-  // Finally, ensure that the reload policy doesn't trigger for a visible tab,
-  // even if it becomes hidden and then visible again.
+  // Making the WebContents visible again should keep the sad frame and should
+  // not load anything new.
+  web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
+  EXPECT_TRUE(WaitForLoadStop(web_contents()));
+  EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
+  EXPECT_FALSE(root->child_at(0)->current_frame_host()->IsRenderFrameLive());
+}
+
+// Ensure that the sad frame reload policy doesn't trigger for a visible tab,
+// even if it becomes hidden and then visible again.
+IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTestWithSadFrameTabReload,
+                       DoNotReloadVisibleTabWithCrashedSubframe) {
+  // Set WebContents to VISIBLE to avoid hitting the |!did_first_set_visible_|
+  // case when we hide it later.
+  web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
   EXPECT_EQ(Visibility::VISIBLE, web_contents()->GetVisibility());
+
+  GURL main_url(embedded_test_server()->GetURL(
+      "a.com", "/cross_site_iframe_factory.html?a(b)"));
   EXPECT_TRUE(NavigateToURL(shell(), main_url));
-  {
-    SCOPED_TRACE("Visible sad frame on a visible tab");
-    base::HistogramTester histograms;
-    crash_process(root->child_at(0));
-    histograms.ExpectUniqueSample(
-        "Stability.ChildFrameCrash.TabMarkedForReload", false, 1);
-  }
+  FrameTreeNode* root = web_contents()->GetFrameTree()->root();
+  base::HistogramTester histograms;
+  CrashProcess(root->child_at(0));
+  histograms.ExpectUniqueSample("Stability.ChildFrameCrash.TabMarkedForReload",
+                                false, 1);
+
   EXPECT_EQ(Visibility::VISIBLE, web_contents()->GetVisibility());
   web_contents()->UpdateWebContentsVisibility(Visibility::HIDDEN);
   web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
   EXPECT_TRUE(WaitForLoadStop(web_contents()));
   EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
   EXPECT_FALSE(root->child_at(0)->current_frame_host()->IsRenderFrameLive());
+  histograms.ExpectUniqueSample("Stability.ChildFrameCrash.TabMarkedForReload",
+                                false, 1);
 }
 
 // Check that when a frame changes a subframe's size twice and then sends a
diff --git a/content/browser/sms/sms_browsertest.cc b/content/browser/sms/sms_browsertest.cc
index 94f9689..3ae6e8f4 100644
--- a/content/browser/sms/sms_browsertest.cc
+++ b/content/browser/sms/sms_browsertest.cc
@@ -771,7 +771,8 @@
   EXPECT_TRUE(render_frame_host->DocumentUsedWebOTP());
 }
 
-IN_PROC_BROWSER_TEST_F(SmsBrowserTest, RecordPendingOriginCount) {
+// Disabled test: https://crbug.com/1134455
+IN_PROC_BROWSER_TEST_F(SmsBrowserTest, DISABLED_RecordPendingOriginCount) {
   base::HistogramTester histogram_tester;
   auto provider = std::make_unique<MockSmsProvider>();
   MockSmsProvider* mock_provider_ptr = provider.get();
diff --git a/content/browser/xr/DEPS b/content/browser/xr/DEPS
index 974b375c..ef170b7f44c 100644
--- a/content/browser/xr/DEPS
+++ b/content/browser/xr/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
   "+device/base",
-  "+device/vr",
+  "+device/vr/orientation",
+  "+device/vr/test",
 ]
diff --git a/content/browser/xr/service/vr_service_impl.cc b/content/browser/xr/service/vr_service_impl.cc
index c985059e..cee6ad2 100644
--- a/content/browser/xr/service/vr_service_impl.cc
+++ b/content/browser/xr/service/vr_service_impl.cc
@@ -504,6 +504,9 @@
                        "id", request.runtime_id);
 
   auto runtime_options = GetRuntimeOptions(request.options.get());
+  // Make the resolved enabled features available to the runtime.
+  runtime_options->enabled_features.assign(request.enabled_features.begin(),
+                                           request.enabled_features.end());
 
 #if defined(OS_ANDROID) && BUILDFLAG(ENABLE_ARCORE)
   if (request.runtime_id == device::mojom::XRDeviceId::ARCORE_DEVICE_ID) {
@@ -512,10 +515,15 @@
     runtime_options->render_frame_id = render_frame_host_->GetRoutingID();
   }
 #endif
-  // Make the resolved enabled features available to the runtime.
-  runtime_options->enabled_features.reserve(request.enabled_features.size());
-  for (const auto& feature : request.enabled_features) {
-    runtime_options->enabled_features.push_back(feature);
+
+  bool use_dom_overlay =
+      base::Contains(runtime_options->enabled_features,
+                     device::mojom::XRSessionFeature::DOM_OVERLAY);
+
+  if (use_dom_overlay) {
+    // Tell RenderFrameHostImpl that we're setting up the WebXR DOM Overlay,
+    // it checks for this in EnterFullscreen via HasSeenRecentXrOverlaySetup().
+    render_frame_host_->SetIsXrOverlaySetup();
   }
 
   if (device::XRSessionModeUtils::IsImmersive(runtime_options->mode)) {
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 450c516..ce6dd47f 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -356,10 +356,6 @@
 #endif
 };
 
-// Enables lowering the priority of the resources in iframes.
-const base::Feature kLowPriorityIframes{"LowPriorityIframes",
-                                        base::FEATURE_DISABLED_BY_DEFAULT};
-
 // Removes the association between the `AgentSchedulingGroup` interfaces and the
 // IPC Channel. This will break ordering guarantees between different agent
 // scheduling groups (ordering withing a group is still preserved).
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index 579f33f..8a6d4cb 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -82,7 +82,6 @@
 CONTENT_EXPORT extern const base::Feature kLazyInitializeMediaControls;
 CONTENT_EXPORT extern const base::Feature kLegacyWindowsDWriteFontFallback;
 CONTENT_EXPORT extern const base::Feature kLogJsConsoleMessages;
-CONTENT_EXPORT extern const base::Feature kLowPriorityIframes;
 CONTENT_EXPORT extern const base::Feature
     kMbiDetachAgentSchedulingGroupFromChannel;
 CONTENT_EXPORT extern const base::Feature kMediaDevicesSystemMonitorCache;
diff --git a/content/renderer/child_frame_compositing_helper.cc b/content/renderer/child_frame_compositing_helper.cc
index 9c016e3..43097a2 100644
--- a/content/renderer/child_frame_compositing_helper.cc
+++ b/content/renderer/child_frame_compositing_helper.cc
@@ -90,8 +90,7 @@
 }
 
 scoped_refptr<cc::DisplayItemList>
-ChildFrameCompositingHelper::PaintContentsToDisplayList(
-    PaintingControlSetting) {
+ChildFrameCompositingHelper::PaintContentsToDisplayList() {
   DCHECK(crash_ui_layer_);
   auto layer_size = crash_ui_layer_->bounds();
   auto display_list = base::MakeRefCounted<cc::DisplayItemList>();
diff --git a/content/renderer/child_frame_compositing_helper.h b/content/renderer/child_frame_compositing_helper.h
index 2615b9b..6ff2d6a 100644
--- a/content/renderer/child_frame_compositing_helper.h
+++ b/content/renderer/child_frame_compositing_helper.h
@@ -53,8 +53,7 @@
   // cc::ContentLayerClient implementation. Called from the cc::PictureLayer
   // created for the crashed child frame to display the sad image.
   gfx::Rect PaintableRegion() override;
-  scoped_refptr<cc::DisplayItemList> PaintContentsToDisplayList(
-      PaintingControlSetting) override;
+  scoped_refptr<cc::DisplayItemList> PaintContentsToDisplayList() override;
   bool FillsBoundsCompletely() const override;
   size_t GetApproximateUnsharedMemoryUsage() const override;
 
diff --git a/content/test/data/back_forward_cache/page_with_wakelock.html b/content/test/data/back_forward_cache/page_with_wakelock.html
deleted file mode 100644
index 1586440668..0000000
--- a/content/test/data/back_forward_cache/page_with_wakelock.html
+++ /dev/null
@@ -1,21 +0,0 @@
-<html>
-  <title>wakelock</title>
-</html>
-<script>
-let lock;
-
-async function requestWakeLock() {
-  try {
-    lock = await navigator.wakeLock.request('screen');
-    return 'DONE';
-  } catch (error) {
-    return `${error}`;
-  }
-}
-
-async function releaseWakeLock() {
-  await lock.release();
-  return 'DONE';
-}
-
-</script>
diff --git a/device/vr/android/BUILD.gn b/device/vr/android/BUILD.gn
new file mode 100644
index 0000000..37f5894
--- /dev/null
+++ b/device/vr/android/BUILD.gn
@@ -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.
+
+static_library("vr_android") {
+  defines = []
+  sources = [
+    "mailbox_to_surface_bridge.h",
+    "web_xr_presentation_state.cc",
+    "web_xr_presentation_state.h",
+  ]
+
+  deps = [
+    "//gpu/ipc/common:common",
+    "//ui/gl:gl",
+  ]
+}
diff --git a/device/vr/android/DEPS b/device/vr/android/DEPS
new file mode 100644
index 0000000..fefc58b
--- /dev/null
+++ b/device/vr/android/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+  "+gpu/ipc/common",
+  "+gpu/command_buffer/common/shared_image_usage.h",
+  "+ui/gl",
+]
diff --git a/device/vr/android/arcore/BUILD.gn b/device/vr/android/arcore/BUILD.gn
index 85f76776..e733c1c 100644
--- a/device/vr/android/arcore/BUILD.gn
+++ b/device/vr/android/arcore/BUILD.gn
@@ -11,12 +11,22 @@
   defines = [ "IS_VR_ARCORE_IMPL" ]
   sources = [
     "address_to_id_map.h",
+    "ar_image_transport.cc",
+    "ar_image_transport.h",
+    "ar_renderer.cc",
+    "ar_renderer.h",
     "arcore.cc",
     "arcore.h",
     "arcore_anchor_manager.cc",
     "arcore_anchor_manager.h",
+    "arcore_device.cc",
+    "arcore_device.h",
     "arcore_device_provider_factory.cc",
     "arcore_device_provider_factory.h",
+    "arcore_gl.cc",
+    "arcore_gl.h",
+    "arcore_gl_thread.cc",
+    "arcore_gl_thread.h",
     "arcore_impl.cc",
     "arcore_impl.h",
     "arcore_math_utils.cc",
@@ -24,6 +34,7 @@
     "arcore_plane_manager.cc",
     "arcore_plane_manager.h",
     "arcore_sdk.h",
+    "arcore_session_utils.h",
     "arcore_shim.cc",
     "arcore_shim.h",
     "scoped_arcore_objects.h",
@@ -35,9 +46,12 @@
 
   deps = [
     "//base",
+    "//device/vr:vr",
     "//device/vr:vr_base",
+    "//device/vr/android:vr_android",
     "//mojo/public/cpp/bindings",
     "//ui/gfx",
+    "//ui/gl/init",
   ]
 
   configs += [ "//third_party/arcore-android-sdk:libarcore_config" ]
diff --git a/device/vr/android/arcore/DEPS b/device/vr/android/arcore/DEPS
index efd700c..5e9ba5929 100644
--- a/device/vr/android/arcore/DEPS
+++ b/device/vr/android/arcore/DEPS
@@ -1 +1,3 @@
-include_rules = [ "+third_party/arcore-android-sdk/src", ]
\ No newline at end of file
+include_rules = [
+  "+third_party/arcore-android-sdk/src",
+  ]
\ No newline at end of file
diff --git a/chrome/browser/android/vr/arcore_device/ar_image_transport.cc b/device/vr/android/arcore/ar_image_transport.cc
similarity index 96%
rename from chrome/browser/android/vr/arcore_device/ar_image_transport.cc
rename to device/vr/android/arcore/ar_image_transport.cc
index 3fe8fc2..c2ef9322 100644
--- a/chrome/browser/android/vr/arcore_device/ar_image_transport.cc
+++ b/device/vr/android/arcore/ar_image_transport.cc
@@ -2,15 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/android/vr/arcore_device/ar_image_transport.h"
+#include "device/vr/android/arcore/ar_image_transport.h"
 
 #include "base/android/android_hardware_buffer_compat.h"
 #include "base/android/scoped_hardware_buffer_handle.h"
 #include "base/containers/queue.h"
 #include "base/trace_event/trace_event.h"
 #include "base/trace_event/traced_value.h"
-#include "chrome/browser/android/vr/mailbox_to_surface_bridge.h"
-#include "chrome/browser/android/vr/web_xr_presentation_state.h"
+#include "device/vr/android/mailbox_to_surface_bridge.h"
+#include "device/vr/android/web_xr_presentation_state.h"
 #include "gpu/command_buffer/common/shared_image_usage.h"
 #include "gpu/ipc/common/gpu_memory_buffer_impl_android_hardware_buffer.h"
 #include "ui/gfx/gpu_fence.h"
@@ -26,7 +26,7 @@
 namespace device {
 
 ArImageTransport::ArImageTransport(
-    std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_bridge)
+    std::unique_ptr<MailboxToSurfaceBridge> mailbox_bridge)
     : gl_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
       mailbox_bridge_(std::move(mailbox_bridge)) {
   DVLOG(2) << __func__;
@@ -378,7 +378,8 @@
 
 void ArImageTransport::CopyMailboxToSurfaceAndSwap(
     const gfx::Size& frame_size,
-    const gpu::MailboxHolder& mailbox) {
+    const gpu::MailboxHolder& mailbox,
+    const gfx::Transform& uv_transform) {
   DVLOG(2) << __func__;
   if (frame_size != surface_size_) {
     DVLOG(2) << __func__ << " resize from " << surface_size_.ToString()
@@ -392,7 +393,8 @@
   // Draw the image to the surface in the GPU process's command buffer context.
   // This will trigger an OnFrameAvailable event once the corresponding
   // SurfaceTexture in the local GL context is ready for updating.
-  bool swapped = mailbox_bridge_->CopyMailboxToSurfaceAndSwap(mailbox);
+  bool swapped =
+      mailbox_bridge_->CopyMailboxToSurfaceAndSwap(mailbox, uv_transform);
   DCHECK(swapped);
 }
 
@@ -401,7 +403,7 @@
 }
 
 std::unique_ptr<ArImageTransport> ArImageTransportFactory::Create(
-    std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_bridge) {
+    std::unique_ptr<MailboxToSurfaceBridge> mailbox_bridge) {
   return std::make_unique<ArImageTransport>(std::move(mailbox_bridge));
 }
 
diff --git a/chrome/browser/android/vr/arcore_device/ar_image_transport.h b/device/vr/android/arcore/ar_image_transport.h
similarity index 89%
rename from chrome/browser/android/vr/arcore_device/ar_image_transport.h
rename to device/vr/android/arcore/ar_image_transport.h
index 0daa910..a7fca4d 100644
--- a/chrome/browser/android/vr/arcore_device/ar_image_transport.h
+++ b/device/vr/android/arcore/ar_image_transport.h
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_AR_IMAGE_TRANSPORT_H_
-#define CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_AR_IMAGE_TRANSPORT_H_
+#ifndef DEVICE_VR_ANDROID_ARCORE_AR_IMAGE_TRANSPORT_H_
+#define DEVICE_VR_ANDROID_ARCORE_AR_IMAGE_TRANSPORT_H_
 
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "base/single_thread_task_runner.h"
-#include "chrome/browser/android/vr/arcore_device/ar_renderer.h"
+#include "device/vr/android/arcore/ar_renderer.h"
 #include "device/vr/public/mojom/vr_service.mojom.h"
 #include "ui/gfx/geometry/size_f.h"
 
@@ -27,22 +27,23 @@
 }  // namespace gpu
 
 namespace vr {
-class MailboxToSurfaceBridge;
 class WebXrPresentationState;
 struct WebXrSharedBuffer;
 }  // namespace vr
 
 namespace device {
 
+class MailboxToSurfaceBridge;
+
 using XrFrameCallback = base::RepeatingCallback<void(const gfx::Transform&)>;
 
 // This class handles transporting WebGL rendered output from the GPU process's
 // command buffer GL context to the local GL context, and compositing WebGL
 // output onto the camera image using the local GL context.
-class ArImageTransport {
+class COMPONENT_EXPORT(VR_ARCORE) ArImageTransport {
  public:
   explicit ArImageTransport(
-      std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_bridge);
+      std::unique_ptr<MailboxToSurfaceBridge> mailbox_bridge);
   virtual ~ArImageTransport();
 
   virtual void DestroySharedBuffers(vr::WebXrPresentationState* webxr);
@@ -85,7 +86,8 @@
                                         const gfx::Transform& uv_transform);
   virtual void WaitSyncToken(const gpu::SyncToken& sync_token);
   virtual void CopyMailboxToSurfaceAndSwap(const gfx::Size& frame_size,
-                                           const gpu::MailboxHolder& mailbox);
+                                           const gpu::MailboxHolder& mailbox,
+                                           const gfx::Transform& uv_transform);
 
   bool UseSharedBuffer() { return shared_buffer_draw_; }
   void SetFrameAvailableCallback(XrFrameCallback on_frame_available);
@@ -109,7 +111,7 @@
 
   scoped_refptr<base::SingleThreadTaskRunner> gl_thread_task_runner_;
 
-  std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_bridge_;
+  std::unique_ptr<MailboxToSurfaceBridge> mailbox_bridge_;
 
   // If true, use shared buffer transport aka DRAW_INTO_TEXTURE_MAILBOX.
   // If false, use Surface transport aka SUBMIT_AS_MAILBOX_HOLDER.
@@ -134,13 +136,13 @@
   DISALLOW_COPY_AND_ASSIGN(ArImageTransport);
 };
 
-class ArImageTransportFactory {
+class COMPONENT_EXPORT(VR_ARCORE) ArImageTransportFactory {
  public:
   virtual ~ArImageTransportFactory() = default;
   virtual std::unique_ptr<ArImageTransport> Create(
-      std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_bridge);
+      std::unique_ptr<MailboxToSurfaceBridge> mailbox_bridge);
 };
 
 }  // namespace device
 
-#endif  // CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_AR_IMAGE_TRANSPORT_H_
+#endif  // DEVICE_VR_ANDROID_ARCORE_AR_IMAGE_TRANSPORT_H_
diff --git a/chrome/browser/android/vr/arcore_device/ar_renderer.cc b/device/vr/android/arcore/ar_renderer.cc
similarity index 87%
rename from chrome/browser/android/vr/arcore_device/ar_renderer.cc
rename to device/vr/android/arcore/ar_renderer.cc
index 68ab235..51f721a 100644
--- a/chrome/browser/android/vr/arcore_device/ar_renderer.cc
+++ b/device/vr/android/arcore/ar_renderer.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/android/vr/arcore_device/ar_renderer.h"
+#include "device/vr/android/arcore/ar_renderer.h"
 
 #include "base/stl_util.h"
 #include "device/vr/vr_gl_util.h"
@@ -100,12 +100,15 @@
   glVertexAttribPointer(position_handle_, 2, GL_FLOAT, false, 0, 0);
   glEnableVertexAttribArray(position_handle_);
 
-  // Bind texture. This is a 1:1 pixel copy since the source surface
-  // and renderbuffer destination size are resized to match, so use
-  // GL_NEAREST.
+  // Bind texture. This is not necessarily a 1:1 pixel copy since the
+  // size is modified by framebufferScaleFactor and requestViewportScale,
+  // so use GL_LINEAR.
   glActiveTexture(GL_TEXTURE0);
   glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture_handle);
-  vr::SetTexParameters(GL_TEXTURE_EXTERNAL_OES);
+  glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+  glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+  glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+  glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
   glUniform1i(texture_handle_, 0);
 
   glUniformMatrix4fv(uv_transform_, 1, GL_FALSE, &uv_transform[0]);
diff --git a/chrome/browser/android/vr/arcore_device/ar_renderer.h b/device/vr/android/arcore/ar_renderer.h
similarity index 79%
rename from chrome/browser/android/vr/arcore_device/ar_renderer.h
rename to device/vr/android/arcore/ar_renderer.h
index 5fc7d8d..b1365bc 100644
--- a/chrome/browser/android/vr/arcore_device/ar_renderer.h
+++ b/device/vr/android/arcore/ar_renderer.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_AR_RENDERER_H_
-#define CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_AR_RENDERER_H_
+#ifndef DEVICE_VR_ANDROID_ARCORE_AR_RENDERER_H_
+#define DEVICE_VR_ANDROID_ARCORE_AR_RENDERER_H_
 
 #include "base/macros.h"
 #include "ui/gl/gl_bindings.h"
@@ -33,4 +33,4 @@
 
 }  // namespace device
 
-#endif  // CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_AR_RENDERER_H_
+#endif  // DEVICE_VR_ANDROID_ARCORE_AR_RENDERER_H_
diff --git a/chrome/browser/android/vr/arcore_device/arcore_device.cc b/device/vr/android/arcore/arcore_device.cc
similarity index 88%
rename from chrome/browser/android/vr/arcore_device/arcore_device.cc
rename to device/vr/android/arcore/arcore_device.cc
index cab362d..37ab0d6 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_device.cc
+++ b/device/vr/android/arcore/arcore_device.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/android/vr/arcore_device/arcore_device.h"
+#include "device/vr/android/arcore/arcore_device.h"
 
 #include <algorithm>
 
@@ -11,16 +11,12 @@
 #include "base/optional.h"
 #include "base/task/post_task.h"
 #include "base/trace_event/trace_event.h"
-#include "chrome/browser/android/tab_android.h"
-#include "chrome/browser/android/vr/arcore_device/ar_image_transport.h"
-#include "chrome/browser/android/vr/arcore_device/arcore_gl.h"
-#include "chrome/browser/android/vr/arcore_device/arcore_gl_thread.h"
-#include "chrome/browser/android/vr/arcore_device/arcore_java_utils.h"
-#include "chrome/browser/android/vr/arcore_device/arcore_session_utils.h"
-#include "chrome/browser/android/vr/mailbox_to_surface_bridge.h"
-#include "chrome/browser/permissions/permission_update_infobar_delegate_android.h"
-#include "content/public/browser/render_frame_host.h"
+#include "device/vr/android/arcore/ar_image_transport.h"
+#include "device/vr/android/arcore/arcore_gl.h"
+#include "device/vr/android/arcore/arcore_gl_thread.h"
 #include "device/vr/android/arcore/arcore_impl.h"
+#include "device/vr/android/arcore/arcore_session_utils.h"
+#include "device/vr/android/mailbox_to_surface_bridge.h"
 #include "ui/display/display.h"
 
 using base::android::JavaRef;
@@ -64,14 +60,16 @@
 ArCoreDevice::ArCoreDevice(
     std::unique_ptr<ArCoreFactory> arcore_factory,
     std::unique_ptr<ArImageTransportFactory> ar_image_transport_factory,
-    std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_to_surface_bridge,
+    std::unique_ptr<MailboxToSurfaceBridgeFactory>
+        mailbox_to_surface_bridge_factory,
     std::unique_ptr<vr::ArCoreSessionUtils> arcore_session_utils)
     : VRDeviceBase(mojom::XRDeviceId::ARCORE_DEVICE_ID),
       main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
       arcore_factory_(std::move(arcore_factory)),
       ar_image_transport_factory_(std::move(ar_image_transport_factory)),
-      mailbox_bridge_(std::move(mailbox_to_surface_bridge)),
+      mailbox_bridge_factory_(std::move(mailbox_to_surface_bridge_factory)),
       arcore_session_utils_(std::move(arcore_session_utils)),
+      mailbox_bridge_(mailbox_bridge_factory_->Create()),
       session_state_(std::make_unique<ArCoreDevice::SessionState>()) {
   // Ensure display_info_ is set to avoid crash in CallDeferredSessionCallback
   // if initialization fails. Use an arbitrary but really low resolution to make
@@ -80,12 +78,6 @@
   SetVRDisplayInfo(CreateVRDisplayInfo({16, 16}));
 }
 
-ArCoreDevice::ArCoreDevice()
-    : ArCoreDevice(std::make_unique<ArCoreImplFactory>(),
-                   std::make_unique<ArImageTransportFactory>(),
-                   std::make_unique<vr::MailboxToSurfaceBridge>(),
-                   std::make_unique<vr::ArCoreJavaUtils>()) {}
-
 ArCoreDevice::~ArCoreDevice() {
   // If there's still a pending session request, reject it.
   CallDeferredRequestSessionCallback(/*success=*/false);
@@ -128,16 +120,6 @@
   bool use_dom_overlay = base::Contains(
       options->enabled_features, device::mojom::XRSessionFeature::DOM_OVERLAY);
 
-  if (use_dom_overlay) {
-    // Tell RenderFrameHostImpl that we're setting up the WebXR DOM Overlay,
-    // it checks for this in EnterFullscreen via HasSeenRecentXrOverlaySetup().
-    content::RenderFrameHost* render_frame_host =
-        content::RenderFrameHost::FromID(options->render_process_id,
-                                         options->render_frame_id);
-    DCHECK(render_frame_host);
-    render_frame_host->SetIsXrOverlaySetup();
-  }
-
   // mailbox_bridge_ is either supplied from the constructor, or recreated in
   // OnSessionEnded().
   DCHECK(mailbox_bridge_);
@@ -242,7 +224,7 @@
 
   // Create a new mailbox bridge for use in the next session. (This is cheap,
   // the constructor doesn't establish a GL context.)
-  mailbox_bridge_ = std::make_unique<vr::MailboxToSurfaceBridge>();
+  mailbox_bridge_ = mailbox_bridge_factory_->Create();
 
   // This sets HasExclusiveSession status to false.
   OnExitPresent();
@@ -294,6 +276,7 @@
   session->data_provider = std::move(frame_data_provider);
   session->display_info = std::move(display_info);
   session->submit_frame_sink = std::move(presentation_connection);
+  session->supports_viewport_scaling = true;
 
   std::move(deferred_callback)
       .Run(std::move(session), std::move(session_controller));
diff --git a/chrome/browser/android/vr/arcore_device/arcore_device.h b/device/vr/android/arcore/arcore_device.h
similarity index 90%
rename from chrome/browser/android/vr/arcore_device/arcore_device.h
rename to device/vr/android/arcore/arcore_device.h
index 4576de3..1e9858e1 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_device.h
+++ b/device/vr/android/arcore/arcore_device.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_DEVICE_H_
-#define CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_DEVICE_H_
+#ifndef DEVICE_VR_ANDROID_ARCORE_ARCORE_DEVICE_H_
+#define DEVICE_VR_ANDROID_ARCORE_ARCORE_DEVICE_H_
 
 #include <jni.h>
 #include <memory>
@@ -22,7 +22,6 @@
 #include "ui/gfx/native_widget_types.h"
 
 namespace vr {
-class MailboxToSurfaceBridge;
 class ArCoreSessionUtils;
 }  // namespace vr
 
@@ -31,15 +30,17 @@
 class ArImageTransportFactory;
 class ArCoreFactory;
 class ArCoreGlThread;
+class MailboxToSurfaceBridge;
+class MailboxToSurfaceBridgeFactory;
 
-class ArCoreDevice : public VRDeviceBase {
+class COMPONENT_EXPORT(VR_ARCORE) ArCoreDevice : public VRDeviceBase {
  public:
   ArCoreDevice(
       std::unique_ptr<ArCoreFactory> arcore_factory,
       std::unique_ptr<ArImageTransportFactory> ar_image_transport_factory,
-      std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_to_surface_bridge,
+      std::unique_ptr<MailboxToSurfaceBridgeFactory>
+          mailbox_to_surface_bridge_factory,
       std::unique_ptr<vr::ArCoreSessionUtils> arcore_session_utils);
-  ArCoreDevice();
   ~ArCoreDevice() override;
 
   // VRDeviceBase implementation.
@@ -112,9 +113,11 @@
   scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_;
   std::unique_ptr<ArCoreFactory> arcore_factory_;
   std::unique_ptr<ArImageTransportFactory> ar_image_transport_factory_;
-  std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_bridge_;
+  std::unique_ptr<MailboxToSurfaceBridgeFactory> mailbox_bridge_factory_;
   std::unique_ptr<vr::ArCoreSessionUtils> arcore_session_utils_;
 
+  std::unique_ptr<MailboxToSurfaceBridge> mailbox_bridge_;
+
   // Encapsulates data with session lifetime.
   struct SessionState {
     SessionState();
@@ -149,4 +152,4 @@
 
 }  // namespace device
 
-#endif  // CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_DEVICE_H_
+#endif  // DEVICE_VR_ANDROID_ARCORE_ARCORE_DEVICE_H_
diff --git a/chrome/browser/android/vr/arcore_device/arcore_gl.cc b/device/vr/android/arcore/arcore_gl.cc
similarity index 94%
rename from chrome/browser/android/vr/arcore_device/arcore_gl.cc
rename to device/vr/android/arcore/arcore_gl.cc
index 2c1a5c8..0002139 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_gl.cc
+++ b/device/vr/android/arcore/arcore_gl.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/android/vr/arcore_device/arcore_gl.h"
+#include "device/vr/android/arcore/arcore_gl.h"
 
 #include <algorithm>
 #include <iomanip>
@@ -20,12 +20,12 @@
 #include "base/time/time.h"
 #include "base/trace_event/trace_event.h"
 #include "base/trace_event/traced_value.h"
-#include "chrome/browser/android/vr/arcore_device/ar_image_transport.h"
-#include "chrome/browser/android/vr/arcore_device/arcore_session_utils.h"
-#include "chrome/browser/android/vr/web_xr_presentation_state.h"
+#include "device/vr/android/arcore/ar_image_transport.h"
 #include "device/vr/android/arcore/arcore.h"
 #include "device/vr/android/arcore/arcore_math_utils.h"
+#include "device/vr/android/arcore/arcore_session_utils.h"
 #include "device/vr/android/arcore/type_converters.h"
+#include "device/vr/android/web_xr_presentation_state.h"
 #include "device/vr/public/mojom/pose.h"
 #include "device/vr/public/mojom/vr_service.mojom.h"
 #include "gpu/ipc/common/gpu_memory_buffer_impl_android_hardware_buffer.h"
@@ -62,6 +62,22 @@
 const gfx::Size kDefaultFrameSize = {1, 1};
 const display::Display::Rotation kDefaultRotation = display::Display::ROTATE_0;
 
+gfx::Transform GetContentTransform(const gfx::RectF& bounds) {
+  // Calculate the transform matrix from quad coordinates (range 0..1 with
+  // origin at bottom left of the quad) to texture lookup UV coordinates (also
+  // range 0..1 with origin at bottom left), where the active viewport uses a
+  // subset of the texture range that needs to be magnified to fill the quad.
+  // The bounds as used by the UpdateLayerBounds mojo messages appear to use an
+  // old WebVR convention with origin at top left, so the Y range needs to be
+  // mirrored.
+  gfx::Transform transform;
+  transform.matrix().set(0, 0, bounds.width());
+  transform.matrix().set(1, 1, bounds.height());
+  transform.matrix().set(0, 3, bounds.x());
+  transform.matrix().set(1, 3, 1.f - bounds.y() - bounds.height());
+  return transform;
+}
+
 }  // namespace
 
 namespace device {
@@ -400,6 +416,7 @@
 
   vr::WebXrFrame* xrframe = webxr_->GetAnimatingFrame();
   xrframe->time_pose = now;
+  xrframe->bounds_left = viewport_bounds_;
 
   if (display_info_changed_) {
     frame_data->left_eye = display_info_->left_eye.Clone();
@@ -573,7 +590,13 @@
   DCHECK(webxr_->HaveProcessingFrame());
   DCHECK(!ar_image_transport_->UseSharedBuffer());
 
-  ar_image_transport_->CopyMailboxToSurfaceAndSwap(transfer_size_, mailbox);
+  // Use only the active bounds of the viewport, converting the
+  // bounds UV boundaries to a transform. See also OnWebXrTokenSignaled().
+  gfx::Transform transform =
+      GetContentTransform(webxr_->GetProcessingFrame()->bounds_left);
+  ar_image_transport_->CopyMailboxToSurfaceAndSwap(transfer_size_, mailbox,
+                                                   transform);
+
   // Notify the client that we're done with the mailbox so that the underlying
   // image is eligible for destruction.
   submit_client_->OnSubmitFrameTransferred(true);
@@ -661,9 +684,13 @@
   webxr_->GetProcessingFrame()->time_copied = base::TimeTicks::Now();
   webxr_->TransitionFrameProcessingToRendering();
 
+  // Use only the active bounds of the viewport, converting the
+  // bounds UV boundaries to a transform. See also ProcessFrameFromMailbox().
+  gfx::Transform transform =
+      GetContentTransform(webxr_->GetRenderingFrame()->bounds_left);
   glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, 0);
   ar_image_transport_->CopyDrawnImageToFramebuffer(
-      webxr_.get(), camera_image_size_, shared_buffer_transform_);
+      webxr_.get(), camera_image_size_, transform);
 
   FinishFrame(frame_index);
 
@@ -684,8 +711,22 @@
                                  const gfx::RectF& left_bounds,
                                  const gfx::RectF& right_bounds,
                                  const gfx::Size& source_size) {
-  DVLOG(2) << __func__ << " source_size=" << source_size.ToString();
+  DVLOG(2) << __func__ << " source_size=" << source_size.ToString()
+           << " left_bounds=" << left_bounds.ToString();
 
+  // The first UpdateLayerBounds may arrive early, when there's
+  // no animating frame yet. In that case, just save it in viewport_bounds_
+  // so that it's applied to the next animating frame.
+  if (webxr_->HaveAnimatingFrame()) {
+    // Handheld AR mode is monoscopic and only uses the left bounds.
+    webxr_->GetAnimatingFrame()->bounds_left = left_bounds;
+    (void)right_bounds;
+  }
+  viewport_bounds_ = left_bounds;
+
+  // Early setting of transfer_size_ is OK since that's only used by the
+  // animating frame. Processing/rendering frames use the bounds from
+  // WebXRPresentationState.
   transfer_size_ = source_size;
 }
 
diff --git a/chrome/browser/android/vr/arcore_device/arcore_gl.h b/device/vr/android/arcore/arcore_gl.h
similarity index 96%
rename from chrome/browser/android/vr/arcore_device/arcore_gl.h
rename to device/vr/android/arcore/arcore_gl.h
index 6704d70..71cd3af5 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_gl.h
+++ b/device/vr/android/arcore/arcore_gl.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_GL_H_
-#define CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_GL_H_
+#ifndef DEVICE_VR_ANDROID_ARCORE_ARCORE_GL_H_
+#define DEVICE_VR_ANDROID_ARCORE_ARCORE_GL_H_
 
 #include <memory>
 #include <unordered_set>
@@ -225,6 +225,11 @@
   // smaller than the camera image if framebufferScaleFactor is < 1.0.
   gfx::Size transfer_size_ = gfx::Size(0, 0);
 
+  // Viewport size to use for new animating frames. Currently in-flight
+  // processing/rendering frames continue using the viewport size stored
+  // in their WebXrFrame state.
+  gfx::RectF viewport_bounds_ = gfx::RectF(0.f, 0.f, 1.f, 1.f);
+
   // The camera image size stays locked to the screen size even if
   // framebufferScaleFactor changes.
   gfx::Size camera_image_size_ = gfx::Size(0, 0);
@@ -235,10 +240,6 @@
   // and can include 90 degree rotations or other nontrivial transforms.
   gfx::Transform uv_transform_;
 
-  // UV transform for drawing received WebGL content from a shared buffer's
-  // texture, this is simply an identity.
-  gfx::Transform shared_buffer_transform_;
-
   gfx::Transform projection_;
   gfx::Transform inverse_projection_;
   // The first run of ProduceFrame should set uv_transform_ and projection_
@@ -324,4 +325,4 @@
 
 }  // namespace device
 
-#endif  // CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_GL_H_
+#endif  // DEVICE_VR_ANDROID_ARCORE_ARCORE_GL_H_
diff --git a/chrome/browser/android/vr/arcore_device/arcore_gl_thread.cc b/device/vr/android/arcore/arcore_gl_thread.cc
similarity index 80%
rename from chrome/browser/android/vr/arcore_device/arcore_gl_thread.cc
rename to device/vr/android/arcore/arcore_gl_thread.cc
index 99c60201..cd04fcb 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_gl_thread.cc
+++ b/device/vr/android/arcore/arcore_gl_thread.cc
@@ -2,18 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/android/vr/arcore_device/arcore_gl_thread.h"
+#include "device/vr/android/arcore/arcore_gl_thread.h"
 
 #include <utility>
 #include "base/version.h"
-#include "chrome/browser/android/vr/arcore_device/ar_image_transport.h"
-#include "chrome/browser/android/vr/arcore_device/arcore_gl.h"
+#include "device/vr/android/arcore/ar_image_transport.h"
+#include "device/vr/android/arcore/arcore_gl.h"
 
 namespace device {
 
 ArCoreGlThread::ArCoreGlThread(
     std::unique_ptr<ArImageTransportFactory> ar_image_transport_factory,
-    std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_bridge,
+    std::unique_ptr<MailboxToSurfaceBridge> mailbox_bridge,
     base::OnceCallback<void()> initialized_callback)
     : base::android::JavaHandlerThread("ArCoreGL"),
       ar_image_transport_factory_(std::move(ar_image_transport_factory)),
diff --git a/chrome/browser/android/vr/arcore_device/arcore_gl_thread.h b/device/vr/android/arcore/arcore_gl_thread.h
similarity index 71%
rename from chrome/browser/android/vr/arcore_device/arcore_gl_thread.h
rename to device/vr/android/arcore/arcore_gl_thread.h
index 7e74423..a8a8578 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_gl_thread.h
+++ b/device/vr/android/arcore/arcore_gl_thread.h
@@ -2,26 +2,27 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_GL_THREAD_H_
-#define CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_GL_THREAD_H_
+#ifndef DEVICE_VR_ANDROID_ARCORE_ARCORE_GL_THREAD_H_
+#define DEVICE_VR_ANDROID_ARCORE_ARCORE_GL_THREAD_H_
 
 #include <memory>
 #include "base/android/java_handler_thread.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/single_thread_task_runner.h"
-#include "chrome/browser/android/vr/mailbox_to_surface_bridge.h"
+#include "device/vr/android/mailbox_to_surface_bridge.h"
 
 namespace device {
 
 class ArCoreGl;
 class ArImageTransportFactory;
+class MailboxToSurfaceBridge;
 
 class ArCoreGlThread : public base::android::JavaHandlerThread {
  public:
   ArCoreGlThread(
       std::unique_ptr<ArImageTransportFactory> ar_image_transport_factory,
-      std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_bridge,
+      std::unique_ptr<MailboxToSurfaceBridge> mailbox_bridge,
       base::OnceCallback<void()> initialized_callback);
   ~ArCoreGlThread() override;
   ArCoreGl* GetArCoreGl();
@@ -32,7 +33,7 @@
 
  private:
   std::unique_ptr<ArImageTransportFactory> ar_image_transport_factory_;
-  std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_bridge_;
+  std::unique_ptr<MailboxToSurfaceBridge> mailbox_bridge_;
   base::OnceCallback<void()> initialized_callback_;
 
   // Created on GL thread.
@@ -43,4 +44,4 @@
 
 }  // namespace device
 
-#endif  // CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_GL_THREAD_H_
+#endif  // DEVICE_VR_ANDROID_ARCORE_ARCORE_GL_THREAD_H_
diff --git a/chrome/browser/android/vr/arcore_device/arcore_session_utils.h b/device/vr/android/arcore/arcore_session_utils.h
similarity index 88%
rename from chrome/browser/android/vr/arcore_device/arcore_session_utils.h
rename to device/vr/android/arcore/arcore_session_utils.h
index e48c9080..d3c3f4d 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_session_utils.h
+++ b/device/vr/android/arcore/arcore_session_utils.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_SESSION_UTILS_H_
-#define CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_SESSION_UTILS_H_
+#ifndef DEVICE_VR_ANDROID_ARCORE_ARCORE_SESSION_UTILS_H_
+#define DEVICE_VR_ANDROID_ARCORE_ARCORE_SESSION_UTILS_H_
 
 #include "base/android/scoped_java_ref.h"
 #include "base/memory/weak_ptr.h"
@@ -51,4 +51,4 @@
 
 }  // namespace vr
 
-#endif  // CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_SESSION_UTILS_H_
+#endif  // DEVICE_VR_ANDROID_ARCORE_ARCORE_SESSION_UTILS_H_
diff --git a/device/vr/android/mailbox_to_surface_bridge.h b/device/vr/android/mailbox_to_surface_bridge.h
new file mode 100644
index 0000000..c7b123f
--- /dev/null
+++ b/device/vr/android/mailbox_to_surface_bridge.h
@@ -0,0 +1,101 @@
+// 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 DEVICE_VR_ANDROID_MAILBOX_TO_SURFACE_BRIDGE_H_
+#define DEVICE_VR_ANDROID_MAILBOX_TO_SURFACE_BRIDGE_H_
+
+namespace gfx {
+class ColorSpace;
+class GpuFence;
+class Transform;
+}  // namespace gfx
+
+namespace gl {
+class SurfaceTexture;
+}  // namespace gl
+
+namespace gpu {
+class GpuMemoryBufferImplAndroidHardwareBuffer;
+struct MailboxHolder;
+struct SyncToken;
+}  // namespace gpu
+
+namespace device {
+class MailboxToSurfaceBridge {
+ public:
+  virtual ~MailboxToSurfaceBridge() {}
+
+  // Returns true if the GPU process connection is established and ready to use.
+  // Equivalent to waiting for on_initialized to be called.
+  virtual bool IsConnected() = 0;
+
+  // Checks if a workaround from "gpu/config/gpu_driver_bug_workaround_type.h"
+  // is active. Requires initialization to be complete.
+  virtual bool IsGpuWorkaroundEnabled(int32_t workaround) = 0;
+
+  // This call is needed for Surface transport, in that case it must be called
+  // on the GL thread with a valid local native GL context. If it's not used,
+  // only the SharedBuffer transport methods are available.
+  virtual void CreateSurface(gl::SurfaceTexture*) = 0;
+
+  // Asynchronously create the context using the surface provided by an earlier
+  // CreateSurface call, or an offscreen context if that wasn't called. Also
+  // binds the context provider to the current thread (making it the GL thread),
+  // and calls the callback on the GL thread.
+  virtual void CreateAndBindContextProvider(base::OnceClosure callback) = 0;
+
+  // All other public methods below must be called on the GL thread
+  // (except when marked otherwise).
+
+  virtual void ResizeSurface(int width, int height) = 0;
+
+  // Returns true if swapped successfully. This can fail if the GL
+  // context isn't ready for use yet, in that case the caller
+  // won't get a new frame on the SurfaceTexture.
+  virtual bool CopyMailboxToSurfaceAndSwap(
+      const gpu::MailboxHolder& mailbox) = 0;
+  virtual bool CopyMailboxToSurfaceAndSwap(
+      const gpu::MailboxHolder& mailbox,
+      const gfx::Transform& uv_transform) = 0;
+
+  virtual void GenSyncToken(gpu::SyncToken* out_sync_token) = 0;
+
+  virtual void WaitSyncToken(const gpu::SyncToken& sync_token) = 0;
+
+  // Copies a GpuFence from the local context to the GPU process,
+  // and issues a server wait for it.
+  virtual void WaitForClientGpuFence(gfx::GpuFence*) = 0;
+
+  // Creates a GpuFence in the GPU process after the supplied sync_token
+  // completes, and copies it for use in the local context. This is
+  // asynchronous, the callback receives the GpuFence once it's available.
+  virtual void CreateGpuFence(
+      const gpu::SyncToken& sync_token,
+      base::OnceCallback<void(std::unique_ptr<gfx::GpuFence>)> callback) = 0;
+
+  // Creates a shared image bound to |buffer|. Returns a mailbox holder that
+  // references the shared image with a sync token representing a point after
+  // the creation. Caller must call DestroySharedImage to free the shared image.
+  // Does not take ownership of |buffer| or retain any references to it.
+  virtual gpu::MailboxHolder CreateSharedImage(
+      gpu::GpuMemoryBufferImplAndroidHardwareBuffer* buffer,
+      const gfx::ColorSpace& color_space,
+      uint32_t usage) = 0;
+
+  // Destroys a shared image created by CreateSharedImage. The mailbox_holder's
+  // sync_token must have been updated to a sync token after the last use of the
+  // shared image.
+  virtual void DestroySharedImage(const gpu::MailboxHolder& mailbox_holder) = 0;
+};
+
+class MailboxToSurfaceBridgeFactory {
+ public:
+  virtual ~MailboxToSurfaceBridgeFactory() {}
+
+  virtual std::unique_ptr<device::MailboxToSurfaceBridge> Create() const = 0;
+};
+
+}  // namespace device
+
+#endif  // DEVICE_VR_ANDROID_MAILBOX_TO_SURFACE_BRIDGE_H_
diff --git a/chrome/browser/android/vr/web_xr_presentation_state.cc b/device/vr/android/web_xr_presentation_state.cc
similarity index 98%
rename from chrome/browser/android/vr/web_xr_presentation_state.cc
rename to device/vr/android/web_xr_presentation_state.cc
index 52caa35..7c42a6d8 100644
--- a/chrome/browser/android/vr/web_xr_presentation_state.cc
+++ b/device/vr/android/web_xr_presentation_state.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/android/vr/web_xr_presentation_state.h"
+#include "device/vr/android/web_xr_presentation_state.h"
 
 #include "base/logging.h"
 #include "base/trace_event/trace_event.h"
diff --git a/chrome/browser/android/vr/web_xr_presentation_state.h b/device/vr/android/web_xr_presentation_state.h
similarity index 90%
rename from chrome/browser/android/vr/web_xr_presentation_state.h
rename to device/vr/android/web_xr_presentation_state.h
index c65fecb..dce959a 100644
--- a/chrome/browser/android/vr/web_xr_presentation_state.h
+++ b/device/vr/android/web_xr_presentation_state.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_ANDROID_VR_WEB_XR_PRESENTATION_STATE_H_
-#define CHROME_BROWSER_ANDROID_VR_WEB_XR_PRESENTATION_STATE_H_
+#ifndef DEVICE_VR_ANDROID_WEB_XR_PRESENTATION_STATE_H_
+#define DEVICE_VR_ANDROID_WEB_XR_PRESENTATION_STATE_H_
 
 #include <memory>
 #include <utility>
@@ -13,6 +13,7 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "gpu/command_buffer/common/mailbox_holder.h"
+#include "ui/gfx/geometry/rect_f.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/transform.h"
 
@@ -127,6 +128,16 @@
 
   std::unique_ptr<WebXrSharedBuffer> camera_image_shared_buffer;
 
+  // Viewport bounds used for rendering, in texture coordinates with uv=(0, 1)
+  // corresponding to viewport pixel (0, 0) as set by UpdateLayerBounds.
+  //
+  // Currently this is only used by the ARCore handheld AR mode which is
+  // monoscopic and uses the left viewport. TODO(https://crbug.com/1134203): The
+  // GVR device currently has its own separate bounds tracking implementation.
+  // That should be updated to use this implementation, at that time a matching
+  // bounds_right would need to be added.
+  gfx::RectF bounds_left;
+
   DISALLOW_COPY_AND_ASSIGN(WebXrFrame);
 };
 
@@ -214,4 +225,4 @@
 
 }  // namespace vr
 
-#endif  // CHROME_BROWSER_ANDROID_VR_WEB_XR_PRESENTATION_STATE_H_
+#endif  // DEVICE_VR_ANDROID_WEB_XR_PRESENTATION_STATE_H_
diff --git a/docs/accessibility/ia2_to_uia.md b/docs/accessibility/ia2_to_uia.md
new file mode 100644
index 0000000..5dffbad
--- /dev/null
+++ b/docs/accessibility/ia2_to_uia.md
@@ -0,0 +1,139 @@
+# Runtime two way IAccessible2 to UI Automation elements look up via unique id
+Assistive technologies (ATs) who currently rely on IAccessible2 (IA2) that want
+to take advantage of the UI Automation (UIA) features at runtime can convert an
+IA2 element to an UIA element via a unique id and directly access UIA's API.
+This enables ATs who want to gradually transition from IA2 to UIA to experiment
+with individual UIA elements at runtime without switching entirely to UIA.
+
+
+To look up an UIA element through a unique id, an AT can utilize
+`IUIAutomationItemContainerPattern::FindItemByProperty()` with the custom UIA
+unique id property and the element's unique id as parameters.
+To look up an IA2 element from an UIA element, an AT can simply
+utilize IUIAutomationLegacyIAccessiblePattern::GetIAccessible() and
+then query for IAccessible2 interface. The unique id is not needed to
+look up the IA2 element from UIA element.
+
+## Convert an IA2 element to UIA element via unique id
+An IA2 element can be converted to an UIA element at runtime via a unique id
+that is shared between the two APIs.
+
+*Note: For the purpose of brevity and clarity, the code snippets below do not
+include clean-up of COM references neither does it have error handling.*
+
+~~~c++
+  #include <uiautomation.h>
+  #include <uiautomationclient.h>
+
+  // Consider the following HTML:
+  // <html>
+  //  <button>button</button>
+  // </html>
+
+  // Register custom UIA property for retrieving the unique id of IA2 object.
+  // {cc7eeb32-4b62-4f4c-aff6-1c2e5752ad8e}
+  GUID UiaPropertyUniqueIdGuid = {
+      0xcc7eeb32,
+      0x4b62,
+      0x4f4c,
+      {0xaf, 0xf6, 0x1c, 0x2e, 0x57, 0x52, 0xad, 0x8e}};
+
+  // Create the registrar object and get the IUIAutomationRegistrar
+  // interface pointer.
+  IUIAutomationRegistrar* registrar;
+  CoCreateInstance(CLSID_CUIAutomationRegistrar, nullptr, CLSCTX_INPROC_SERVER,
+                   IID_PPV_ARGS(&registrar));
+
+  // Custom UIA property id used to retrieve the unique id between UIA/IA2.
+  PROPERTYID uia_unique_id_property_id;
+
+  // Register the custom UIA property that represents the unique id of an UIA
+  // element which also matches its corresponding IA2 element's unique id.
+  // Custom property registration only needs to be done once per process
+  // lifetime.
+  UIAutomationPropertyInfo unique_id_property_info = {
+      UiaPropertyUniqueIdGuid, L"UniqueId", UIAutomationType_String};
+  registrar->RegisterProperty(&unique_id_property_info,
+                              &uia_unique_id_property_id);
+
+  // Assume we are given the IAccessible2 element for button, and we want to
+  // retrieve its corresponding UIA element for the final result.
+  IAccessible2* button_ia2; /* Initialized */
+
+  // Retrieve button IA2 element's unique id, which will be used to look up the
+  // corresponding UIA element later.
+  LONG unique_id_long;
+  button_ia2->get_uniqueID(&unique_id_long);
+
+  // Assume we are given the Window Handle hwnd for the root.
+  UIA_HWND hwnd; /* Initialized */
+
+  // Instantiating an IUIAutomation object.
+  IUIAutomation* ui_automation;
+  CoCreateInstance(CLSID_CUIAutomation, NULL, CLSCTX_INPROC_SERVER,
+                   IID_PPV_ARGS(&ui_automation));
+
+  // Retrieve the root element from the window handle.
+  IUIAutomationElement* root_element;
+  ui_automation->ElementFromHandle(hwnd, &root_element);
+
+  // Retrieve the ItemContainerPattern of the root element.
+  IUIAutomationItemContainerPattern* item_container_pattern;
+  root_element->GetCurrentPatternAs(UIA_ItemContainerPatternId,
+                                    IID_PPV_ARGS(&item_container_pattern));
+
+  // We also need to convert the retrieved IA2 element unique id from long to
+  // VARIANT.VT_BSTR to be consumed by UIA. For demo purpose, I utilize
+  // std::string here as an intermediary step to convert to VARIANT.VT_BSTR.
+  std::string unique_id_str = std::to_string(unique_id_long);
+
+  VARIANT unique_id_variant;
+  unique_id_variant.vt = VT_BSTR;
+  unique_id_variant.bstrVal = SysAllocString(unique_id_str.c_str());
+
+  // Retrieving the corresponding UIAutomation element from the unique id of IA2
+  // object.
+  IUIAutomationElement* button_uia;
+  item_container_pattern->FindItemByProperty(nullptr, uia_unique_id_property_id,
+                                             unique_id_variant,
+                                             &button_uia /* final result */);
+~~~
+
+## Convert an UIA element to IA2 element.
+Converting an UIA element to an IA2 element is a lot more straightforward and
+does not require the shared unique id. Consider the same example above.
+~~~c++
+  #include <uiautomationclient.h>
+
+  // Assume we are given the UIAutomation element for button, and we want to
+  // retrieve its corresponding IAccessible2 element for the final result.
+  IUIAutomationElement* button_uia; /* Initialized */
+
+  // Retrieve the LegacyIAccessiblePattern of the button UIA element.
+  IUIAutomationLegacyIAccessiblePattern* legacy_iaccessible_pattern;
+  legacy_iaccessible_pattern->GetCurrentPatternAs(
+      UIA_LegacyIAccessiblePatternId,
+      IID_PPV_ARGS(&legacy_iaccessible_pattern));
+
+  // Retrieve the IAccessible element from button UIA element.
+  IAccessible* button_iaccessible;
+  legacy_iaccessible_pattern->GetIAccessible(&iaccessible);
+
+  // Use QueryService to retrieve button's IAccessible2 element from IAccessible
+  // element.
+  IServiceProvider* service_provider;
+  IAccessible2* button_ia2;
+  if (SUCCEEDED(button_iaccessible->QueryInterface(
+          IID_PPV_ARGS(&service_provider)))) {
+    service_provider->QueryService(
+        IID_PPV_ARGS(&button_ia2 /* final result */));
+  }
+~~~
+
+## Docs & References:
+[Custom UIA Property and Pattern registration in Chromium](https://chromium.googlesource.com/chromium/src/+/master/ui/accessibility/platform/uia_registrar_win.h)
+
+[UI Automation IItemContainerPattern. It is used to look up IAccessible2 element
+via a unique id](https://docs.microsoft.com/en-us/windows/win32/api/uiautomationclient/nn-uiautomationclient-iuiautomationitemcontainerpattern)
+
+[UI Automation Client](https://docs.microsoft.com/en-us/windows/win32/api/uiautomationclient/)
diff --git a/extensions/browser/api/declarative_net_request/action_tracker.h b/extensions/browser/api/declarative_net_request/action_tracker.h
index aeb7e06..4c310ca 100644
--- a/extensions/browser/api/declarative_net_request/action_tracker.h
+++ b/extensions/browser/api/declarative_net_request/action_tracker.h
@@ -61,8 +61,9 @@
                      const WebRequestInfo& request_info);
 
   // Updates the action count for all tabs for the specified |extension_id|'s
-  // extension action. Called when chrome.setActionCountAsBadgeText(true) is
-  // called by an extension.
+  // extension action. Called when the extension calls setExtensionActionOptions
+  // to enable setting the action count as badge text.
+  // TODO(karandeepb): Rename to OnActionCountAsBadgeTextPreferenceEnabled.
   void OnPreferenceEnabled(const ExtensionId& extension_id) const;
 
   // Clears the TrackedInfo for the specified |extension_id| for all tabs.
diff --git a/extensions/browser/api/declarative_net_request/declarative_net_request_api.cc b/extensions/browser/api/declarative_net_request/declarative_net_request_api.cc
index 69069b9..a8f7caf 100644
--- a/extensions/browser/api/declarative_net_request/declarative_net_request_api.cc
+++ b/extensions/browser/api/declarative_net_request/declarative_net_request_api.cc
@@ -332,31 +332,35 @@
   return user_gesture() || disable_throttling_for_test_;
 }
 
-DeclarativeNetRequestSetActionCountAsBadgeTextFunction::
-    DeclarativeNetRequestSetActionCountAsBadgeTextFunction() = default;
-DeclarativeNetRequestSetActionCountAsBadgeTextFunction::
-    ~DeclarativeNetRequestSetActionCountAsBadgeTextFunction() = default;
+DeclarativeNetRequestSetExtensionActionOptionsFunction::
+    DeclarativeNetRequestSetExtensionActionOptionsFunction() = default;
+DeclarativeNetRequestSetExtensionActionOptionsFunction::
+    ~DeclarativeNetRequestSetExtensionActionOptionsFunction() = default;
 
 ExtensionFunction::ResponseAction
-DeclarativeNetRequestSetActionCountAsBadgeTextFunction::Run() {
-  using Params = dnr_api::SetActionCountAsBadgeText::Params;
+DeclarativeNetRequestSetExtensionActionOptionsFunction::Run() {
+  using Params = dnr_api::SetExtensionActionOptions::Params;
 
   base::string16 error;
   std::unique_ptr<Params> params(Params::Create(*args_, &error));
   EXTENSION_FUNCTION_VALIDATE(params);
   EXTENSION_FUNCTION_VALIDATE(error.empty());
 
+  bool use_action_count_as_badge_text =
+      params->options.display_action_count_as_badge_text;
   ExtensionPrefs* prefs = ExtensionPrefs::Get(browser_context());
-  if (params->enable == prefs->GetDNRUseActionCountAsBadgeText(extension_id()))
+  if (use_action_count_as_badge_text ==
+      prefs->GetDNRUseActionCountAsBadgeText(extension_id()))
     return RespondNow(NoArguments());
 
-  prefs->SetDNRUseActionCountAsBadgeText(extension_id(), params->enable);
+  prefs->SetDNRUseActionCountAsBadgeText(extension_id(),
+                                         use_action_count_as_badge_text);
 
   // If the preference is switched on, update the extension's badge text with
   // the number of actions matched for this extension. Otherwise, clear the
   // action count for the extension's icon and show the default badge text if
   // set.
-  if (params->enable) {
+  if (use_action_count_as_badge_text) {
     declarative_net_request::RulesMonitorService* rules_monitor_service =
         declarative_net_request::RulesMonitorService::Get(browser_context());
     DCHECK(rules_monitor_service);
diff --git a/extensions/browser/api/declarative_net_request/declarative_net_request_api.h b/extensions/browser/api/declarative_net_request/declarative_net_request_api.h
index b63b5b2..496d9dc 100644
--- a/extensions/browser/api/declarative_net_request/declarative_net_request_api.h
+++ b/extensions/browser/api/declarative_net_request/declarative_net_request_api.h
@@ -105,15 +105,15 @@
   static bool disable_throttling_for_test_;
 };
 
-class DeclarativeNetRequestSetActionCountAsBadgeTextFunction
+class DeclarativeNetRequestSetExtensionActionOptionsFunction
     : public ExtensionFunction {
  public:
-  DeclarativeNetRequestSetActionCountAsBadgeTextFunction();
-  DECLARE_EXTENSION_FUNCTION("declarativeNetRequest.setActionCountAsBadgeText",
+  DeclarativeNetRequestSetExtensionActionOptionsFunction();
+  DECLARE_EXTENSION_FUNCTION("declarativeNetRequest.setExtensionActionOptions",
                              DECLARATIVENETREQUEST_SETACTIONCOUNTASBADGETEXT)
 
  protected:
-  ~DeclarativeNetRequestSetActionCountAsBadgeTextFunction() override;
+  ~DeclarativeNetRequestSetExtensionActionOptionsFunction() override;
 
   ExtensionFunction::ResponseAction Run() override;
 };
diff --git a/extensions/browser/extension_action.h b/extensions/browser/extension_action.h
index cda369b8..1e38d63f 100644
--- a/extensions/browser/extension_action.h
+++ b/extensions/browser/extension_action.h
@@ -308,7 +308,7 @@
 
   // Maps tab_id to the number of actions taken based on declarative net request
   // rule matches on incoming requests. Overrides the default |badge_text_| for
-  // this extension if it has called chrome.setActionCountAsBadgeText(true).
+  // this extension if it has opted into setting the action count as badge text.
   std::map<int, int> dnr_action_count_;
 
   // ExtensionIconSet containing paths to bitmaps from which default icon's
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index 9cbd5bdd..5f3e5a5 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1573,6 +1573,7 @@
   ACCESSIBILITY_PRIVATE_MOVEMAGNIFIERTORECT = 1510,
   FILEMANAGERPRIVATE_SINGLEPARTITIONFORMAT = 1511,
   TABS_REMOVECSS = 1512,
+  IDENTITY_CLEARALLCACHEDAUTHTOKENS = 1513,
   // Last entry: Add new entries above, then run:
   // python tools/metrics/histograms/update_extension_histograms.py
   ENUM_BOUNDARY
diff --git a/extensions/browser/extension_prefs.h b/extensions/browser/extension_prefs.h
index a708acc5..abb2ab6 100644
--- a/extensions/browser/extension_prefs.h
+++ b/extensions/browser/extension_prefs.h
@@ -671,7 +671,7 @@
 
   // Whether the extension with the given |extension_id| is using its ruleset's
   // matched action count for the badge text. This is set via the
-  // setActionCountAsBadgeText API call.
+  // setExtensionActionOptions API call.
   bool GetDNRUseActionCountAsBadgeText(const ExtensionId& extension_id) const;
   void SetDNRUseActionCountAsBadgeText(const ExtensionId& extension_id,
                                        bool use_action_count_as_badge_text);
diff --git a/extensions/common/api/declarative_net_request.idl b/extensions/common/api/declarative_net_request.idl
index 76fe5b61..b736b68 100644
--- a/extensions/common/api/declarative_net_request.idl
+++ b/extensions/common/api/declarative_net_request.idl
@@ -428,6 +428,12 @@
     DOMString[]? enableRulesetIds;
   };
 
+  dictionary ExtensionActionOptions {
+    // Whether to automatically display the action count for a page as the
+    // extension's badge text. False by default.
+    boolean displayActionCountAsBadgeText;
+  };
+
   callback EmptyCallback = void();
   callback GetAllowedPagesCallback = void(DOMString[] result);
   callback GetRulesCallback = void(Rule[] rules);
@@ -500,10 +506,10 @@
     static void getMatchedRules(optional MatchedRulesFilter filter,
                                 GetMatchedRulesCallback callback);
 
-    // Sets whether to automatically badge extension's icon to the matched
-    // action count for a tab. This preference is persisted across sessions and
-    // is false by default.
-    static void setActionCountAsBadgeText(boolean enable);
+    // Configures how matched actions will be displayed on the extension action.
+    // This preference is persisted across sessions.
+    static void setExtensionActionOptions(
+        ExtensionActionOptions options);
 
     // Checks if the given regular expression will be supported as a
     // <code>regexFilter</code> rule condition.
diff --git a/extensions/renderer/api_activity_logger.cc b/extensions/renderer/api_activity_logger.cc
index e4c7fc1..d7616ff2 100644
--- a/extensions/renderer/api_activity_logger.cc
+++ b/extensions/renderer/api_activity_logger.cc
@@ -56,24 +56,28 @@
 
   ScriptContext* script_context =
       ScriptContextSet::GetContextByV8Context(context);
-  auto value_args = std::make_unique<base::ListValue>();
   std::unique_ptr<content::V8ValueConverter> converter =
       content::V8ValueConverter::Create();
   ActivityLogConverterStrategy strategy;
   converter->SetFunctionAllowed(true);
   converter->SetStrategy(&strategy);
-  value_args->Reserve(arguments.size());
+
+  base::Value::ListStorage value_args;
+  value_args.reserve(arguments.size());
   // TODO(devlin): This doesn't protect against custom properties, so it might
   // not perfectly reflect the passed arguments.
   for (const auto& arg : arguments) {
     std::unique_ptr<base::Value> converted_arg =
         converter->FromV8Value(arg, context);
-    value_args->Append(converted_arg ? std::move(converted_arg)
-                                     : std::make_unique<base::Value>());
+    if (!converted_arg)
+      converted_arg = std::make_unique<base::Value>();
+    value_args.push_back(
+        base::Value::FromUniquePtrValue(std::move(converted_arg)));
   }
 
   LogInternal(APICALL, script_context->GetExtensionID(), call_name,
-              std::move(value_args), std::string());
+              std::make_unique<base::ListValue>(std::move(value_args)),
+              std::string());
 }
 
 void APIActivityLogger::LogEvent(ScriptContext* script_context,
@@ -114,10 +118,10 @@
   }
 
   // Get the array of call arguments.
-  auto arguments = std::make_unique<base::ListValue>();
+  base::Value::ListStorage arguments;
   v8::Local<v8::Array> arg_array = v8::Local<v8::Array>::Cast(args[2]);
   if (arg_array->Length() > 0) {
-    arguments->Reserve(arg_array->Length());
+    arguments.reserve(arg_array->Length());
     std::unique_ptr<content::V8ValueConverter> converter =
         content::V8ValueConverter::Create();
     ActivityLogConverterStrategy strategy;
@@ -128,12 +132,15 @@
       // actual error handling.
       std::unique_ptr<base::Value> converted_arg = converter->FromV8Value(
           arg_array->Get(context, i).ToLocalChecked(), context);
-      arguments->Append(converted_arg ? std::move(converted_arg)
-                                      : std::make_unique<base::Value>());
+      if (!converted_arg)
+        converted_arg = std::make_unique<base::Value>();
+      arguments.push_back(
+          base::Value::FromUniquePtrValue(std::move(converted_arg)));
     }
   }
 
-  LogInternal(call_type, extension_id, call_name, std::move(arguments), extra);
+  LogInternal(call_type, extension_id, call_name,
+              std::make_unique<base::ListValue>(std::move(arguments)), extra);
 }
 
 // static
diff --git a/extensions/renderer/bindings/api_signature.cc b/extensions/renderer/bindings/api_signature.cc
index 900215b..9d23e3cd 100644
--- a/extensions/renderer/bindings/api_signature.cc
+++ b/extensions/renderer/bindings/api_signature.cc
@@ -500,8 +500,8 @@
       callback = value.As<v8::Function>();
   }
 
-  auto json = std::make_unique<base::ListValue>();
-  json->Reserve(size);
+  base::Value::ListStorage json;
+  json.reserve(size);
 
   std::unique_ptr<content::V8ValueConverter> converter =
       content::V8ValueConverter::Create();
@@ -520,11 +520,11 @@
       // null). Duplicate that behavior here.
       converted = std::make_unique<base::Value>();
     }
-    json->Append(std::move(converted));
+    json.push_back(base::Value::FromUniquePtrValue(std::move(converted)));
   }
 
   JSONParseResult result;
-  result.arguments = std::move(json);
+  result.arguments = std::make_unique<base::ListValue>(std::move(json));
   result.callback = callback;
   result.async_type = callback.IsEmpty()
                           ? binding::AsyncResponseType::kNone
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.h b/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.h
index 0d10395..c66613b 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.h
+++ b/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.h
@@ -22,6 +22,10 @@
 class ColorSpace;
 }  // namespace gfx
 
+namespace gl {
+class ProgressReporter;
+}  // namespace gl
+
 namespace gpu {
 class SharedImageBacking;
 class SharedImageBatchAccessManager;
@@ -43,7 +47,8 @@
       const GpuDriverBugWorkarounds& workarounds,
       const GpuFeatureInfo& gpu_feature_info,
       ImageFactory* image_factory,
-      SharedImageBatchAccessManager* batch_access_manager);
+      SharedImageBatchAccessManager* batch_access_manager,
+      gl::ProgressReporter* progress_reporter);
   ~SharedImageBackingFactoryGLTexture() override;
 
   // SharedImageBackingFactory implementation.
@@ -170,6 +175,10 @@
   SharedImageBackingGLCommon::UnpackStateAttribs attribs;
   GpuDriverBugWorkarounds workarounds_;
 
+  // Used to notify the watchdog before a buffer allocation in case it takes
+  // long.
+  gl::ProgressReporter* const progress_reporter_ = nullptr;
+
 #if defined(OS_ANDROID)
   SharedImageBatchAccessManager* batch_access_manager_ = nullptr;
 #endif
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_gl_texture_unittest.cc b/gpu/command_buffer/service/shared_image_backing_factory_gl_texture_unittest.cc
index 0fbb0f60..276317a3f 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_gl_texture_unittest.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_gl_texture_unittest.cc
@@ -29,6 +29,7 @@
 #include "gpu/config/gpu_feature_info.h"
 #include "gpu/config/gpu_preferences.h"
 #include "gpu/config/gpu_test_config.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/core/SkPromiseImageTexture.h"
 #include "third_party/skia/include/core/SkSurface.h"
@@ -44,6 +45,9 @@
 #include "ui/gl/gl_image_stub.h"
 #include "ui/gl/gl_surface.h"
 #include "ui/gl/init/gl_factory.h"
+#include "ui/gl/progress_reporter.h"
+
+using testing::AtLeast;
 
 namespace gpu {
 namespace {
@@ -79,6 +83,15 @@
 #endif
 }
 
+class MockProgressReporter : public gl::ProgressReporter {
+ public:
+  MockProgressReporter() = default;
+  ~MockProgressReporter() override = default;
+
+  // gl::ProgressReporter implementation.
+  MOCK_METHOD0(ReportProgress, void());
+};
+
 class SharedImageBackingFactoryGLTextureTestBase
     : public testing::TestWithParam<std::tuple<bool, viz::ResourceFormat>> {
  public:
@@ -105,7 +118,7 @@
     preferences.use_passthrough_cmd_decoder = use_passthrough();
     backing_factory_ = std::make_unique<SharedImageBackingFactoryGLTexture>(
         preferences, workarounds, GpuFeatureInfo(), factory,
-        shared_image_manager_->batch_access_manager());
+        shared_image_manager_->batch_access_manager(), &progress_reporter_);
 
     memory_type_tracker_ = std::make_unique<MemoryTypeTracker>(nullptr);
     shared_image_representation_factory_ =
@@ -118,11 +131,31 @@
            gles2::PassthroughCommandDecoderSupported();
   }
 
+  bool can_create_non_scanout_shared_image(viz::ResourceFormat format) const {
+    if (format == viz::ResourceFormat::BGRA_1010102 ||
+        format == viz::ResourceFormat::RGBA_1010102) {
+      return supports_ar30_ || supports_ab30_;
+    } else if (format == viz::ResourceFormat::ETC1) {
+      return supports_etc1_;
+    }
+    return true;
+  }
+
+  bool can_create_scanout_or_gmb_shared_image(
+      viz::ResourceFormat format) const {
+    if (format == viz::ResourceFormat::BGRA_1010102)
+      return supports_ar30_;
+    else if (format == viz::ResourceFormat::RGBA_1010102)
+      return supports_ab30_;
+    return true;
+  }
+
   viz::ResourceFormat get_format() { return std::get<1>(GetParam()); }
 
   GrDirectContext* gr_context() { return context_state_->gr_context(); }
 
  protected:
+  ::testing::NiceMock<MockProgressReporter> progress_reporter_;
   scoped_refptr<gl::GLSurface> surface_;
   scoped_refptr<gl::GLContext> context_;
   scoped_refptr<SharedContextState> context_state_;
@@ -213,6 +246,9 @@
     return;
   }
 
+  const bool should_succeed = can_create_non_scanout_shared_image(get_format());
+  if (should_succeed)
+    EXPECT_CALL(progress_reporter_, ReportProgress).Times(AtLeast(1));
   auto mailbox = Mailbox::GenerateForSharedImage();
   auto format = get_format();
   gfx::Size size(256, 256);
@@ -225,12 +261,7 @@
       mailbox, format, surface_handle, size, color_space, surface_origin,
       alpha_type, usage, false /* is_thread_safe */);
 
-  // As long as either |chromium_image_ar30| or |chromium_image_ab30| is
-  // enabled, we can create a non-scanout SharedImage with format
-  // viz::ResourceFormat::{BGRA,RGBA}_1010102.
-  if ((format == viz::ResourceFormat::BGRA_1010102 ||
-       format == viz::ResourceFormat::RGBA_1010102) &&
-      !supports_ar30_ && !supports_ab30_) {
+  if (!should_succeed) {
     EXPECT_FALSE(backing);
     return;
   }
@@ -347,6 +378,11 @@
       bot_config.Matches("mac passthrough")) {
     return;
   }
+
+  const bool should_succeed =
+      can_create_scanout_or_gmb_shared_image(get_format());
+  if (should_succeed)
+    EXPECT_CALL(progress_reporter_, ReportProgress).Times(AtLeast(1));
   auto mailbox = Mailbox::GenerateForSharedImage();
   auto format = get_format();
   gfx::Size size(256, 256);
@@ -359,15 +395,12 @@
       mailbox, format, surface_handle, size, color_space, surface_origin,
       alpha_type, usage, false /* is_thread_safe */);
 
-  // We can only create a scanout SharedImage with format
-  // viz::ResourceFormat::{BGRA,RGBA}_1010102 if the corresponding
-  // |chromium_image_ar30| or |chromium_image_ab30| is enabled.
-  if ((format == viz::ResourceFormat::BGRA_1010102 && !supports_ar30_) ||
-      (format == viz::ResourceFormat::RGBA_1010102 && !supports_ab30_)) {
+  if (!should_succeed) {
     EXPECT_FALSE(backing);
     return;
   }
   ASSERT_TRUE(backing);
+  ::testing::Mock::VerifyAndClearExpectations(&progress_reporter_);
 
   // Check clearing.
   if (!backing->IsCleared()) {
@@ -472,6 +505,7 @@
 
   if (!use_passthrough() &&
       context_state_->feature_info()->feature_flags().ext_texture_rg) {
+    EXPECT_CALL(progress_reporter_, ReportProgress).Times(AtLeast(1));
     // Create a R-8 image texture, and check that the internal_format is that
     // of the image (GL_RGBA for TextureImageFactory). This only matters for
     // the validating decoder.
@@ -503,6 +537,9 @@
   for (auto format :
        {viz::ResourceFormat::RGBA_8888, viz::ResourceFormat::ETC1,
         viz::ResourceFormat::BGRA_1010102, viz::ResourceFormat::RGBA_1010102}) {
+    const bool should_succeed = can_create_non_scanout_shared_image(format);
+    if (should_succeed)
+      EXPECT_CALL(progress_reporter_, ReportProgress).Times(AtLeast(1));
     auto mailbox = Mailbox::GenerateForSharedImage();
     gfx::Size size(256, 256);
     auto color_space = gfx::ColorSpace::CreateSRGB();
@@ -514,22 +551,11 @@
     auto backing = backing_factory_->CreateSharedImage(
         mailbox, format, size, color_space, surface_origin, alpha_type, usage,
         initial_data);
-
-    if (format == viz::ResourceFormat::ETC1 && !supports_etc1_) {
+    ::testing::Mock::VerifyAndClearExpectations(&progress_reporter_);
+    if (!should_succeed) {
       EXPECT_FALSE(backing);
       continue;
     }
-
-    // As long as either |chromium_image_ar30| or |chromium_image_ab30| is
-    // enabled, we can create a non-scanout SharedImage with format
-    // viz::ResourceFormat::{BGRA,RGBA}_1010102.
-    if ((format == viz::ResourceFormat::BGRA_1010102 ||
-         format == viz::ResourceFormat::RGBA_1010102) &&
-        !supports_ar30_ && !supports_ab30_) {
-      EXPECT_FALSE(backing);
-      continue;
-    }
-
     ASSERT_TRUE(backing);
     EXPECT_TRUE(backing->IsCleared());
 
@@ -571,6 +597,10 @@
 }
 
 TEST_P(SharedImageBackingFactoryGLTextureTest, InitialDataImage) {
+  const bool should_succeed =
+      can_create_scanout_or_gmb_shared_image(get_format());
+  if (should_succeed)
+    EXPECT_CALL(progress_reporter_, ReportProgress).Times(AtLeast(1));
   auto mailbox = Mailbox::GenerateForSharedImage();
   auto format = get_format();
   gfx::Size size(256, 256);
@@ -582,12 +612,7 @@
   auto backing = backing_factory_->CreateSharedImage(
       mailbox, format, size, color_space, surface_origin, alpha_type, usage,
       initial_data);
-
-  // We can only create a scanout SharedImage with format
-  // viz::ResourceFormat::{BGRA,RGBA}_1010102 if the corresponding
-  // |chromium_image_ar30| or |chromium_image_ab30| is enabled.
-  if ((format == viz::ResourceFormat::BGRA_1010102 && !supports_ar30_) ||
-      (format == viz::ResourceFormat::RGBA_1010102 && !supports_ab30_)) {
+  if (!should_succeed) {
     EXPECT_FALSE(backing);
     return;
   }
@@ -679,6 +704,9 @@
 }
 
 TEST_P(SharedImageBackingFactoryGLTextureTest, EstimatedSize) {
+  const bool should_succeed = can_create_non_scanout_shared_image(get_format());
+  if (should_succeed)
+    EXPECT_CALL(progress_reporter_, ReportProgress).Times(AtLeast(1));
   auto mailbox = Mailbox::GenerateForSharedImage();
   auto format = get_format();
   gfx::Size size(256, 256);
@@ -691,12 +719,7 @@
       mailbox, format, surface_handle, size, color_space, surface_origin,
       alpha_type, usage, false /* is_thread_safe */);
 
-  // As long as either |chromium_image_ar30| or |chromium_image_ab30| is
-  // enabled, we can create a non-scanout SharedImage with format
-  // viz::ResourceFormat::{BGRA,RGBA}_1010102.
-  if ((format == viz::ResourceFormat::BGRA_1010102 ||
-       format == viz::ResourceFormat::RGBA_1010102) &&
-      !supports_ar30_ && !supports_ab30_) {
+  if (!should_succeed) {
     EXPECT_FALSE(backing);
     return;
   }
@@ -891,12 +914,7 @@
   auto backing = backing_factory_->CreateSharedImage(
       mailbox, kClientId, std::move(handle), format, kNullSurfaceHandle, size,
       color_space, surface_origin, alpha_type, usage);
-
-  // We can only create a GMB SharedImage with format
-  // viz::ResourceFormat::{BGRA,RGBA}_1010102 if the corresponding
-  // |chromium_image_ar30| or |chromium_image_ab30| is enabled.
-  if ((get_format() == viz::ResourceFormat::BGRA_1010102 && !supports_ar30_) ||
-      (get_format() == viz::ResourceFormat::RGBA_1010102 && !supports_ab30_)) {
+  if (!can_create_scanout_or_gmb_shared_image(get_format())) {
     EXPECT_FALSE(backing);
     return;
   }
@@ -952,12 +970,7 @@
   auto backing = backing_factory_->CreateSharedImage(
       mailbox, kClientId, std::move(handle), format, kNullSurfaceHandle, size,
       color_space, surface_origin, alpha_type, usage);
-
-  // We can only create a GMB SharedImage with format
-  // viz::ResourceFormat::{BGRA,RGBA}_1010102 if the corresponding
-  // |chromium_image_ar30| or |chromium_image_ab30| is enabled.
-  if ((get_format() == viz::ResourceFormat::BGRA_1010102 && !supports_ar30_) ||
-      (get_format() == viz::ResourceFormat::RGBA_1010102 && !supports_ab30_)) {
+  if (!can_create_scanout_or_gmb_shared_image(get_format())) {
     EXPECT_FALSE(backing);
     return;
   }
@@ -990,12 +1003,7 @@
   auto backing = backing_factory_->CreateSharedImage(
       mailbox, kClientId, std::move(handle), format, kNullSurfaceHandle, size,
       color_space, surface_origin, alpha_type, usage);
-
-  // We can only create a GMB SharedImage with format
-  // viz::ResourceFormat::{BGRA,RGBA}_1010102 if the corresponding
-  // |chromium_image_ar30| or |chromium_image_ab30| is enabled.
-  if ((get_format() == viz::ResourceFormat::BGRA_1010102 && !supports_ar30_) ||
-      (get_format() == viz::ResourceFormat::RGBA_1010102 && !supports_ab30_)) {
+  if (!can_create_scanout_or_gmb_shared_image(get_format())) {
     EXPECT_FALSE(backing);
     return;
   }
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_iosurface_unittest.cc b/gpu/command_buffer/service/shared_image_backing_factory_iosurface_unittest.cc
index 5bf507c..8fc0c73f8 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_iosurface_unittest.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_iosurface_unittest.cc
@@ -67,7 +67,8 @@
 
     backing_factory_ = std::make_unique<SharedImageBackingFactoryGLTexture>(
         preferences, workarounds, GpuFeatureInfo(), &image_factory_,
-        shared_image_manager_.batch_access_manager());
+        shared_image_manager_.batch_access_manager(),
+        /*progress_reporter=*/nullptr);
 
     memory_type_tracker_ = std::make_unique<MemoryTypeTracker>(nullptr);
     shared_image_representation_factory_ =
diff --git a/gpu/command_buffer/service/shared_image_backing_gl_texture.cc b/gpu/command_buffer/service/shared_image_backing_gl_texture.cc
index 308d6d7..354d494 100644
--- a/gpu/command_buffer/service/shared_image_backing_gl_texture.cc
+++ b/gpu/command_buffer/service/shared_image_backing_gl_texture.cc
@@ -44,6 +44,7 @@
 #include "ui/gl/gl_image_shared_memory.h"
 #include "ui/gl/gl_implementation.h"
 #include "ui/gl/gl_version_info.h"
+#include "ui/gl/progress_reporter.h"
 #include "ui/gl/scoped_binders.h"
 #include "ui/gl/shared_gl_fence_egl.h"
 #include "ui/gl/trace_util.h"
@@ -75,11 +76,13 @@
     const GpuDriverBugWorkarounds& workarounds,
     const GpuFeatureInfo& gpu_feature_info,
     ImageFactory* image_factory,
-    SharedImageBatchAccessManager* batch_access_manager)
+    SharedImageBatchAccessManager* batch_access_manager,
+    gl::ProgressReporter* progress_reporter)
     : use_passthrough_(gpu_preferences.use_passthrough_cmd_decoder &&
                        gles2::PassthroughCommandDecoderSupported()),
       image_factory_(image_factory),
-      workarounds_(workarounds) {
+      workarounds_(workarounds),
+      progress_reporter_(progress_reporter) {
 #if defined(OS_ANDROID)
   batch_access_manager_ = batch_access_manager;
 #endif
@@ -505,13 +508,22 @@
   // the internal format in the LevelInfo. https://crbug.com/628064
   GLuint level_info_internal_format = format_info.gl_format;
   bool is_cleared = false;
+
+  // |scoped_progress_reporter| will notify |progress_reporter_| upon
+  // construction and destruction. We limit the scope so that progress is
+  // reported immediately after allocation/upload and before other GL
+  // operations.
   if (use_buffer) {
-    image = image_factory_->CreateAnonymousImage(
-        size, format_info.buffer_format, gfx::BufferUsage::SCANOUT,
-        surface_handle, &is_cleared);
+    {
+      gl::ScopedProgressReporter scoped_progress_reporter(progress_reporter_);
+      image = image_factory_->CreateAnonymousImage(
+          size, format_info.buffer_format, gfx::BufferUsage::SCANOUT,
+          surface_handle, &is_cleared);
+    }
     // Scanout images have different constraints than GL images and might fail
     // to allocate even if GL images can be created.
     if (!image) {
+      gl::ScopedProgressReporter scoped_progress_reporter(progress_reporter_);
       // TODO(dcastagna): Use BufferUsage::GPU_READ_WRITE instead
       // BufferUsage::GPU_READ once we add it.
       image = image_factory_->CreateAnonymousImage(
@@ -546,6 +558,7 @@
         image, mailbox, format, size, color_space, surface_origin, alpha_type,
         usage, params, attribs, use_passthrough_);
     if (!pixel_data.empty()) {
+      gl::ScopedProgressReporter scoped_progress_reporter(progress_reporter_);
       result->InitializePixels(format_info.adjusted_format, format_info.gl_type,
                                pixel_data.data());
     }
@@ -561,12 +574,16 @@
     api->glBindTextureFn(target, result->GetGLServiceId());
 
     if (format_info.supports_storage) {
-      api->glTexStorage2DEXTFn(target, 1, format_info.storage_internal_format,
-                               size.width(), size.height());
+      {
+        gl::ScopedProgressReporter scoped_progress_reporter(progress_reporter_);
+        api->glTexStorage2DEXTFn(target, 1, format_info.storage_internal_format,
+                                 size.width(), size.height());
+      }
 
       if (!pixel_data.empty()) {
         ScopedResetAndRestoreUnpackState scoped_unpack_state(
             api, attribs, true /* uploading_data */);
+        gl::ScopedProgressReporter scoped_progress_reporter(progress_reporter_);
         api->glTexSubImage2DFn(target, 0, 0, 0, size.width(), size.height(),
                                format_info.adjusted_format, format_info.gl_type,
                                pixel_data.data());
@@ -574,12 +591,14 @@
     } else if (format_info.is_compressed) {
       ScopedResetAndRestoreUnpackState scoped_unpack_state(api, attribs,
                                                            !pixel_data.empty());
+      gl::ScopedProgressReporter scoped_progress_reporter(progress_reporter_);
       api->glCompressedTexImage2DFn(
           target, 0, format_info.image_internal_format, size.width(),
           size.height(), 0, pixel_data.size(), pixel_data.data());
     } else {
       ScopedResetAndRestoreUnpackState scoped_unpack_state(api, attribs,
                                                            !pixel_data.empty());
+      gl::ScopedProgressReporter scoped_progress_reporter(progress_reporter_);
       api->glTexImage2DFn(target, 0, format_info.image_internal_format,
                           size.width(), size.height(), 0,
                           format_info.adjusted_format, format_info.gl_type,
diff --git a/gpu/command_buffer/service/shared_image_factory.cc b/gpu/command_buffer/service/shared_image_factory.cc
index d8e3bb5..c671ee2f 100644
--- a/gpu/command_buffer/service/shared_image_factory.cc
+++ b/gpu/command_buffer/service/shared_image_factory.cc
@@ -100,7 +100,9 @@
   if (use_gl) {
     gl_backing_factory_ = std::make_unique<SharedImageBackingFactoryGLTexture>(
         gpu_preferences, workarounds, gpu_feature_info, image_factory,
-        shared_image_manager->batch_access_manager());
+        shared_image_manager->batch_access_manager(),
+        shared_context_state_ ? shared_context_state_->progress_reporter()
+                              : nullptr);
   }
 
   // TODO(ccameron): This block of code should be changed to a switch on
diff --git a/gpu/config/BUILD.gn b/gpu/config/BUILD.gn
index 569657ae..134306e 100644
--- a/gpu/config/BUILD.gn
+++ b/gpu/config/BUILD.gn
@@ -245,9 +245,19 @@
     template = "android/java/src/org/chromium/gpu/config/GpuSwitches.java.tmpl"
   }
 
+  java_cpp_features("java_features_srcjar") {
+    # External code should depend on ":config_java" instead.
+    visibility = [ ":*" ]
+    sources = [ "gpu_finch_features.cc" ]
+    template = "android/java/src/org/chromium/gpu/config/GpuFeatures.java.tmpl"
+  }
+
   android_library("config_java") {
-    # Right now, this only includes the Java switches. But if we need more Java
-    # files, they should be added here as necessary.
-    srcjar_deps = [ ":java_switches_srcjar" ]
+    # Right now, this only includes the Java switches/features. But if we need
+    # more Java files, they should be added here as necessary.
+    srcjar_deps = [
+      ":java_features_srcjar",
+      ":java_switches_srcjar",
+    ]
   }
 }
diff --git a/gpu/config/android/java/src/org/chromium/gpu/config/GpuFeatures.java.tmpl b/gpu/config/android/java/src/org/chromium/gpu/config/GpuFeatures.java.tmpl
new file mode 100644
index 0000000..9ef9708
--- /dev/null
+++ b/gpu/config/android/java/src/org/chromium/gpu/config/GpuFeatures.java.tmpl
@@ -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.
+
+package org.chromium.gpu.config;
+
+/**
+ * Constants for the names of GPU Features.
+ */
+public final class GpuFeatures {{
+
+{NATIVE_FEATURES}
+
+    // Prevent instantiation.
+    private GpuFeatures() {{}}
+}}
diff --git a/gpu/config/gpu_finch_features.cc b/gpu/config/gpu_finch_features.cc
index b81cf575..dd4d77b 100644
--- a/gpu/config/gpu_finch_features.cc
+++ b/gpu/config/gpu_finch_features.cc
@@ -31,16 +31,17 @@
 
 // Enable GPU Rasterization by default. This can still be overridden by
 // --force-gpu-rasterization or --disable-gpu-rasterization.
-#if defined(OS_MAC) || defined(OS_WIN) || defined(OS_CHROMEOS) || \
-    defined(OS_ANDROID) || defined(OS_FUCHSIA)
 // DefaultEnableGpuRasterization has launched on Mac, Windows, ChromeOS, and
 // Android.
 const base::Feature kDefaultEnableGpuRasterization{
-    "DefaultEnableGpuRasterization", base::FEATURE_ENABLED_BY_DEFAULT};
+  "DefaultEnableGpuRasterization",
+#if defined(OS_MAC) || defined(OS_WIN) || defined(OS_CHROMEOS) || \
+    defined(OS_ANDROID) || defined(OS_FUCHSIA)
+      base::FEATURE_ENABLED_BY_DEFAULT
 #else
-const base::Feature kDefaultEnableGpuRasterization{
-    "DefaultEnableGpuRasterization", base::FEATURE_DISABLED_BY_DEFAULT};
+      base::FEATURE_DISABLED_BY_DEFAULT
 #endif
+};
 
 // Enable out of process rasterization by default.  This can still be overridden
 // by --disable-oop-rasterization.
@@ -51,16 +52,22 @@
 // Use a high priority for GPU process on Windows.
 const base::Feature kGpuProcessHighPriorityWin{
     "GpuProcessHighPriorityWin", base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Compute the root damage rect from the surface damage list for overlays on
+// Windows.
+const base::Feature kDirectCompositionUseOverlayDamageList{
+    "DirectCompositionUseOverlayDamageList", base::FEATURE_ENABLED_BY_DEFAULT};
 #endif
 
 // Use ThreadPriority::DISPLAY for GPU main, viz compositor and IO threads.
+const base::Feature kGpuUseDisplayThreadPriority{
+  "GpuUseDisplayThreadPriority",
 #if defined(OS_ANDROID) || defined(OS_CHROMEOS) || defined(OS_WIN)
-const base::Feature kGpuUseDisplayThreadPriority{
-    "GpuUseDisplayThreadPriority", base::FEATURE_ENABLED_BY_DEFAULT};
+      base::FEATURE_ENABLED_BY_DEFAULT
 #else
-const base::Feature kGpuUseDisplayThreadPriority{
-    "GpuUseDisplayThreadPriority", base::FEATURE_DISABLED_BY_DEFAULT};
+      base::FEATURE_DISABLED_BY_DEFAULT
 #endif
+};
 
 // Gpu watchdog V2 to simplify the logic and reduce GPU hangs
 const base::Feature kGpuWatchdogV2{"GpuWatchdogV2",
diff --git a/gpu/config/gpu_finch_features.h b/gpu/config/gpu_finch_features.h
index 1fac460..e8a4f367 100644
--- a/gpu/config/gpu_finch_features.h
+++ b/gpu/config/gpu_finch_features.h
@@ -28,6 +28,8 @@
 
 #if defined(OS_WIN)
 GPU_EXPORT extern const base::Feature kGpuProcessHighPriorityWin;
+
+GPU_EXPORT extern const base::Feature kDirectCompositionUseOverlayDamageList;
 #endif
 
 GPU_EXPORT extern const base::Feature kGpuUseDisplayThreadPriority;
diff --git a/ios/chrome/app/main_controller.mm b/ios/chrome/app/main_controller.mm
index 5ef4941..ca2f6c3 100644
--- a/ios/chrome/app/main_controller.mm
+++ b/ios/chrome/app/main_controller.mm
@@ -138,6 +138,9 @@
 // Constants for deferred deletion of leftover temporary passwords files.
 NSString* const kDeleteTempPasswords = @"DeleteTempPasswords";
 
+// Constants for deferred UMA logging of existing Siri User shortcuts.
+NSString* const kLogSiriShortcuts = @"LogSiriShortcuts";
+
 // Constants for deferred sending of queued feedback.
 NSString* const kSendQueuedFeedback = @"SendQueuedFeedback";
 
@@ -984,6 +987,7 @@
   [self scheduleSpotlightResync];
   [self scheduleDeleteTempDownloadsDirectory];
   [self scheduleDeleteTempPasswordsDirectory];
+  [self scheduleLogSiriShortcuts];
   [self scheduleStartupAttemptReset];
   [self startFreeMemoryMonitoring];
   [self scheduleAppDistributionPings];
@@ -1020,6 +1024,15 @@
                   }];
 }
 
+- (void)scheduleLogSiriShortcuts {
+  __weak StartupTasks* startupTasks = _startupTasks;
+  [[DeferredInitializationRunner sharedInstance]
+      enqueueBlockNamed:kLogSiriShortcuts
+                  block:^{
+                    [startupTasks logSiriShortcuts];
+                  }];
+}
+
 - (void)scheduleSpotlightResync {
   if (!_spotlightManager) {
     return;
diff --git a/ios/chrome/app/startup_tasks.h b/ios/chrome/app/startup_tasks.h
index 2664398..c4146d63 100644
--- a/ios/chrome/app/startup_tasks.h
+++ b/ios/chrome/app/startup_tasks.h
@@ -20,6 +20,8 @@
 - (void)initializeOmaha;
 // Registers to receive UIApplicationWillResignActiveNotification.
 - (void)registerForApplicationWillResignActiveNotification;
+// Logs the number of Chrome Siri Shortcuts to UMA.
+- (void)logSiriShortcuts;
 
 @end
 
diff --git a/ios/chrome/app/startup_tasks.mm b/ios/chrome/app/startup_tasks.mm
index 7e030fe0..c5899e80 100644
--- a/ios/chrome/app/startup_tasks.mm
+++ b/ios/chrome/app/startup_tasks.mm
@@ -7,6 +7,8 @@
 #import <MediaPlayer/MediaPlayer.h>
 
 #include "base/bind.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/task/post_task.h"
 #import "ios/chrome/app/deferred_initialization_runner.h"
 #include "ios/chrome/app/intents/SearchInChromeIntent.h"
 #include "ios/chrome/browser/application_context.h"
@@ -77,6 +79,26 @@
            object:nil];
 }
 
+- (void)logSiriShortcuts {
+  base::ThreadPool::PostTask(
+      FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
+      base::BindOnce(^{
+        [[INVoiceShortcutCenter sharedCenter]
+            getAllVoiceShortcutsWithCompletion:^(
+                NSArray<INVoiceShortcut*>* voiceShortcuts, NSError* error) {
+              if (error || !voiceShortcuts) {
+                return;
+              }
+
+              // The 20 shortcuts cap is arbitrary but seems like a reasonable
+              // limit.
+              base::UmaHistogramExactLinear(
+                  "IOS.SiriShortcuts.Count",
+                  base::saturated_cast<int>([voiceShortcuts count]), 20);
+            }];
+      }));
+}
+
 #pragma mark - Private methods.
 
 + (void)performDeferredInitializationForBrowserState:
diff --git a/ios/chrome/browser/metrics/BUILD.gn b/ios/chrome/browser/metrics/BUILD.gn
index bf8347f..1d45088 100644
--- a/ios/chrome/browser/metrics/BUILD.gn
+++ b/ios/chrome/browser/metrics/BUILD.gn
@@ -282,6 +282,123 @@
 import("//components/metrics/generate_expired_histograms_array.gni")
 
 generate_expired_histograms_array("expired_histograms_array") {
+  inputs = [
+    "//tools/metrics/histograms/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/accessibility/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/android/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/apps/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/arc/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/ash/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/assistant/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/auth/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/auto/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/autofill/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/back_forward_cache/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/background/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/blink/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/bluetooth/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/browser/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/chrome/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/cloud/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/compositing/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/content/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/cookie/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/cras/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/cros/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/crostini/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/crypt/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/cryptohome/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/custom_tabs/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/data_reduction_proxy/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/dev/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/diagnostics/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/direct/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/disk/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/dom/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/download/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/enterprise/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/event/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/extension/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/extensions/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/file/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/fingerprint/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/gcm/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/geolocation/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/google/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/gpu/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml",
+    "//tools/metrics/histograms/histograms_xml/history/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/holding_space/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/image/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/input/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/installer/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/instant/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/interstitial/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/ios/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/local/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/login/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/media/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/memory/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/mobile/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/multi_device/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/na_cl/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/navigation/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/net/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/network/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/new_tab_page/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/notifications/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/offline/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/omnibox/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/oobe/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/optimization/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/others/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/page/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/password/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/payment/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/permissions/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/platform/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/plugin/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/power/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/print/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/printing/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/profile/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/quickoffice/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/quota/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/renderer/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/renderer4/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/safe_browsing/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/sb_client/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/search/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/security/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/service/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/session/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/settings/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/sharing/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/signin/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/simple/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/smart/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/software/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/stability/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/startup/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/storage/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/subresource/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/sync/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/tab/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/translate/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/ukm/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/uma/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/update_engine/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/v8/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/variations/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/video_tutorials/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/web_apk/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/web_audio/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/web_core/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/web_rtc/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/windows/histograms.xml",
+    "//tools/metrics/histograms/histograms_xml/obsolete_histograms.xml",
+    "//tools/metrics/histograms/enums.xml",
+  ]
   namespace = ""
   header_filename = "ios_expired_histograms_array.h"
   major_branch_date_filepath = "//chrome/MAJOR_BRANCH_DATE"
diff --git a/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller_unittest.mm b/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller_unittest.mm
index cc66ddd..9049b28 100644
--- a/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller_unittest.mm
@@ -183,7 +183,7 @@
 
 // Tests that |-replaceItemID:withItem:| does not crash when updating an item
 // that is scrolled offscreen.
-TEST_F(GridViewControllerTest, ReplaceScrolledOffScreenCell) {
+TEST_F(GridViewControllerTest, DISABLED_ReplaceScrolledOffScreenCell) {
   // TODO(crbug.com/1104872): On iOS 14 iPhone X, visibleCellsCount is always
   // equal to the total number of cells, so the while loop below never
   // terminates.
diff --git a/media/capture/video/video_capture_device_unittest.cc b/media/capture/video/video_capture_device_unittest.cc
index a878a553..38f5617 100644
--- a/media/capture/video/video_capture_device_unittest.cc
+++ b/media/capture/video/video_capture_device_unittest.cc
@@ -55,7 +55,7 @@
 #include "media/capture/video/chromeos/public/cros_features.h"
 #include "media/capture/video/chromeos/video_capture_device_chromeos_halv3.h"
 #include "media/capture/video/chromeos/video_capture_device_factory_chromeos.h"
-#include "media/gpu/test/local_gpu_memory_buffer_manager.h"
+#include "media/gpu/test/local_gpu_memory_buffer_manager.h"  // nogncheck
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #endif
 
diff --git a/media/gpu/test/BUILD.gn b/media/gpu/test/BUILD.gn
index 92f16e16..675e565 100644
--- a/media/gpu/test/BUILD.gn
+++ b/media/gpu/test/BUILD.gn
@@ -214,7 +214,7 @@
   }
 }
 
-if (is_chromeos || use_vaapi) {
+if (is_chromeos) {
   static_library("local_gpu_memory_buffer_manager") {
     testonly = true
     sources = [
diff --git a/media/gpu/v4l2/BUILD.gn b/media/gpu/v4l2/BUILD.gn
index 1307adc..9a999e4 100644
--- a/media/gpu/v4l2/BUILD.gn
+++ b/media/gpu/v4l2/BUILD.gn
@@ -23,6 +23,8 @@
 source_set("v4l2") {
   defines = [ "MEDIA_GPU_IMPLEMENTATION" ]
   sources = [
+    "buffer_affinity_tracker.cc",
+    "buffer_affinity_tracker.h",
     "generic_v4l2_device.cc",
     "generic_v4l2_device.h",
     "v4l2_decode_surface.cc",
diff --git a/media/gpu/v4l2/buffer_affinity_tracker.cc b/media/gpu/v4l2/buffer_affinity_tracker.cc
new file mode 100644
index 0000000..2e1524b
--- /dev/null
+++ b/media/gpu/v4l2/buffer_affinity_tracker.cc
@@ -0,0 +1,50 @@
+// 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 "media/gpu/v4l2/buffer_affinity_tracker.h"
+#include "base/synchronization/lock.h"
+#include "media/gpu/macros.h"
+#include "ui/gfx/generic_shared_memory_id.h"
+
+namespace media {
+
+BufferAffinityTracker::BufferAffinityTracker(size_t nb_buffers) {
+  resize(0);
+}
+
+void BufferAffinityTracker::resize(size_t nb_buffers) {
+  base::AutoLock lock(lock_);
+
+  id_to_buffer_map_.clear();
+  nb_buffers_ = nb_buffers;
+  DVLOGF(4) << this << " resize: " << nb_buffers;
+}
+
+base::Optional<size_t> BufferAffinityTracker::get_buffer_for_id(
+    gfx::GenericSharedMemoryId id) {
+  base::AutoLock lock(lock_);
+
+  auto it = id_to_buffer_map_.find(id);
+  // If the handle is already bound to a buffer, return it.
+  if (it != id_to_buffer_map_.end()) {
+    DVLOGF(4) << this << " match for " << it->second;
+    return it->second;
+  }
+
+  // Try to assign a new buffer for this handle...
+
+  // No buffer available? No luck then.
+  if (id_to_buffer_map_.size() == nb_buffers()) {
+    DVLOGF(4) << this << " tracker is full!";
+    return base::nullopt;
+  }
+
+  const size_t v4l2_id = id_to_buffer_map_.size();
+  id_to_buffer_map_.emplace(id, v4l2_id);
+
+  DVLOGF(4) << this << " add " << v4l2_id;
+  return v4l2_id;
+}
+
+}  // namespace media
\ No newline at end of file
diff --git a/media/gpu/v4l2/buffer_affinity_tracker.h b/media/gpu/v4l2/buffer_affinity_tracker.h
new file mode 100644
index 0000000..3b3c572
--- /dev/null
+++ b/media/gpu/v4l2/buffer_affinity_tracker.h
@@ -0,0 +1,55 @@
+// 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 MEDIA_GPU_V4L2_V4L2_BUFFER_AFFINITY_TRACKER_H_
+#define MEDIA_GPU_V4L2_V4L2_BUFFER_AFFINITY_TRACKER_H_
+
+#include <cstddef>
+#include <map>
+
+#include "base/optional.h"
+#include "base/synchronization/lock.h"
+#include "ui/gfx/generic_shared_memory_id.h"
+
+namespace media {
+
+/**
+ * Maintains affinity between native handles and V4L2 buffers.
+ *
+ * Give a handle ID, `get_buffer_for_id()` will attempt to always return the
+ * same V4L2 buffer ID so handles are always used with the same buffer. This
+ * is both beneficial for performance, and necessary in some cases like the
+ * stateful decoder.
+ *
+ * All the methods of this class are thread-safe.
+ */
+class BufferAffinityTracker {
+ public:
+  explicit BufferAffinityTracker(size_t nb_buffers);
+  size_t nb_buffers() const { return nb_buffers_; }
+  // Resize this tracker and reset its state.
+  void resize(size_t nb_buffers);
+
+  /**
+   * Return the V4L2 buffer index suitable for this buffer ID.
+   *
+   * If it is the first time this method is called with a given id, return the
+   * first available buffer it can find, and memorize the association between
+   * the id and the V4L2 buffer.
+   *
+   * On subsequent calls with the same id, that same V4L2 buffer will be
+   * returned.
+   */
+  base::Optional<size_t> get_buffer_for_id(gfx::GenericSharedMemoryId id);
+
+ private:
+  base::Lock lock_;
+  std::map<gfx::GenericSharedMemoryId, size_t> id_to_buffer_map_;
+  // Maximum number of buffers we are allowed to track.
+  size_t nb_buffers_;
+};
+
+}  // namespace media
+
+#endif  // MEDIA_GPU_V4L2_V4L2_BUFFER_AFFINITY_TRACKER_H_
diff --git a/media/gpu/v4l2/v4l2_device.cc b/media/gpu/v4l2/v4l2_device.cc
index 7386a0bd..9dd1c87 100644
--- a/media/gpu/v4l2/v4l2_device.cc
+++ b/media/gpu/v4l2/v4l2_device.cc
@@ -29,7 +29,9 @@
 #include "media/gpu/chromeos/fourcc.h"
 #include "media/gpu/chromeos/platform_video_frame_utils.h"
 #include "media/gpu/macros.h"
+#include "media/gpu/v4l2/buffer_affinity_tracker.h"
 #include "media/gpu/v4l2/generic_v4l2_device.h"
+#include "ui/gfx/generic_shared_memory_id.h"
 #include "ui/gfx/native_pixmap_handle.h"
 
 #if defined(ARCH_CPU_ARMEL)
@@ -908,6 +910,7 @@
                      enum v4l2_buf_type type,
                      base::OnceClosure destroy_cb)
     : type_(type),
+      affinity_tracker_(0),
       device_(dev),
       destroy_cb_(std::move(destroy_cb)),
       weak_this_factory_(this) {
@@ -1097,6 +1100,8 @@
     free_buffers_->ReturnBuffer(i);
   }
 
+  affinity_tracker_.resize(buffers_.size());
+
   DCHECK(free_buffers_);
   DCHECK_EQ(free_buffers_->size(), buffers_.size());
   DCHECK_EQ(queued_buffers_.size(), 0u);
@@ -1117,6 +1122,7 @@
 
   weak_this_factory_.InvalidateWeakPtrs();
   buffers_.clear();
+  affinity_tracker_.resize(0);
   free_buffers_ = nullptr;
 
   // Free all buffers.
@@ -1184,6 +1190,37 @@
       weak_this_factory_.GetWeakPtr());
 }
 
+base::Optional<V4L2WritableBufferRef> V4L2Queue::GetFreeBufferForFrame(
+    const VideoFrame& frame) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // No buffers allocated at the moment?
+  if (!free_buffers_)
+    return base::nullopt;
+
+  if (memory_ != V4L2_MEMORY_DMABUF) {
+    DVLOGF(1) << "Queue is not DMABUF";
+    return base::nullopt;
+  }
+
+  gfx::GenericSharedMemoryId id;
+  if (auto gmb = frame.GetGpuMemoryBuffer()) {
+    id = gmb->GetId();
+  } else if (frame.HasDmaBufs()) {
+    id = gfx::GenericSharedMemoryId(frame.DmabufFds()[0].get());
+  } else {
+    DVLOGF(1) << "Unsupported frame provided";
+    return base::nullopt;
+  }
+
+  const auto v4l2_id = affinity_tracker_.get_buffer_for_id(id);
+  if (!v4l2_id) {
+    return base::nullopt;
+  }
+
+  return GetFreeBuffer(*v4l2_id);
+}
+
 bool V4L2Queue::QueueBuffer(struct v4l2_buffer* v4l2_buffer,
                             scoped_refptr<VideoFrame> video_frame) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
diff --git a/media/gpu/v4l2/v4l2_device.h b/media/gpu/v4l2/v4l2_device.h
index 30653ec..849d1cd 100644
--- a/media/gpu/v4l2/v4l2_device.h
+++ b/media/gpu/v4l2/v4l2_device.h
@@ -28,6 +28,7 @@
 #include "media/base/video_frame_layout.h"
 #include "media/gpu/chromeos/fourcc.h"
 #include "media/gpu/media_gpu_export.h"
+#include "media/gpu/v4l2/buffer_affinity_tracker.h"
 #include "media/gpu/v4l2/v4l2_device_poller.h"
 #include "media/video/video_decode_accelerator.h"
 #include "media/video/video_encode_accelerator.h"
@@ -374,6 +375,23 @@
   // return |base::nullopt|.
   base::Optional<V4L2WritableBufferRef> GetFreeBuffer(
       size_t requested_buffer_id);
+  // Return a V4L2 buffer suitable for the passed VideoFrame.
+  //
+  // This method will try as much as possible to always return the same V4L2
+  // buffer when the same frame is passed again, to avoid memory unmap
+  // operations in the kernel driver.
+  //
+  // The operating mode of the queue must be DMABUF, and the VideoFrame must
+  // be backed either by a GpuMemoryBuffer, or by DMABUFs. In the case of
+  // DMABUFs, this method will only work correctly if the same DMABUFs are
+  // passed with each call, i.e. no dup shall be performed.
+  //
+  // This should be the preferred way to obtain buffers when using DMABUF mode,
+  // since it will maximize performance in that case provided the number of
+  // different VideoFrames passed to this method does not exceed the number of
+  // V4L2 buffers allocated on the queue.
+  base::Optional<V4L2WritableBufferRef> GetFreeBufferForFrame(
+      const VideoFrame& frame);
 
   // Attempt to dequeue a buffer, and return a reference to it if one was
   // available.
@@ -443,6 +461,9 @@
   // value will be set to the VideoFrame that has been passed when we queued
   // the buffer, if any.
   std::map<size_t, scoped_refptr<VideoFrame>> queued_buffers_;
+  // Keep track of which buffer was assigned to which frame by
+  // |GetFreeBufferForFrame()| so we reuse the same buffer in subsequent calls.
+  BufferAffinityTracker affinity_tracker_;
 
   scoped_refptr<V4L2Device> device_;
   // Callback to call in this queue's destructor.
diff --git a/media/gpu/v4l2/v4l2_image_processor_backend.cc b/media/gpu/v4l2/v4l2_image_processor_backend.cc
index 00b6119c..064b893 100644
--- a/media/gpu/v4l2/v4l2_image_processor_backend.cc
+++ b/media/gpu/v4l2/v4l2_image_processor_backend.cc
@@ -96,13 +96,23 @@
 }
 
 bool AllocateV4L2Buffers(V4L2Queue* queue,
-                         size_t num_buffers,
+                         const size_t num_buffers,
                          v4l2_memory memory_type) {
   DCHECK(queue);
-  if (queue->AllocateBuffers(num_buffers, memory_type) == 0u)
+
+  size_t requested_buffers = num_buffers;
+
+  // If we are using DMABUFs, then we will try to keep using the same V4L2
+  // buffer for a given input or output frame. In that case, allocate as many
+  // V4L2 buffers as we can to avoid running out of them. Unused buffers won't
+  // use backed memory and are thus virtually free.
+  if (memory_type == V4L2_MEMORY_DMABUF)
+    requested_buffers = VIDEO_MAX_FRAME;
+
+  if (queue->AllocateBuffers(requested_buffers, memory_type) == 0u)
     return false;
 
-  if (queue->AllocatedBuffersCount() != num_buffers) {
+  if (queue->AllocatedBuffersCount() < num_buffers) {
     VLOGF(1) << "Failed to allocate buffers. Allocated number="
              << queue->AllocatedBuffersCount()
              << ", Requested number=" << num_buffers;
@@ -613,8 +623,26 @@
     }
 
     // We need one input and one output buffer to schedule the job
-    auto input_buffer = input_queue_->GetFreeBuffer();
-    auto output_buffer = output_queue_->GetFreeBuffer();
+    base::Optional<V4L2WritableBufferRef> input_buffer;
+    // If we are using DMABUF frames, try to always obtain the same V4L2 buffer.
+    if (input_memory_type_ == V4L2_MEMORY_DMABUF) {
+      const VideoFrame& input_frame =
+          *(input_job_queue_.front()->input_frame.get());
+      input_buffer = input_queue_->GetFreeBufferForFrame(input_frame);
+    }
+    if (!input_buffer)
+      input_buffer = input_queue_->GetFreeBuffer();
+
+    base::Optional<V4L2WritableBufferRef> output_buffer;
+    // If we are using DMABUF frames, try to always obtain the same V4L2 buffer.
+    if (output_memory_type_ == V4L2_MEMORY_DMABUF) {
+      const VideoFrame& output_frame =
+          *(input_job_queue_.front()->output_frame.get());
+      output_buffer = output_queue_->GetFreeBufferForFrame(output_frame);
+    }
+    if (!output_buffer)
+      output_buffer = output_queue_->GetFreeBuffer();
+
     if (!input_buffer || !output_buffer)
       break;
 
diff --git a/media/gpu/vaapi/vaapi_wrapper.h b/media/gpu/vaapi/vaapi_wrapper.h
index 6f3387cd..d67b497 100644
--- a/media/gpu/vaapi/vaapi_wrapper.h
+++ b/media/gpu/vaapi/vaapi_wrapper.h
@@ -34,7 +34,7 @@
 #include "ui/gfx/geometry/size.h"
 
 #if defined(USE_X11)
-#include "ui/gfx/x/x11.h"
+#include "ui/gfx/x/x11.h"  // nogncheck
 #endif  // USE_X11
 
 namespace gfx {
diff --git a/net/quic/bidirectional_stream_quic_impl_unittest.cc b/net/quic/bidirectional_stream_quic_impl_unittest.cc
index 9aabe53..8d0a5a20 100644
--- a/net/quic/bidirectional_stream_quic_impl_unittest.cc
+++ b/net/quic/bidirectional_stream_quic_impl_unittest.cc
@@ -530,8 +530,8 @@
         new QuicChromiumConnectionHelper(&clock_, &random_generator_));
     alarm_factory_.reset(new QuicChromiumAlarmFactory(runner_.get(), &clock_));
     connection_ = new quic::QuicConnection(
-        connection_id_, ToQuicSocketAddress(peer_addr_), helper_.get(),
-        alarm_factory_.get(),
+        connection_id_, quic::QuicSocketAddress(),
+        ToQuicSocketAddress(peer_addr_), helper_.get(), alarm_factory_.get(),
         new QuicChromiumPacketWriter(socket.get(), runner_.get()),
         true /* owns_writer */, quic::Perspective::IS_CLIENT,
         quic::test::SupportedVersions(version_));
diff --git a/net/quic/quic_chromium_client_session_test.cc b/net/quic/quic_chromium_client_session_test.cc
index 17b0895..b0b9d11 100644
--- a/net/quic/quic_chromium_client_session_test.cc
+++ b/net/quic/quic_chromium_client_session_test.cc
@@ -181,8 +181,8 @@
         socket.get(), base::ThreadTaskRunnerHandle::Get().get());
     quic::QuicConnection* connection = new quic::QuicConnection(
         quic::QuicUtils::CreateRandomConnectionId(&random_),
-        ToQuicSocketAddress(kIpEndPoint), &helper_, &alarm_factory_, writer,
-        true, quic::Perspective::IS_CLIENT,
+        quic::QuicSocketAddress(), ToQuicSocketAddress(kIpEndPoint), &helper_,
+        &alarm_factory_, writer, true, quic::Perspective::IS_CLIENT,
         quic::test::SupportedVersions(version_));
     session_.reset(new TestingQuicChromiumClientSession(
         connection, std::move(socket),
diff --git a/net/quic/quic_connection_logger.cc b/net/quic/quic_connection_logger.cc
index a8d3875..d74eb20 100644
--- a/net/quic/quic_connection_logger.cc
+++ b/net/quic/quic_connection_logger.cc
@@ -290,8 +290,9 @@
   // TODO(rtenneti): Add logging.
 }
 
-void QuicConnectionLogger::OnPacketHeader(
-    const quic::QuicPacketHeader& header) {
+void QuicConnectionLogger::OnPacketHeader(const quic::QuicPacketHeader& header,
+                                          quic::QuicTime receive_time,
+                                          quic::EncryptionLevel level) {
   if (!first_received_packet_number_.IsInitialized()) {
     first_received_packet_number_ = header.packet_number;
   } else if (header.packet_number < first_received_packet_number_) {
@@ -338,7 +339,7 @@
     no_packet_received_after_ping_ = false;
   }
   last_received_packet_number_ = header.packet_number;
-  event_logger_.OnPacketHeader(header);
+  event_logger_.OnPacketHeader(header, receive_time, level);
 }
 
 void QuicConnectionLogger::OnStreamFrame(const quic::QuicStreamFrame& frame) {
diff --git a/net/quic/quic_connection_logger.h b/net/quic/quic_connection_logger.h
index 9c5e351..a9def71 100644
--- a/net/quic/quic_connection_logger.h
+++ b/net/quic/quic_connection_logger.h
@@ -73,7 +73,9 @@
       quic::EncryptionLevel decryption_level) override;
   void OnDuplicatePacket(quic::QuicPacketNumber packet_number) override;
   void OnProtocolVersionMismatch(quic::ParsedQuicVersion version) override;
-  void OnPacketHeader(const quic::QuicPacketHeader& header) override;
+  void OnPacketHeader(const quic::QuicPacketHeader& header,
+                      quic::QuicTime receive_time,
+                      quic::EncryptionLevel level) override;
   void OnPathChallengeFrame(const quic::QuicPathChallengeFrame& frame) override;
   void OnPathResponseFrame(const quic::QuicPathResponseFrame& frame) override;
   void OnCryptoFrame(const quic::QuicCryptoFrame& frame) override;
diff --git a/net/quic/quic_event_logger.cc b/net/quic/quic_event_logger.cc
index e1023fa7..e8807eee 100644
--- a/net/quic/quic_event_logger.cc
+++ b/net/quic/quic_event_logger.cc
@@ -642,7 +642,9 @@
       [&] { return NetLogQuicDuplicatePacketParams(packet_number); });
 }
 
-void QuicEventLogger::OnPacketHeader(const quic::QuicPacketHeader& header) {
+void QuicEventLogger::OnPacketHeader(const quic::QuicPacketHeader& header,
+                                     quic::QuicTime /*receive_time*/,
+                                     quic::EncryptionLevel /*level*/) {
   if (!net_log_.IsCapturing())
     return;
   net_log_.AddEvent(NetLogEventType::QUIC_SESSION_PACKET_AUTHENTICATED);
diff --git a/net/quic/quic_event_logger.h b/net/quic/quic_event_logger.h
index d25a8f6..8e8eb4d 100644
--- a/net/quic/quic_event_logger.h
+++ b/net/quic/quic_event_logger.h
@@ -52,7 +52,9 @@
   void OnAttemptingToProcessUndecryptablePacket(
       quic::EncryptionLevel decryption_level) override;
   void OnDuplicatePacket(quic::QuicPacketNumber packet_number) override;
-  void OnPacketHeader(const quic::QuicPacketHeader& header) override;
+  void OnPacketHeader(const quic::QuicPacketHeader& header,
+                      quic::QuicTime receive_time,
+                      quic::EncryptionLevel level) override;
   void OnPathChallengeFrame(const quic::QuicPathChallengeFrame& frame) override;
   void OnPathResponseFrame(const quic::QuicPathResponseFrame& frame) override;
   void OnCryptoFrame(const quic::QuicCryptoFrame& frame) override;
diff --git a/net/quic/quic_flags_list.h b/net/quic/quic_flags_list.h
index bf264bd..2413624 100644
--- a/net/quic/quic_flags_list.h
+++ b/net/quic/quic_flags_list.h
@@ -456,3 +456,14 @@
 
 // If true, check for NULL before sending a fallback config.
 QUIC_FLAG(bool, FLAGS_quic_reloadable_flag_quic_check_fallback_null, true)
+
+// If true, HTTP/3 sesions will report error and close connection upon receiving
+// HTTP/2 only frames.
+QUIC_FLAG(bool, FLAGS_quic_reloadable_flag_quic_reject_spdy_frames, false)
+
+// If true, QuicConnection will initialize its self address to the self address
+// of the first received packet, for all server connections and client
+// connections that know its own address.
+QUIC_FLAG(bool,
+          FLAGS_quic_reloadable_flag_quic_connection_set_initial_self_address,
+          false)
diff --git a/net/quic/quic_http_stream_test.cc b/net/quic/quic_http_stream_test.cc
index cfec1ae1..6115b11 100644
--- a/net/quic/quic_http_stream_test.cc
+++ b/net/quic/quic_http_stream_test.cc
@@ -165,6 +165,7 @@
                      QuicChromiumAlarmFactory* alarm_factory,
                      quic::QuicPacketWriter* writer)
       : quic::QuicConnection(connection_id,
+                             quic::QuicSocketAddress(),
                              ToQuicSocketAddress(address),
                              helper,
                              alarm_factory,
diff --git a/net/quic/quic_proxy_client_socket_unittest.cc b/net/quic/quic_proxy_client_socket_unittest.cc
index 2876669..751eecf 100644
--- a/net/quic/quic_proxy_client_socket_unittest.cc
+++ b/net/quic/quic_proxy_client_socket_unittest.cc
@@ -227,7 +227,8 @@
     QuicChromiumPacketWriter* writer = new QuicChromiumPacketWriter(
         socket.get(), base::ThreadTaskRunnerHandle::Get().get());
     quic::QuicConnection* connection = new quic::QuicConnection(
-        connection_id_, net::ToQuicSocketAddress(peer_addr_), helper_.get(),
+        connection_id_, quic::QuicSocketAddress(),
+        net::ToQuicSocketAddress(peer_addr_), helper_.get(),
         alarm_factory_.get(), writer, true /* owns_writer */,
         quic::Perspective::IS_CLIENT, quic::test::SupportedVersions(version_));
     connection->set_visitor(&visitor_);
diff --git a/net/quic/quic_stream_factory.cc b/net/quic/quic_stream_factory.cc
index ad845cb..dabd78c 100644
--- a/net/quic/quic_stream_factory.cc
+++ b/net/quic/quic_stream_factory.cc
@@ -1774,8 +1774,8 @@
   QuicChromiumPacketWriter* writer =
       new QuicChromiumPacketWriter(socket.get(), task_runner_);
   quic::QuicConnection* connection = new quic::QuicConnection(
-      connection_id, ToQuicSocketAddress(addr), helper_.get(),
-      alarm_factory_.get(), writer, true /* owns_writer */,
+      connection_id, quic::QuicSocketAddress(), ToQuicSocketAddress(addr),
+      helper_.get(), alarm_factory_.get(), writer, true /* owns_writer */,
       quic::Perspective::IS_CLIENT, {quic_version});
   connection->set_ping_timeout(ping_timeout_);
   connection->SetMaxPacketLength(params_.max_packet_length);
diff --git a/net/quic/quic_transport_client.cc b/net/quic/quic_transport_client.cc
index 6f2138d..e45116b9 100644
--- a/net/quic/quic_transport_client.cc
+++ b/net/quic/quic_transport_client.cc
@@ -275,8 +275,9 @@
       quic::QuicUtils::CreateRandomConnectionId(
           quic_context_->random_generator());
   connection_ = std::make_unique<quic::QuicConnection>(
-      connection_id, ToQuicSocketAddress(server_address),
-      quic_context_->helper(), alarm_factory_.get(),
+      connection_id, quic::QuicSocketAddress(),
+      ToQuicSocketAddress(server_address), quic_context_->helper(),
+      alarm_factory_.get(),
       new QuicChromiumPacketWriter(socket_.get(), task_runner_),
       /* owns_writer */ true, quic::Perspective::IS_CLIENT,
       supported_versions_);
diff --git a/sandbox/linux/BUILD.gn b/sandbox/linux/BUILD.gn
index 4fd639f..e99b5e5 100644
--- a/sandbox/linux/BUILD.gn
+++ b/sandbox/linux/BUILD.gn
@@ -434,6 +434,7 @@
     "system_headers/linux_filter.h",
     "system_headers/linux_futex.h",
     "system_headers/linux_prctl.h",
+    "system_headers/linux_ptrace.h",
     "system_headers/linux_seccomp.h",
     "system_headers/linux_signal.h",
     "system_headers/linux_syscalls.h",
diff --git a/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc b/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc
index a100248..45c7eda 100644
--- a/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc
+++ b/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc
@@ -29,6 +29,7 @@
 #include "sandbox/linux/seccomp-bpf/sandbox_bpf.h"
 #include "sandbox/linux/system_headers/linux_futex.h"
 #include "sandbox/linux/system_headers/linux_prctl.h"
+#include "sandbox/linux/system_headers/linux_ptrace.h"
 #include "sandbox/linux/system_headers/linux_syscalls.h"
 #include "sandbox/linux/system_headers/linux_time.h"
 
@@ -405,20 +406,26 @@
 #if !defined(OS_NACL_NONSFI)
 ResultExpr RestrictPtrace() {
   const Arg<int> request(0);
-  return Switch(request).CASES((
+#if defined(__aarch64__)
+  const Arg<uintptr_t> addr(2);
+#endif
+  return Switch(request)
+      .CASES((
 #if !defined(__aarch64__)
-        PTRACE_GETREGS,
-        PTRACE_GETFPREGS,
-        PTRACE_GET_THREAD_AREA,
+                 PTRACE_GETREGS, PTRACE_GETFPREGS, PTRACE_GET_THREAD_AREA,
+                 PTRACE_GETREGSET,
 #endif
 #if defined(__arm__)
-        PTRACE_GETVFPREGS,
+                 PTRACE_GETVFPREGS,
 #endif
-        PTRACE_GETREGSET,
-        PTRACE_PEEKDATA,
-        PTRACE_ATTACH,
-        PTRACE_DETACH),
-      Allow())
+                 PTRACE_PEEKDATA, PTRACE_ATTACH, PTRACE_DETACH),
+             Allow())
+#if defined(__aarch64__)
+      .Case(
+          PTRACE_GETREGSET,
+          If(AllOf(addr != NT_ARM_PACA_KEYS, addr != NT_ARM_PACG_KEYS), Allow())
+              .Else(CrashSIGSYSPtrace()))
+#endif
       .Default(CrashSIGSYSPtrace());
 }
 #endif  // defined(OS_NACL_NONSFI)
diff --git a/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions_unittests.cc b/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions_unittests.cc
index b6c8c63..4bbfc7e 100644
--- a/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions_unittests.cc
+++ b/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions_unittests.cc
@@ -32,6 +32,7 @@
 #include "sandbox/linux/seccomp-bpf/sandbox_bpf.h"
 #include "sandbox/linux/seccomp-bpf/syscall.h"
 #include "sandbox/linux/services/syscall_wrappers.h"
+#include "sandbox/linux/system_headers/linux_ptrace.h"
 #include "sandbox/linux/system_headers/linux_syscalls.h"
 #include "sandbox/linux/system_headers/linux_time.h"
 #include "sandbox/linux/tests/unit_tests.h"
@@ -341,6 +342,36 @@
          &iov);
 }
 
+#if defined(__aarch64__)
+BPF_DEATH_TEST_C(
+    ParameterRestrictions,
+    ptrace_getregs_nt_arm_paca_keys_blocked,
+    DEATH_SEGV_MESSAGE(sandbox::GetPtraceErrorMessageContentForTests()),
+    RestrictPtracePolicy) {
+  user_regs_struct regs{};
+  iovec iov;
+  iov.iov_base = &regs;
+  iov.iov_len = sizeof(regs);
+  errno = 0;
+  ptrace(PTRACE_GETREGSET, getpid(), reinterpret_cast<void*>(NT_ARM_PACA_KEYS),
+         &iov);
+}
+
+BPF_DEATH_TEST_C(
+    ParameterRestrictions,
+    ptrace_getregs_nt_arm_pacg_keys_blocked,
+    DEATH_SEGV_MESSAGE(sandbox::GetPtraceErrorMessageContentForTests()),
+    RestrictPtracePolicy) {
+  user_regs_struct regs{};
+  iovec iov;
+  iov.iov_base = &regs;
+  iov.iov_len = sizeof(regs);
+  errno = 0;
+  ptrace(PTRACE_GETREGSET, getpid(), reinterpret_cast<void*>(NT_ARM_PACG_KEYS),
+         &iov);
+}
+#endif
+
 }  // namespace
 
 }  // namespace sandbox
diff --git a/sandbox/linux/system_headers/linux_ptrace.h b/sandbox/linux/system_headers/linux_ptrace.h
new file mode 100644
index 0000000..c7f47ac
--- /dev/null
+++ b/sandbox/linux/system_headers/linux_ptrace.h
@@ -0,0 +1,13 @@
+// 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 SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_PTRACE_H_
+#define SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_PTRACE_H_
+
+#if !defined(NT_ARM_PACA_KEYS)
+#define NT_ARM_PACA_KEYS 0x407 /* Arm pointer authentication address keys */
+#define NT_ARM_PACG_KEYS 0x408 /* Arm pointer authentication generic key */
+#endif
+
+#endif  // SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_PTRACE_H_
diff --git a/services/network/resource_scheduler/resource_scheduler.cc b/services/network/resource_scheduler/resource_scheduler.cc
index e738be0..e898448 100644
--- a/services/network/resource_scheduler/resource_scheduler.cc
+++ b/services/network/resource_scheduler/resource_scheduler.cc
@@ -1270,10 +1270,6 @@
         ideal_duration_to_wait);
   }
 
-  // Tracks if the main HTML parser has reached the body which marks the end of
-  // layout-blocking resources.
-  // This is disabled and the is always true when kRendererSideResourceScheduler
-  // is enabled.
   RequestQueue pending_requests_;
   RequestSet in_flight_requests_;
 
diff --git a/testing/buildbot/generate_buildbot_json.py b/testing/buildbot/generate_buildbot_json.py
index 5975cdd..af61bc4 100755
--- a/testing/buildbot/generate_buildbot_json.py
+++ b/testing/buildbot/generate_buildbot_json.py
@@ -727,8 +727,9 @@
 
     self.initialize_args_for_test(
         result, tester_config, additional_arg_keys=['gtest_args'])
-    if self.is_android(tester_config) and tester_config.get('use_swarming',
-                                                            True):
+    if self.is_android(tester_config) and tester_config.get(
+        'use_swarming',
+        True) and not test_config.get('use_isolated_scripts_api', False):
       self.add_android_presentation_args(tester_config, test_name, result)
       result['args'] = result.get('args', []) + ['--recover-devices']
 
@@ -740,9 +741,14 @@
     if not result.get('merge'):
       # TODO(https://crbug.com/958376): Consider adding the ability to not have
       # this default.
+      if test_config.get('use_isolated_scripts_api', False):
+        merge_script = 'standard_isolated_script_merge'
+      else:
+        merge_script = 'standard_gtest_merge'
+
       result['merge'] = {
-        'script': '//testing/merge_scripts/standard_gtest_merge.py',
-        'args': [],
+          'script': '//testing/merge_scripts/%s.py' % merge_script,
+          'args': [],
       }
     return result
 
diff --git a/testing/buildbot/generate_buildbot_json_unittest.py b/testing/buildbot/generate_buildbot_json_unittest.py
index 1c5601a9..266674f 100755
--- a/testing/buildbot/generate_buildbot_json_unittest.py
+++ b/testing/buildbot/generate_buildbot_json_unittest.py
@@ -892,6 +892,19 @@
 }
 """
 
+GTEST_AS_ISOLATED_SCRIPT_SUITE = """\
+{
+  'basic_suites': {
+    'foo_tests': {
+      'foo_test': {
+        'script': 'foo.py',
+        'use_isolated_scripts_api': True,
+      },
+    },
+  },
+}
+"""
+
 SCRIPT_WITH_ARGS_EXCEPTIONS = """\
 {
   'foo_test': {
@@ -1176,6 +1189,35 @@
 }
 """
 
+FOO_WATERFALL_GTEST_ISOLATED_SCRIPT_OUTPUT = """\
+{
+  "AAAAA1 AUTOGENERATED FILE DO NOT EDIT": {},
+  "AAAAA2 See generate_buildbot_json.py to make changes": {},
+  "Fake Tester": {
+    "gtest_tests": [
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "script": "foo.py",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1"
+            }
+          ]
+        },
+        "test": "foo_test",
+        "test_id_prefix": "ninja://chrome/test:foo_test/",
+        "use_isolated_scripts_api": true
+      }
+    ]
+  }
+}
+"""
+
 COMPOSITION_WATERFALL_FILTERED_OUTPUT = """\
 {
   "AAAAA1 AUTOGENERATED FILE DO NOT EDIT": {},
@@ -2425,6 +2467,19 @@
     fbb.check_output_file_consistency(verbose=True)
     self.assertFalse(fbb.printed_lines)
 
+  def test_gtest_as_isolated_Script(self):
+    fbb = FakeBBGen(self.args,
+                    FOO_GTESTS_WATERFALL,
+                    GTEST_AS_ISOLATED_SCRIPT_SUITE,
+                    LUCI_MILO_CFG,
+                    gn_isolate_map=GN_ISOLATE_MAP)
+    self.create_testing_buildbot_json_file(
+        'chromium.test.json', FOO_WATERFALL_GTEST_ISOLATED_SCRIPT_OUTPUT)
+    self.create_testing_buildbot_json_file(
+        'chromium.ci.json', FOO_WATERFALL_GTEST_ISOLATED_SCRIPT_OUTPUT)
+    fbb.check_output_file_consistency(verbose=True)
+    self.assertFalse(fbb.printed_lines)
+
   def test_ungenerated_output_files_are_caught(self):
     fbb = FakeBBGen(self.args,
                     COMPOSITION_GTEST_SUITE_WATERFALL,
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 2aeb8b36..b4cee57 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -1846,6 +1846,25 @@
             ]
         }
     ],
+    "CrosSchedulerCore": [
+        {
+            "platforms": [
+                "chromeos"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "params": {
+                        "config": "core-scheduling"
+                    },
+                    "enable_features": [
+                        "CoreSchedulingEnabled",
+                        "SchedulerConfiguration"
+                    ]
+                }
+            ]
+        }
+    ],
     "CrostiniWebUIUpgrader": [
         {
             "platforms": [
@@ -2821,27 +2840,6 @@
             ]
         }
     ],
-    "FlocIdComputedEventLogging": [
-        {
-            "platforms": [
-                "android",
-                "android_webview",
-                "chromeos",
-                "ios",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "FlocIdComputedEventLogging"
-                    ]
-                }
-            ]
-        }
-    ],
     "FormControlsDarkMode": [
         {
             "platforms": [
@@ -4109,29 +4107,6 @@
             ]
         }
     ],
-    "LowPriorityIframes2": [
-        {
-            "platforms": [
-                "linux",
-                "mac",
-                "windows",
-                "android",
-                "android_weblayer",
-                "chromeos"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "params": {
-                        "max_effective_connection_type_threshold": "4G"
-                    },
-                    "enable_features": [
-                        "LowPriorityIframes"
-                    ]
-                }
-            ]
-        }
-    ],
     "LowerJavaScriptPriorityWhenForceDeferred": [
         {
             "platforms": [
diff --git a/third_party/blink/common/OWNERS b/third_party/blink/common/OWNERS
index 9b94b90..f5f8884e 100644
--- a/third_party/blink/common/OWNERS
+++ b/third_party/blink/common/OWNERS
@@ -1,11 +1,12 @@
-jam@chromium.org
-haraken@chromium.org
-kinuko@chromium.org
-tkent@chromium.org
-jbroman@chromium.org
-pfeldman@chromium.org
-dgozman@chromium.org
 dcheng@chromium.org
+dgozman@chromium.org
+dtapuska@chromium.org
 falken@chromium.org
+haraken@chromium.org
+jam@chromium.org
+jbroman@chromium.org
+kinuko@chromium.org
+pfeldman@chromium.org
+tkent@chromium.org
 
 # COMPONENT: Blink
diff --git a/third_party/blink/renderer/bindings/generated_in_modules.gni b/third_party/blink/renderer/bindings/generated_in_modules.gni
index 6cf406ae..9875903 100644
--- a/third_party/blink/renderer/bindings/generated_in_modules.gni
+++ b/third_party/blink/renderer/bindings/generated_in_modules.gni
@@ -399,12 +399,18 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gpu_vertex_buffer_layout_descriptor.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gpu_vertex_state_descriptor.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gpu_vertex_state_descriptor.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_hid_collection_info.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_hid_collection_info.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_hid_connection_event_init.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_hid_connection_event_init.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_hid_device_filter.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_hid_device_filter.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_hid_device_request_options.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_hid_device_request_options.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_hid_report_info.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_hid_report_info.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_hid_report_item.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_hid_report_item.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_hit_region_options.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_hit_region_options.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_idb_database_info.cc",
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc
index 8a07d74..2d07ca8 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -4249,4 +4249,11 @@
   web_widget_->SetDeviceColorSpaceForTesting(color_space);
 }
 
+void WebViewImpl::RunPaintBenchmark(int repeat_count,
+                                    cc::PaintBenchmarkResult& result) {
+  DCHECK(MainFrameImpl());
+  if (auto* frame_view = MainFrameImpl()->GetFrameView())
+    frame_view->RunPaintBenchmark(repeat_count, result);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.h b/third_party/blink/renderer/core/exported/web_view_impl.h
index e5dc418..03a2e10 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.h
+++ b/third_party/blink/renderer/core/exported/web_view_impl.h
@@ -463,6 +463,8 @@
   // Called when keyboard focus switches to an anchor with the given URL.
   void SetKeyboardFocusURL(const KURL&);
 
+  void RunPaintBenchmark(int repeat_count, cc::PaintBenchmarkResult& result);
+
  private:
   FRIEND_TEST_ALL_PREFIXES(WebFrameTest, DivScrollIntoEditableTest);
   FRIEND_TEST_ALL_PREFIXES(WebFrameTest,
diff --git a/third_party/blink/renderer/core/frame/DEPS b/third_party/blink/renderer/core/frame/DEPS
index bce2d2b..66e05bf7 100644
--- a/third_party/blink/renderer/core/frame/DEPS
+++ b/third_party/blink/renderer/core/frame/DEPS
@@ -21,6 +21,7 @@
     "+ui/gfx/transform.h"
   ],
   "local_frame_view.cc": [
+    "+base/timer/lap_timer.h",
     "+cc/tiles/frame_viewer_instrumentation.h",
     "+components/paint_preview/common/paint_preview_tracker.h",
   ],
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index dc779bb..6cfbe3e 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -35,6 +35,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/numerics/safe_conversions.h"
+#include "base/timer/lap_timer.h"
 #include "cc/input/main_thread_scrolling_reason.h"
 #include "cc/layers/picture_layer.h"
 #include "cc/tiles/frame_viewer_instrumentation.h"
@@ -2738,7 +2739,7 @@
   return target_state > DocumentLifecycle::kPrePaintClean;
 }
 
-void LocalFrameView::RunPaintLifecyclePhase() {
+void LocalFrameView::RunPaintLifecyclePhase(PaintBenchmarkMode benchmark_mode) {
   TRACE_EVENT0("blink,benchmark", "LocalFrameView::RunPaintLifecyclePhase");
   // While printing or capturing a paint preview of a document, the paint walk
   // is done into a special canvas. There is no point doing a normal paint step
@@ -2746,7 +2747,7 @@
   bool is_capturing_layout = frame_->GetDocument()->IsCapturingLayout();
   HashSet<const GraphicsLayer*> repainted_layers;
   if (!is_capturing_layout)
-    PaintTree(repainted_layers);
+    PaintTree(repainted_layers, benchmark_mode);
 
   if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
     if (GetLayoutView()->Compositor()->InCompositingMode()) {
@@ -2754,6 +2755,15 @@
     }
   }
 
+  if (benchmark_mode ==
+          PaintBenchmarkMode::kForcePaintArtifactCompositorUpdate ||
+      // TODO(paint-dev): Separate requirement for update for repaint and full
+      // PaintArtifactCompositor update.
+      (RuntimeEnabledFeatures::CompositeAfterPaintEnabled() &&
+       benchmark_mode != PaintBenchmarkMode::kNormal)) {
+    paint_artifact_compositor_->SetNeedsUpdate();
+  }
+
   if (!is_capturing_layout) {
     bool needed_update = !paint_artifact_compositor_ ||
                          paint_artifact_compositor_->NeedsUpdate();
@@ -2901,8 +2911,8 @@
       });
 }
 
-void LocalFrameView::PaintTree(
-    HashSet<const GraphicsLayer*>& repainted_layers) {
+void LocalFrameView::PaintTree(HashSet<const GraphicsLayer*>& repainted_layers,
+                               PaintBenchmarkMode benchmark_mode) {
   SCOPED_UMA_AND_UKM_TIMER(EnsureUkmAggregator(),
                            LocalFrameUkmAggregator::kPaint);
 
@@ -2924,12 +2934,16 @@
     if (!paint_controller_)
       paint_controller_ = std::make_unique<PaintController>();
 
+    PaintController::ScopedBenchmarkMode scoped_benchmark(*paint_controller_,
+                                                          benchmark_mode);
+
     // TODO(crbug.com/917911): Painting of overlays should not force repainting
     // of the frame contents.
     auto* web_local_frame_impl = WebLocalFrameImpl::FromFrame(frame_);
     bool has_dev_tools_overlays =
         web_local_frame_impl && web_local_frame_impl->HasDevToolsOverlays();
-    if (!GetLayoutView()->Layer()->SelfOrDescendantNeedsRepaint() &&
+    if (!paint_controller_->ShouldForcePaintForBenchmark() &&
+        !GetLayoutView()->Layer()->SelfOrDescendantNeedsRepaint() &&
         !visual_viewport_needs_repaint_ && !has_dev_tools_overlays) {
       paint_controller_->UpdateUMACountsOnFullyCached();
     } else {
@@ -2988,7 +3002,7 @@
     // the host page and will be painted during painting of the host page.
     if (GraphicsLayer* root_graphics_layer =
             layout_view->Compositor()->PaintRootGraphicsLayer()) {
-      root_graphics_layer->PaintRecursively(repainted_layers);
+      root_graphics_layer->PaintRecursively(repainted_layers, benchmark_mode);
       if (!repainted_layers.IsEmpty()) {
         // If the painted result changed, the recorded hit test data may have
         // changed which will affect the mapped hit test geometry.
@@ -3047,7 +3061,7 @@
 
   // Skip updating property trees, pushing cc::Layers, and issuing raster
   // invalidations if possible.
-  // TODO(paint-team): In CompositeAfterPaint mode, repainted_layers will always
+  // TODO(paint-dev): In CompositeAfterPaint mode, repainted_layers will always
   // be empty, even if painted output has changed. We need an equivalent signal
   // to indicate that PAC doesn't need to run the layerization algorithm, but it
   // does need to update properties on layers that depend on painted output.
@@ -4804,4 +4818,43 @@
   return GetFullScreenOverlayVideoLayer(*doc);
 }
 
+void LocalFrameView::RunPaintBenchmark(int repeat_count,
+                                       cc::PaintBenchmarkResult& result) {
+  DCHECK_EQ(Lifecycle().GetState(), DocumentLifecycle::kPaintClean);
+
+  auto run_benchmark = [&](PaintBenchmarkMode mode) -> double {
+    constexpr int kTimeCheckInterval = 1;
+    constexpr int kWarmupRuns = 0;
+    constexpr base::TimeDelta kTimeLimit = base::TimeDelta::FromMilliseconds(1);
+
+    base::TimeDelta min_time = base::TimeDelta::Max();
+    for (int i = 0; i < repeat_count; i++) {
+      // Run for a minimum amount of time to avoid problems with timer
+      // quantization when the time is very small.
+      base::LapTimer timer(kWarmupRuns, kTimeLimit, kTimeCheckInterval);
+      do {
+        RunPaintLifecyclePhase(mode);
+        timer.NextLap();
+      } while (!timer.HasTimeLimitExpired());
+
+      base::TimeDelta duration = timer.TimePerLap();
+      if (duration < min_time)
+        min_time = duration;
+    }
+    return min_time.InMillisecondsF();
+  };
+
+  result.record_time_ms = run_benchmark(PaintBenchmarkMode::kForcePaint);
+  result.record_time_caching_disabled_ms =
+      run_benchmark(PaintBenchmarkMode::kCachingDisabled);
+  result.record_time_subsequence_caching_disabled_ms =
+      run_benchmark(PaintBenchmarkMode::kSubsequenceCachingDisabled);
+  result.record_time_partial_invalidation_ms =
+      run_benchmark(PaintBenchmarkMode::kPartialInvalidation);
+  result.raster_invalidation_and_convert_time_ms =
+      run_benchmark(PaintBenchmarkMode::kForceRasterInvalidationAndConvert);
+  result.paint_artifact_compositor_update_time_ms =
+      run_benchmark(PaintBenchmarkMode::kForcePaintArtifactCompositorUpdate);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.h b/third_party/blink/renderer/core/frame/local_frame_view.h
index 4ca0577..520dde8 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.h
+++ b/third_party/blink/renderer/core/frame/local_frame_view.h
@@ -46,6 +46,7 @@
 #include "third_party/blink/renderer/platform/graphics/color.h"
 #include "third_party/blink/renderer/platform/graphics/compositor_element_id.h"
 #include "third_party/blink/renderer/platform/graphics/paint/cull_rect.h"
+#include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h"
 #include "third_party/blink/renderer/platform/graphics/paint_invalidation_reason.h"
 #include "third_party/blink/renderer/platform/graphics/subtree_paint_property_update_reason.h"
 #include "third_party/blink/renderer/platform/timer.h"
@@ -57,8 +58,8 @@
 class Layer;
 class PaintOpBuffer;
 enum class PaintHoldingCommitTrigger;
-
 using PaintRecord = PaintOpBuffer;
+struct PaintBenchmarkResult;
 }
 
 namespace ui {
@@ -729,6 +730,8 @@
 
   PaintLayer* GetFullScreenOverlayLayer() const;
 
+  void RunPaintBenchmark(int repeat_count, cc::PaintBenchmarkResult& result);
+
  protected:
   void FrameRectsChanged(const IntRect&) override;
   void SelfVisibleChanged() override;
@@ -803,9 +806,10 @@
       DocumentLifecycle::LifecycleState target_state);
   bool RunPrePaintLifecyclePhase(
       DocumentLifecycle::LifecycleState target_state);
-  void RunPaintLifecyclePhase();
+  void RunPaintLifecyclePhase(PaintBenchmarkMode = PaintBenchmarkMode::kNormal);
 
-  void PaintTree(HashSet<const GraphicsLayer*>& repainted_layers);
+  void PaintTree(HashSet<const GraphicsLayer*>& repainted_layers,
+                 PaintBenchmarkMode);
   void UpdateStyleAndLayoutIfNeededRecursive();
 
   void PushPaintArtifactToCompositor(
diff --git a/third_party/blink/renderer/core/frame/web_view_frame_widget.cc b/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
index 892b34da..cfd1be3 100644
--- a/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
+++ b/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
@@ -416,6 +416,11 @@
   return true;
 }
 
+void WebViewFrameWidget::RunPaintBenchmark(int repeat_count,
+                                           cc::PaintBenchmarkResult& result) {
+  web_view_->RunPaintBenchmark(repeat_count, result);
+}
+
 const ScreenInfo& WebViewFrameWidget::GetOriginalScreenInfo() {
   if (device_emulator_)
     return device_emulator_->original_screen_info();
diff --git a/third_party/blink/renderer/core/frame/web_view_frame_widget.h b/third_party/blink/renderer/core/frame/web_view_frame_widget.h
index e5d3b46..1d98679 100644
--- a/third_party/blink/renderer/core/frame/web_view_frame_widget.h
+++ b/third_party/blink/renderer/core/frame/web_view_frame_widget.h
@@ -135,6 +135,8 @@
   gfx::Rect ViewportVisibleRect() override;
   bool UpdateScreenRects(const gfx::Rect& widget_screen_rect,
                          const gfx::Rect& window_screen_rect) override;
+  void RunPaintBenchmark(int repeat_count,
+                         cc::PaintBenchmarkResult& result) override;
 
   void SetScreenMetricsEmulationParameters(
       bool enabled,
diff --git a/third_party/blink/renderer/core/inspector/inspector_overlay_agent.cc b/third_party/blink/renderer/core/inspector/inspector_overlay_agent.cc
index 29402d2..9f9dc0e 100644
--- a/third_party/blink/renderer/core/inspector/inspector_overlay_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_overlay_agent.cc
@@ -296,8 +296,7 @@
   bool FillsBoundsCompletely() const override { return false; }
   size_t GetApproximateUnsharedMemoryUsage() const override { return 0; }
 
-  scoped_refptr<cc::DisplayItemList> PaintContentsToDisplayList(
-      PaintingControlSetting) override {
+  scoped_refptr<cc::DisplayItemList> PaintContentsToDisplayList() override {
     auto display_list = base::MakeRefCounted<cc::DisplayItemList>();
     display_list->StartPaint();
     display_list->push<cc::DrawRecordOp>(
diff --git a/third_party/blink/renderer/core/layout/layout_button.h b/third_party/blink/renderer/core/layout/layout_button.h
index 796df53..279451d0 100644
--- a/third_party/blink/renderer/core/layout/layout_button.h
+++ b/third_party/blink/renderer/core/layout/layout_button.h
@@ -42,8 +42,7 @@
   }
   bool IsOfType(LayoutObjectType type) const override {
     NOT_DESTROYED();
-    return type == kLayoutObjectLayoutButton ||
-           LayoutFlexibleBox::IsOfType(type);
+    return type == kLayoutObjectButton || LayoutFlexibleBox::IsOfType(type);
   }
 
   void AddChild(LayoutObject* new_child,
diff --git a/third_party/blink/renderer/core/layout/layout_custom_scrollbar_part.h b/third_party/blink/renderer/core/layout/layout_custom_scrollbar_part.h
index 1c8d465..58aae26 100644
--- a/third_party/blink/renderer/core/layout/layout_custom_scrollbar_part.h
+++ b/third_party/blink/renderer/core/layout/layout_custom_scrollbar_part.h
@@ -71,7 +71,7 @@
 
   bool IsOfType(LayoutObjectType type) const override {
     NOT_DESTROYED();
-    return type == kLayoutObjectLayoutCustomScrollbarPart ||
+    return type == kLayoutObjectCustomScrollbarPart ||
            LayoutReplaced::IsOfType(type);
   }
   ScrollableArea* GetScrollableArea() const {
diff --git a/third_party/blink/renderer/core/layout/layout_grid.h b/third_party/blink/renderer/core/layout/layout_grid.h
index c5f349b..63e16523 100644
--- a/third_party/blink/renderer/core/layout/layout_grid.h
+++ b/third_party/blink/renderer/core/layout/layout_grid.h
@@ -146,7 +146,7 @@
  private:
   bool IsOfType(LayoutObjectType type) const override {
     NOT_DESTROYED();
-    return type == kLayoutObjectLayoutGrid || LayoutBlock::IsOfType(type);
+    return type == kLayoutObjectGrid || LayoutBlock::IsOfType(type);
   }
   MinMaxSizes ComputeIntrinsicLogicalWidths() const override;
 
diff --git a/third_party/blink/renderer/core/layout/layout_iframe.h b/third_party/blink/renderer/core/layout/layout_iframe.h
index bc22555..45f3f76b 100644
--- a/third_party/blink/renderer/core/layout/layout_iframe.h
+++ b/third_party/blink/renderer/core/layout/layout_iframe.h
@@ -47,8 +47,7 @@
 
   bool IsOfType(LayoutObjectType type) const override {
     NOT_DESTROYED();
-    return type == kLayoutObjectLayoutIFrame ||
-           LayoutEmbeddedContent::IsOfType(type);
+    return type == kLayoutObjectIFrame || LayoutEmbeddedContent::IsOfType(type);
   }
 
   PaintLayerType LayerTypeRequired() const override;
diff --git a/third_party/blink/renderer/core/layout/layout_image.h b/third_party/blink/renderer/core/layout/layout_image.h
index 94db77be..4ac30e6c 100644
--- a/third_party/blink/renderer/core/layout/layout_image.h
+++ b/third_party/blink/renderer/core/layout/layout_image.h
@@ -117,7 +117,7 @@
 
   bool IsOfType(LayoutObjectType type) const override {
     NOT_DESTROYED();
-    return type == kLayoutObjectLayoutImage || LayoutReplaced::IsOfType(type);
+    return type == kLayoutObjectImage || LayoutReplaced::IsOfType(type);
   }
 
   void WillBeDestroyed() override;
diff --git a/third_party/blink/renderer/core/layout/layout_multi_column_set.h b/third_party/blink/renderer/core/layout/layout_multi_column_set.h
index 03f086e..85782690 100644
--- a/third_party/blink/renderer/core/layout/layout_multi_column_set.h
+++ b/third_party/blink/renderer/core/layout/layout_multi_column_set.h
@@ -102,7 +102,7 @@
 
   bool IsOfType(LayoutObjectType type) const override {
     NOT_DESTROYED();
-    return type == kLayoutObjectLayoutMultiColumnSet ||
+    return type == kLayoutObjectMultiColumnSet ||
            LayoutBlockFlow::IsOfType(type);
   }
   bool CanHaveChildren() const final {
diff --git a/third_party/blink/renderer/core/layout/layout_multi_column_spanner_placeholder.h b/third_party/blink/renderer/core/layout/layout_multi_column_spanner_placeholder.h
index 074ae5d..94d6224cf 100644
--- a/third_party/blink/renderer/core/layout/layout_multi_column_spanner_placeholder.h
+++ b/third_party/blink/renderer/core/layout/layout_multi_column_spanner_placeholder.h
@@ -19,7 +19,7 @@
  public:
   bool IsOfType(LayoutObjectType type) const override {
     NOT_DESTROYED();
-    return type == kLayoutObjectLayoutMultiColumnSpannerPlaceholder ||
+    return type == kLayoutObjectMultiColumnSpannerPlaceholder ||
            LayoutBox::IsOfType(type);
   }
 
diff --git a/third_party/blink/renderer/core/layout/layout_object.h b/third_party/blink/renderer/core/layout/layout_object.h
index af644aa6..630ef00d 100644
--- a/third_party/blink/renderer/core/layout/layout_object.h
+++ b/third_party/blink/renderer/core/layout/layout_object.h
@@ -838,11 +838,11 @@
   }
   bool IsLayoutTableCol() const {
     NOT_DESTROYED();
-    return IsOfType(kLayoutObjectLayoutTableCol);
+    return IsOfType(kLayoutObjectTableCol);
   }
   bool IsLayoutNGTableCol() const {
     NOT_DESTROYED();
-    return IsOfType(kLayoutObjectLayoutNGTableCol);
+    return IsOfType(kLayoutObjectNGTableCol);
   }
   bool IsListItem() const {
     NOT_DESTROYED();
@@ -882,8 +882,7 @@
   }
   bool IsButtonIncludingNG() const {
     NOT_DESTROYED();
-    return IsOfType(kLayoutObjectLayoutButton) ||
-           IsOfType(kLayoutObjectNGButton);
+    return IsOfType(kLayoutObjectButton) || IsOfType(kLayoutObjectNGButton);
   }
   bool IsLayoutNGButton() const {
     NOT_DESTROYED();
@@ -891,39 +890,39 @@
   }
   bool IsLayoutNGCustom() const {
     NOT_DESTROYED();
-    return IsOfType(kLayoutObjectLayoutNGCustom);
+    return IsOfType(kLayoutObjectNGCustom);
   }
   bool IsLayoutGrid() const {
     NOT_DESTROYED();
-    return IsOfType(kLayoutObjectLayoutGrid);
+    return IsOfType(kLayoutObjectGrid);
   }
   bool IsLayoutIFrame() const {
     NOT_DESTROYED();
-    return IsOfType(kLayoutObjectLayoutIFrame);
+    return IsOfType(kLayoutObjectIFrame);
   }
   bool IsLayoutImage() const {
     NOT_DESTROYED();
-    return IsOfType(kLayoutObjectLayoutImage);
+    return IsOfType(kLayoutObjectImage);
   }
   bool IsLayoutMultiColumnSet() const {
     NOT_DESTROYED();
-    return IsOfType(kLayoutObjectLayoutMultiColumnSet);
+    return IsOfType(kLayoutObjectMultiColumnSet);
   }
   bool IsLayoutMultiColumnSpannerPlaceholder() const {
     NOT_DESTROYED();
-    return IsOfType(kLayoutObjectLayoutMultiColumnSpannerPlaceholder);
+    return IsOfType(kLayoutObjectMultiColumnSpannerPlaceholder);
   }
   bool IsLayoutReplaced() const {
     NOT_DESTROYED();
-    return IsOfType(kLayoutObjectLayoutReplaced);
+    return IsOfType(kLayoutObjectReplaced);
   }
   bool IsLayoutCustomScrollbarPart() const {
     NOT_DESTROYED();
-    return IsOfType(kLayoutObjectLayoutCustomScrollbarPart);
+    return IsOfType(kLayoutObjectCustomScrollbarPart);
   }
   bool IsLayoutView() const {
     NOT_DESTROYED();
-    return IsOfType(kLayoutObjectLayoutView);
+    return IsOfType(kLayoutObjectView);
   }
   bool IsRuby() const {
     NOT_DESTROYED();
@@ -967,7 +966,7 @@
   }
   bool IsTextAreaIncludingNG() const {
     NOT_DESTROYED();
-    return IsOfType(kLayoutObjectTextArea) ||
+    return IsOfType(kLayoutObjectTextControlMultiLine) ||
            IsOfType(kLayoutObjectNGTextControlMultiLine);
   }
   bool IsTextControlIncludingNG() const {
@@ -978,7 +977,7 @@
   }
   bool IsTextFieldIncludingNG() const {
     NOT_DESTROYED();
-    return IsOfType(kLayoutObjectTextField) ||
+    return IsOfType(kLayoutObjectTextControlSingleLine) ||
            IsOfType(kLayoutObjectNGTextControlSingleLine);
   }
   bool IsVideo() const {
@@ -3273,6 +3272,8 @@
   }
 
  protected:
+  // Identifiers for each of LayoutObject subclasses.
+  // The identifier name for blink::LayoutFoo should be kLayoutObjectFoo.
   enum LayoutObjectType {
     kLayoutObjectBr,
     kLayoutObjectCanvas,
@@ -3284,8 +3285,8 @@
     kLayoutObjectFrame,
     kLayoutObjectFrameSet,
     kLayoutObjectInsideListMarker,
-    kLayoutObjectLayoutTableCol,
-    kLayoutObjectLayoutNGTableCol,
+    kLayoutObjectTableCol,
+    kLayoutObjectNGTableCol,
     kLayoutObjectListItem,
     kLayoutObjectListMarker,
     kLayoutObjectListMarkerImage,
@@ -3308,19 +3309,16 @@
     kLayoutObjectOutsideListMarker,
     kLayoutObjectProgress,
     kLayoutObjectQuote,
-    kLayoutObjectLayoutButton,
-    kLayoutObjectLayoutNGCustom,
-    kLayoutObjectLayoutFlowThread,
-    kLayoutObjectLayoutGrid,
-    kLayoutObjectLayoutIFrame,
-    kLayoutObjectLayoutImage,
-    kLayoutObjectLayoutInline,
-    kLayoutObjectLayoutMultiColumnSet,
-    kLayoutObjectLayoutMultiColumnSpannerPlaceholder,
-    kLayoutObjectLayoutEmbeddedContent,
-    kLayoutObjectLayoutReplaced,
-    kLayoutObjectLayoutCustomScrollbarPart,
-    kLayoutObjectLayoutView,
+    kLayoutObjectButton,
+    kLayoutObjectNGCustom,
+    kLayoutObjectGrid,
+    kLayoutObjectIFrame,
+    kLayoutObjectImage,
+    kLayoutObjectMultiColumnSet,
+    kLayoutObjectMultiColumnSpannerPlaceholder,
+    kLayoutObjectReplaced,
+    kLayoutObjectCustomScrollbarPart,
+    kLayoutObjectView,
     kLayoutObjectRuby,
     kLayoutObjectRubyBase,
     kLayoutObjectRubyRun,
@@ -3331,9 +3329,9 @@
     kLayoutObjectTableCellLegacy,
     kLayoutObjectTableRow,
     kLayoutObjectTableSection,
-    kLayoutObjectTextArea,
+    kLayoutObjectTextControlMultiLine,
     kLayoutObjectTextControl,
-    kLayoutObjectTextField,
+    kLayoutObjectTextControlSingleLine,
     kLayoutObjectVideo,
     kLayoutObjectWidget,
 
diff --git a/third_party/blink/renderer/core/layout/layout_replaced.h b/third_party/blink/renderer/core/layout/layout_replaced.h
index ffeec050..7549fd9 100644
--- a/third_party/blink/renderer/core/layout/layout_replaced.h
+++ b/third_party/blink/renderer/core/layout/layout_replaced.h
@@ -169,7 +169,7 @@
 
   bool IsOfType(LayoutObjectType type) const override {
     NOT_DESTROYED();
-    return type == kLayoutObjectLayoutReplaced || LayoutBox::IsOfType(type);
+    return type == kLayoutObjectReplaced || LayoutBox::IsOfType(type);
   }
 
  private:
diff --git a/third_party/blink/renderer/core/layout/layout_table_col.h b/third_party/blink/renderer/core/layout/layout_table_col.h
index b6fd653..f43776f 100644
--- a/third_party/blink/renderer/core/layout/layout_table_col.h
+++ b/third_party/blink/renderer/core/layout/layout_table_col.h
@@ -95,7 +95,7 @@
  private:
   bool IsOfType(LayoutObjectType type) const override {
     NOT_DESTROYED();
-    return type == kLayoutObjectLayoutTableCol || LayoutBox::IsOfType(type);
+    return type == kLayoutObjectTableCol || LayoutBox::IsOfType(type);
   }
   void UpdateFromElement() override;
 
diff --git a/third_party/blink/renderer/core/layout/layout_text_control_multi_line.h b/third_party/blink/renderer/core/layout/layout_text_control_multi_line.h
index 5828d9c..2df8ed5 100644
--- a/third_party/blink/renderer/core/layout/layout_text_control_multi_line.h
+++ b/third_party/blink/renderer/core/layout/layout_text_control_multi_line.h
@@ -35,7 +35,8 @@
  private:
   bool IsOfType(LayoutObjectType type) const override {
     NOT_DESTROYED();
-    return type == kLayoutObjectTextArea || LayoutTextControl::IsOfType(type);
+    return type == kLayoutObjectTextControlMultiLine ||
+           LayoutTextControl::IsOfType(type);
   }
 
   bool NodeAtPoint(HitTestResult&,
diff --git a/third_party/blink/renderer/core/layout/layout_text_control_single_line.h b/third_party/blink/renderer/core/layout/layout_text_control_single_line.h
index e03d33b..7a74105b 100644
--- a/third_party/blink/renderer/core/layout/layout_text_control_single_line.h
+++ b/third_party/blink/renderer/core/layout/layout_text_control_single_line.h
@@ -47,7 +47,8 @@
  private:
   bool IsOfType(LayoutObjectType type) const override {
     NOT_DESTROYED();
-    return type == kLayoutObjectTextField || LayoutTextControl::IsOfType(type);
+    return type == kLayoutObjectTextControlSingleLine ||
+           LayoutTextControl::IsOfType(type);
   }
 
   void Paint(const PaintInfo&) const override;
diff --git a/third_party/blink/renderer/core/layout/layout_view.h b/third_party/blink/renderer/core/layout/layout_view.h
index 028ea58..cc899b6 100644
--- a/third_party/blink/renderer/core/layout/layout_view.h
+++ b/third_party/blink/renderer/core/layout/layout_view.h
@@ -94,7 +94,7 @@
 
   bool IsOfType(LayoutObjectType type) const override {
     NOT_DESTROYED();
-    return type == kLayoutObjectLayoutView || LayoutBlockFlow::IsOfType(type);
+    return type == kLayoutObjectView || LayoutBlockFlow::IsOfType(type);
   }
 
   PaintLayerType LayerTypeRequired() const override {
diff --git a/third_party/blink/renderer/core/layout/ng/custom/layout_ng_custom.h b/third_party/blink/renderer/core/layout/ng/custom/layout_ng_custom.h
index 95f0f3d..720fad9 100644
--- a/third_party/blink/renderer/core/layout/ng/custom/layout_ng_custom.h
+++ b/third_party/blink/renderer/core/layout/ng/custom/layout_ng_custom.h
@@ -37,8 +37,7 @@
 
  private:
   bool IsOfType(LayoutObjectType type) const override {
-    return type == kLayoutObjectLayoutNGCustom ||
-           LayoutNGBlockFlow::IsOfType(type);
+    return type == kLayoutObjectNGCustom || LayoutNGBlockFlow::IsOfType(type);
   }
 
   LayoutNGCustomState state_;
diff --git a/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_column.h b/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_column.h
index 20a342e..87adf3b 100644
--- a/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_column.h
+++ b/third_party/blink/renderer/core/layout/ng/table/layout_ng_table_column.h
@@ -67,7 +67,7 @@
 
   bool IsOfType(LayoutObjectType type) const override {
     NOT_DESTROYED();
-    return type == kLayoutObjectLayoutTableCol || LayoutBox::IsOfType(type);
+    return type == kLayoutObjectTableCol || LayoutBox::IsOfType(type);
   }
 
  private:
diff --git a/third_party/blink/renderer/core/paint/css_mask_painter.cc b/third_party/blink/renderer/core/paint/css_mask_painter.cc
index 6a26d45..cef7d06 100644
--- a/third_party/blink/renderer/core/paint/css_mask_painter.cc
+++ b/third_party/blink/renderer/core/paint/css_mask_painter.cc
@@ -27,6 +27,7 @@
           SVGResources::ReferenceBoxForEffects(object);
       const float reference_box_zoom =
           object.IsSVGForeignObject() ? object.StyleRef().EffectiveZoom() : 1;
+      masker->ClearInvalidationMask();
       return EnclosingIntRect(
           masker->ResourceBoundingBox(reference_box, reference_box_zoom));
     }
diff --git a/third_party/blink/renderer/core/paint/link_highlight_impl.cc b/third_party/blink/renderer/core/paint/link_highlight_impl.cc
index 53eb7f7f..8529965e 100644
--- a/third_party/blink/renderer/core/paint/link_highlight_impl.cc
+++ b/third_party/blink/renderer/core/paint/link_highlight_impl.cc
@@ -159,8 +159,7 @@
 }
 
 scoped_refptr<cc::DisplayItemList>
-LinkHighlightImpl::LinkHighlightFragment::PaintContentsToDisplayList(
-    PaintingControlSetting painting_control) {
+LinkHighlightImpl::LinkHighlightFragment::PaintContentsToDisplayList() {
   auto display_list = base::MakeRefCounted<cc::DisplayItemList>();
 
   PaintRecorder recorder;
diff --git a/third_party/blink/renderer/core/paint/link_highlight_impl.h b/third_party/blink/renderer/core/paint/link_highlight_impl.h
index d1acded..def05d0 100644
--- a/third_party/blink/renderer/core/paint/link_highlight_impl.h
+++ b/third_party/blink/renderer/core/paint/link_highlight_impl.h
@@ -102,8 +102,7 @@
    private:
     // cc::ContentLayerClient implementation.
     gfx::Rect PaintableRegion() override;
-    scoped_refptr<cc::DisplayItemList> PaintContentsToDisplayList(
-        PaintingControlSetting painting_control) override;
+    scoped_refptr<cc::DisplayItemList> PaintContentsToDisplayList() override;
     bool FillsBoundsCompletely() const override { return false; }
     size_t GetApproximateUnsharedMemoryUsage() const override { return 0; }
 
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_impl.cc b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_impl.cc
index 5cc8e0d..54053d6a 100644
--- a/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_impl.cc
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_impl.cc
@@ -163,8 +163,8 @@
   dummy_connection_id =
       quic::QuicConnectionId(connection_id_bytes, sizeof(connection_id_bytes));
   return std::make_unique<quic::QuicConnection>(
-      dummy_connection_id, dummy_address, helper, alarm_factory, packet_writer,
-      /* owns_writer */ true, perspective,
+      dummy_connection_id, quic::QuicSocketAddress(), dummy_address, helper,
+      alarm_factory, packet_writer, /* owns_writer */ true, perspective,
       quic::ParsedQuicVersionVector{quic::CurrentSupportedVersions()[0]});
 }
 
diff --git a/third_party/blink/renderer/modules/wake_lock/wake_lock.cc b/third_party/blink/renderer/modules/wake_lock/wake_lock.cc
index cb0c5f1..8b3983f1 100644
--- a/third_party/blink/renderer/modules/wake_lock/wake_lock.cc
+++ b/third_party/blink/renderer/modules/wake_lock/wake_lock.cc
@@ -18,7 +18,6 @@
 #include "third_party/blink/renderer/modules/wake_lock/wake_lock_type.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
-#include "third_party/blink/renderer/platform/scheduler/public/frame_or_worker_scheduler.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
@@ -34,11 +33,7 @@
       managers_{
           MakeGarbageCollected<WakeLockManager>(&window, WakeLockType::kScreen),
           MakeGarbageCollected<WakeLockManager>(&window,
-                                                WakeLockType::kSystem)} {
-  window.GetScheduler()->RegisterStickyFeature(
-      SchedulingPolicy::Feature::kWakeLock,
-      {SchedulingPolicy::RecordMetricsForBackForwardCache()});
-}
+                                                WakeLockType::kSystem)} {}
 
 WakeLock::WakeLock(DedicatedWorkerGlobalScope& worker_scope)
     : ExecutionContextLifecycleObserver(&worker_scope),
diff --git a/third_party/blink/renderer/modules/wake_lock/wake_lock_manager.cc b/third_party/blink/renderer/modules/wake_lock/wake_lock_manager.cc
index c8cd404b..324f555d 100644
--- a/third_party/blink/renderer/modules/wake_lock/wake_lock_manager.cc
+++ b/third_party/blink/renderer/modules/wake_lock/wake_lock_manager.cc
@@ -51,6 +51,11 @@
     wake_lock_.set_disconnect_handler(WTF::Bind(
         &WakeLockManager::OnWakeLockConnectionError, WrapWeakPersistent(this)));
     wake_lock_->RequestWakeLock();
+
+    feature_handle_for_scheduler_ =
+        execution_context_->GetScheduler()->RegisterFeature(
+            SchedulingPolicy::Feature::kWakeLock,
+            {SchedulingPolicy::RecordMetricsForBackForwardCache()});
   }
   // https://w3c.github.io/screen-wake-lock/#the-request-method
   // 5.2. Let lock be a new WakeLockSentinel object with its type attribute set
@@ -86,6 +91,9 @@
     // 5.2. If success is true and type is "screen" run the following:
     // 5.2.1. Reset the platform-specific inactivity timer after which the
     //        screen is actually turned off.
+
+    // Make the page bfcache-eligible if there is no WakeLock held.
+    feature_handle_for_scheduler_.reset();
   }
 }
 
diff --git a/third_party/blink/renderer/modules/wake_lock/wake_lock_manager.h b/third_party/blink/renderer/modules/wake_lock/wake_lock_manager.h
index ad23acd..d8a5d52 100644
--- a/third_party/blink/renderer/modules/wake_lock/wake_lock_manager.h
+++ b/third_party/blink/renderer/modules/wake_lock/wake_lock_manager.h
@@ -12,6 +12,7 @@
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
 #include "third_party/blink/renderer/platform/mojo/heap_mojo_wrapper_mode.h"
+#include "third_party/blink/renderer/platform/scheduler/public/frame_or_worker_scheduler.h"
 
 namespace blink {
 
@@ -51,6 +52,11 @@
   // ExecutionContext from which we will connect to |wake_lock_service_|.
   Member<ExecutionContext> execution_context_;
 
+  // Do not put a page into BackForwardCache if a page has acquired WakeLock.
+  // The page becomes cache-able when all locks are released.
+  FrameOrWorkerScheduler::SchedulingAffectingFeatureHandle
+      feature_handle_for_scheduler_;
+
   FRIEND_TEST_ALL_PREFIXES(WakeLockManagerTest, AcquireWakeLock);
   FRIEND_TEST_ALL_PREFIXES(WakeLockManagerTest, ReleaseAllWakeLocks);
   FRIEND_TEST_ALL_PREFIXES(WakeLockManagerTest, ReleaseOneWakeLock);
diff --git a/third_party/blink/renderer/modules/webcodecs/fuzzer_utils.cc b/third_party/blink/renderer/modules/webcodecs/fuzzer_utils.cc
index c9c72b2..c259ce9 100644
--- a/third_party/blink/renderer/modules/webcodecs/fuzzer_utils.cc
+++ b/third_party/blink/renderer/modules/webcodecs/fuzzer_utils.cc
@@ -9,7 +9,9 @@
 #include "third_party/blink/renderer/bindings/core/v8/script_function.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_image_bitmap_options.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_audio_chunk_init.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_audio_config.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_video_chunk_init.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_config.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_init.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_video_encoder_config.h"
@@ -17,8 +19,6 @@
 #include "third_party/blink/renderer/core/html/canvas/image_data.h"
 #include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h"
 #include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
-#include "third_party/blink/renderer/modules/webcodecs/encoded_audio_chunk_init.h"
-#include "third_party/blink/renderer/modules/webcodecs/encoded_video_chunk_init.h"
 #include "third_party/blink/renderer/modules/webcodecs/fuzzer_inputs.pb.h"
 #include "third_party/blink/renderer/modules/webcodecs/video_frame.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
diff --git a/third_party/blink/renderer/platform/graphics/compositing/content_layer_client_impl.h b/third_party/blink/renderer/platform/graphics/compositing/content_layer_client_impl.h
index a9b13f4..5a1164a0 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/content_layer_client_impl.h
+++ b/third_party/blink/renderer/platform/graphics/compositing/content_layer_client_impl.h
@@ -34,8 +34,7 @@
   gfx::Rect PaintableRegion() override {
     return gfx::Rect(raster_invalidator_.LayerBounds().size());
   }
-  scoped_refptr<cc::DisplayItemList> PaintContentsToDisplayList(
-      PaintingControlSetting) override {
+  scoped_refptr<cc::DisplayItemList> PaintContentsToDisplayList() override {
     return cc_display_item_list_;
   }
   bool FillsBoundsCompletely() const override { return false; }
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
index 8a422bc5..1edd815 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
@@ -1016,8 +1016,8 @@
   path_ = path;
 }
 
-scoped_refptr<cc::DisplayItemList> SynthesizedClip::PaintContentsToDisplayList(
-    PaintingControlSetting) {
+scoped_refptr<cc::DisplayItemList>
+SynthesizedClip::PaintContentsToDisplayList() {
   auto cc_list = base::MakeRefCounted<cc::DisplayItemList>(
       cc::DisplayItemList::kTopLevelDisplayItemList);
   PaintFlags flags;
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h
index 147c8a5f..884a5f3 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h
@@ -99,8 +99,7 @@
   bool FillsBoundsCompletely() const final { return false; }
   size_t GetApproximateUnsharedMemoryUsage() const final { return 0; }
 
-  scoped_refptr<cc::DisplayItemList> PaintContentsToDisplayList(
-      PaintingControlSetting) final;
+  scoped_refptr<cc::DisplayItemList> PaintContentsToDisplayList() final;
 
  private:
   scoped_refptr<cc::PictureLayer> layer_;
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
index ad71c43..f7e0a0ed 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
@@ -4876,8 +4876,7 @@
              .Build());
   ASSERT_EQ(1u, LayerCount());
   auto* layer = static_cast<cc::PictureLayer*>(LayerAt(0));
-  auto display_item_list = layer->client()->PaintContentsToDisplayList(
-      cc::ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+  auto display_item_list = layer->client()->PaintContentsToDisplayList();
 
   // Change t1 but not t2.
   layer->ClearSubtreePropertyChangedForTesting();
@@ -4895,10 +4894,7 @@
   ASSERT_EQ(1u, LayerCount());
   ASSERT_EQ(layer, LayerAt(0));
   EXPECT_EQ(display_item_list.get(),
-            layer->client()
-                ->PaintContentsToDisplayList(
-                    cc::ContentLayerClient::PAINTING_BEHAVIOR_NORMAL)
-                .get());
+            layer->client()->PaintContentsToDisplayList().get());
   // TODO(wangxianzhu): Probably avoid setting this flag on transform change.
   EXPECT_TRUE(layer->subtree_property_changed());
   // This is set by cc when propagating ancestor change flag to descendants.
@@ -4925,10 +4921,7 @@
   ASSERT_EQ(1u, LayerCount());
   ASSERT_EQ(layer, LayerAt(0));
   EXPECT_EQ(display_item_list.get(),
-            layer->client()
-                ->PaintContentsToDisplayList(
-                    cc::ContentLayerClient::PAINTING_BEHAVIOR_NORMAL)
-                .get());
+            layer->client()->PaintContentsToDisplayList().get());
   // TODO(wangxianzhu): Probably avoid setting this flag on transform change.
   EXPECT_TRUE(layer->subtree_property_changed());
   EXPECT_TRUE(GetTransformNode(layer).transform_changed);
@@ -4952,10 +4945,7 @@
   ASSERT_EQ(1u, LayerCount());
   ASSERT_EQ(layer, LayerAt(0));
   EXPECT_EQ(display_item_list.get(),
-            layer->client()
-                ->PaintContentsToDisplayList(
-                    cc::ContentLayerClient::PAINTING_BEHAVIOR_NORMAL)
-                .get());
+            layer->client()->PaintContentsToDisplayList().get());
   // The new transform is decomposited, so there is no transform_changed, but
   // we set subtree_property_changed because offset_from_transform_parent
   // (calculated from the decomposited transforms) changed.
@@ -4975,10 +4965,7 @@
   ASSERT_EQ(1u, LayerCount());
   ASSERT_EQ(layer, LayerAt(0));
   EXPECT_NE(display_item_list.get(),
-            layer->client()
-                ->PaintContentsToDisplayList(
-                    cc::ContentLayerClient::PAINTING_BEHAVIOR_NORMAL)
-                .get());
+            layer->client()->PaintContentsToDisplayList().get());
 }
 
 TEST_P(PaintArtifactCompositorTest, EffectChange) {
diff --git a/third_party/blink/renderer/platform/graphics/graphics_layer.cc b/third_party/blink/renderer/platform/graphics/graphics_layer.cc
index dd12b90..eb4435b 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_layer.cc
+++ b/third_party/blink/renderer/platform/graphics/graphics_layer.cc
@@ -265,12 +265,15 @@
 }
 
 void GraphicsLayer::PaintRecursively(
-    HashSet<const GraphicsLayer*>& repainted_layers) {
-  ForAllPaintingGraphicsLayers(*this,
-                               [&repainted_layers](GraphicsLayer& layer) {
-                                 if (layer.Paint())
-                                   repainted_layers.insert(&layer);
-                               });
+    HashSet<const GraphicsLayer*>& repainted_layers,
+    PaintBenchmarkMode benchmark_mode) {
+  ForAllPaintingGraphicsLayers(
+      *this, [benchmark_mode, &repainted_layers](GraphicsLayer& layer) {
+        PaintController::ScopedBenchmarkMode scoped_benchmark_mode(
+            layer.GetPaintController(), benchmark_mode);
+        if (layer.Paint())
+          repainted_layers.insert(&layer);
+      });
 
 #if DCHECK_IS_ON()
   if (!repainted_layers.IsEmpty()) {
@@ -297,7 +300,9 @@
   if (PaintWithoutCommit()) {
     GetPaintController().CommitNewDisplayItems();
     UpdateShouldCreateLayersAfterPaint();
-  } else if (!needs_check_raster_invalidation_) {
+  } else if (!needs_check_raster_invalidation_ &&
+             GetPaintController().GetBenchmarkMode() !=
+                 PaintBenchmarkMode::kForceRasterInvalidationAndConvert) {
     return false;
   }
 
@@ -312,6 +317,22 @@
         raster_invalidation_function_,
         GetPaintController().GetPaintArtifactShared(), layer_bounds,
         layer_state_->state.Unalias(), this);
+
+    base::Optional<RasterUnderInvalidationCheckingParams>
+        raster_under_invalidation_params;
+    if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled() &&
+        PaintsContentOrHitTest()) {
+      raster_under_invalidation_params.emplace(
+          EnsureRasterInvalidator().EnsureTracking(), InterestRect(),
+          DebugName());
+    }
+
+    cc_display_item_list_ = PaintChunksToCcLayer::Convert(
+        GetPaintController().PaintChunks(), layer_state_->state.Unalias(),
+        gfx::Vector2dF(layer_state_->offset.X(), layer_state_->offset.Y()),
+        GetPaintController().GetPaintArtifact().GetDisplayItemList(),
+        cc::DisplayItemList::kTopLevelDisplayItemList,
+        base::OptionalOrNullptr(raster_under_invalidation_params));
   }
 
   needs_check_raster_invalidation_ = false;
@@ -374,9 +395,9 @@
     interest_rect = &new_interest_rect;
   }
 
-  if (!GetPaintController().ShouldForcePaintForBenchmark() &&
-      !client_.NeedsRepaint(*this) &&
-      !GetPaintController().CacheIsAllInvalid() &&
+  PaintController& paint_controller = GetPaintController();
+  if (!paint_controller.ShouldForcePaintForBenchmark() &&
+      !client_.NeedsRepaint(*this) && !paint_controller.CacheIsAllInvalid() &&
       previous_interest_rect_ == *interest_rect) {
     GetPaintController().UpdateUMACountsOnFullyCached();
     return false;
@@ -384,8 +405,8 @@
 
   GraphicsContext context(GetPaintController());
   DCHECK(layer_state_) << "No layer state for GraphicsLayer: " << DebugName();
-  GetPaintController().UpdateCurrentPaintChunkProperties(nullptr,
-                                                         layer_state_->state);
+  paint_controller.UpdateCurrentPaintChunkProperties(nullptr,
+                                                     layer_state_->state);
 
   previous_interest_rect_ = *interest_rect;
   client_.PaintContents(this, context, painting_phase_, *interest_rect);
@@ -706,48 +727,9 @@
   client_.GraphicsLayersDidChange();
 }
 
-scoped_refptr<cc::DisplayItemList> GraphicsLayer::PaintContentsToDisplayList(
-    PaintingControlSetting painting_control) {
+scoped_refptr<cc::DisplayItemList> GraphicsLayer::PaintContentsToDisplayList() {
   DCHECK(!ShouldCreateLayersAfterPaint());
-  TRACE_EVENT0("blink,benchmark", "GraphicsLayer::PaintContents");
-
-  if (painting_control == SUBSEQUENCE_CACHING_DISABLED)
-    PaintController::SetSubsequenceCachingDisabledForBenchmark();
-  else if (painting_control == PARTIAL_INVALIDATION)
-    PaintController::SetPartialInvalidationForBenchmark();
-
-  PaintController& paint_controller = GetPaintController();
-  // We also disable caching when Painting or Construction are disabled. In both
-  // cases we would like to compare assuming the full cost of recording, not the
-  // cost of re-using cached content.
-  if (painting_control == DISPLAY_LIST_CACHING_DISABLED)
-    paint_controller.InvalidateAll();
-
-  // Anything other than PAINTING_BEHAVIOR_NORMAL is for testing. In non-testing
-  // scenarios, it is an error to call GraphicsLayer::Paint. Actual painting
-  // occurs in LocalFrameView::PaintTree() which calls GraphicsLayer::Paint();
-  // this method merely copies the painted output to the cc::DisplayItemList.
-  if (painting_control != PAINTING_BEHAVIOR_NORMAL)
-    Paint();
-
-  base::Optional<RasterUnderInvalidationCheckingParams>
-      raster_under_invalidation_params;
-  if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled() &&
-      PaintsContentOrHitTest()) {
-    raster_under_invalidation_params.emplace(
-        EnsureRasterInvalidator().EnsureTracking(), InterestRect(),
-        DebugName());
-  }
-
-  PaintController::ClearFlagsForBenchmark();
-
-  DCHECK(layer_state_) << "No layer state for GraphicsLayer: " << DebugName();
-  return PaintChunksToCcLayer::Convert(
-      GetPaintController().PaintChunks(), layer_state_->state.Unalias(),
-      gfx::Vector2dF(layer_state_->offset.X(), layer_state_->offset.Y()),
-      paint_controller.GetPaintArtifact().GetDisplayItemList(),
-      cc::DisplayItemList::kTopLevelDisplayItemList,
-      base::OptionalOrNullptr(raster_under_invalidation_params));
+  return cc_display_item_list_;
 }
 
 size_t GraphicsLayer::GetApproximateUnsharedMemoryUsage() const {
diff --git a/third_party/blink/renderer/platform/graphics/graphics_layer.h b/third_party/blink/renderer/platform/graphics/graphics_layer.h
index c441191..a5eb8db 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_layer.h
+++ b/third_party/blink/renderer/platform/graphics/graphics_layer.h
@@ -58,6 +58,7 @@
 #include "third_party/skia/include/core/SkRefCnt.h"
 
 namespace cc {
+class DisplayItemList;
 class PictureLayer;
 }  // namespace cc
 
@@ -180,7 +181,8 @@
                                PaintInvalidationReason);
 
   IntRect InterestRect();
-  void PaintRecursively(HashSet<const GraphicsLayer*>& repainted_layers);
+  void PaintRecursively(HashSet<const GraphicsLayer*>& repainted_layers,
+                        PaintBenchmarkMode = PaintBenchmarkMode::kNormal);
   // Returns true if this layer is repainted.
   bool Paint();
 
@@ -240,8 +242,7 @@
 
   // cc::ContentLayerClient implementation.
   gfx::Rect PaintableRegion() final { return InterestRect(); }
-  scoped_refptr<cc::DisplayItemList> PaintContentsToDisplayList(
-      PaintingControlSetting painting_control) final;
+  scoped_refptr<cc::DisplayItemList> PaintContentsToDisplayList() final;
   bool FillsBoundsCompletely() const override { return false; }
   size_t GetApproximateUnsharedMemoryUsage() const final;
 
@@ -297,6 +298,7 @@
 
   scoped_refptr<cc::PictureLayer> layer_;
   scoped_refptr<cc::Layer> contents_layer_;
+  scoped_refptr<cc::DisplayItemList> cc_display_item_list_;
 
   SquashingDisallowedReasons squashing_disallowed_reasons_ =
       SquashingDisallowedReason::kNone;
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc b/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc
index 353434f..700d609 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc
@@ -35,57 +35,15 @@
   }
 }
 
-// For micro benchmarks of record time.
-static bool g_subsequence_caching_disabled = false;
-static bool g_partial_invalidation = false;
-static int g_partial_invalidation_display_item_count = 0;
-static int g_partial_invalidation_subsequence_count = 0;
-
-// This is used to invalidate one out of every |kInvalidateDisplayItemInterval|
-// display items for the micro benchmark of record time with partial
-// invalidation.
-static bool ShouldInvalidateDisplayItemForBenchmark() {
-  constexpr int kInvalidateDisplayItemInterval = 8;
-  return g_partial_invalidation &&
-         !(g_partial_invalidation_display_item_count++ %
-           kInvalidateDisplayItemInterval);
-}
-// Similar to the above, but for subsequences.
-static bool ShouldInvalidateSubsequenceForBenchmark() {
-  constexpr int kInvalidateSubsequenceInterval = 2;
-  return g_partial_invalidation &&
-         !(g_partial_invalidation_subsequence_count++ %
-           kInvalidateSubsequenceInterval);
-}
-
-void PaintController::SetSubsequenceCachingDisabledForBenchmark() {
-  g_subsequence_caching_disabled = true;
-}
-
-void PaintController::SetPartialInvalidationForBenchmark() {
-  g_partial_invalidation = true;
-  g_partial_invalidation_display_item_count = 0;
-  g_partial_invalidation_subsequence_count = 0;
-}
-
-bool PaintController::ShouldForcePaintForBenchmark() {
-  return g_subsequence_caching_disabled || g_partial_invalidation;
-}
-
-void PaintController::ClearFlagsForBenchmark() {
-  g_subsequence_caching_disabled = false;
-  g_partial_invalidation = false;
-}
-
 bool PaintController::UseCachedItemIfPossible(const DisplayItemClient& client,
                                               DisplayItem::Type type) {
   if (usage_ == kTransient)
     return false;
 
-  if (!ClientCacheIsValid(client))
+  if (ShouldInvalidateDisplayItemForBenchmark())
     return false;
 
-  if (ShouldInvalidateDisplayItemForBenchmark())
+  if (!ClientCacheIsValid(client))
     return false;
 
   if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled() &&
@@ -132,9 +90,6 @@
   if (usage_ == kTransient)
     return false;
 
-  if (g_subsequence_caching_disabled)
-    return false;
-
   if (ShouldInvalidateSubsequenceForBenchmark())
     return false;
 
@@ -848,4 +803,38 @@
   sum_num_cached_subsequences_ = 0;
 }
 
+bool PaintController::ShouldInvalidateDisplayItemForBenchmark() {
+  if (benchmark_mode_ == PaintBenchmarkMode::kCachingDisabled)
+    return true;
+
+  // For kPartialInvalidation, invalidate one out of every
+  // |kInvalidateDisplayItemInterval| display items for the micro benchmark of
+  // record time with partial invalidation.
+  constexpr int kInvalidateDisplayItemInterval = 8;
+  return benchmark_mode_ == PaintBenchmarkMode::kPartialInvalidation &&
+         !(partial_invalidation_display_item_count_++ %
+           kInvalidateDisplayItemInterval);
+}
+
+bool PaintController::ShouldInvalidateSubsequenceForBenchmark() {
+  if (benchmark_mode_ == PaintBenchmarkMode::kCachingDisabled ||
+      benchmark_mode_ == PaintBenchmarkMode::kSubsequenceCachingDisabled)
+    return true;
+
+  // Similar to the ShouldInvalidateDisplayItemsForBenchmark(), but for
+  // subsequences.
+  constexpr int kInvalidateSubsequenceInterval = 2;
+  return benchmark_mode_ == PaintBenchmarkMode::kPartialInvalidation &&
+         !(partial_invalidation_subsequence_count_++ %
+           kInvalidateSubsequenceInterval);
+}
+
+void PaintController::SetBenchmarkMode(PaintBenchmarkMode mode) {
+  benchmark_mode_ = mode;
+  if (mode == PaintBenchmarkMode::kPartialInvalidation) {
+    partial_invalidation_display_item_count_ = 0;
+    partial_invalidation_subsequence_count_ = 0;
+  }
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_controller.h b/third_party/blink/renderer/platform/graphics/paint/paint_controller.h
index 5a80c63..0a718a8 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_controller.h
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_controller.h
@@ -31,6 +31,18 @@
 
 static constexpr wtf_size_t kInitialDisplayItemListCapacityBytes = 512;
 
+enum class PaintBenchmarkMode {
+  kNormal,
+  kForceRasterInvalidationAndConvert,
+  kForcePaintArtifactCompositorUpdate,
+  kForcePaint,
+  // The above modes don't additionally invalidate paintings, i.e. during
+  // repeated benchmarking, the PaintController is fully cached.
+  kPartialInvalidation,
+  kSubsequenceCachingDisabled,
+  kCachingDisabled,
+};
+
 // FrameFirstPaint stores first-paint, text or image painted for the
 // corresponding frame. They are never reset to false. First-paint is defined in
 // https://github.com/WICG/paint-timing. It excludes default background paint.
@@ -218,11 +230,29 @@
     return GetPaintArtifact().PaintChunks();
   }
 
-  // For micro benchmarks of record time.
-  static void SetSubsequenceCachingDisabledForBenchmark();
-  static void SetPartialInvalidationForBenchmark();
-  static bool ShouldForcePaintForBenchmark();
-  static void ClearFlagsForBenchmark();
+  class ScopedBenchmarkMode {
+    STACK_ALLOCATED();
+
+   public:
+    ScopedBenchmarkMode(PaintController& paint_controller,
+                        PaintBenchmarkMode mode)
+        : paint_controller_(paint_controller) {
+      // Nesting is not allowed.
+      DCHECK_EQ(PaintBenchmarkMode::kNormal, paint_controller_.benchmark_mode_);
+      paint_controller.SetBenchmarkMode(mode);
+    }
+    ~ScopedBenchmarkMode() {
+      paint_controller_.SetBenchmarkMode(PaintBenchmarkMode::kNormal);
+    }
+
+   private:
+    PaintController& paint_controller_;
+  };
+
+  PaintBenchmarkMode GetBenchmarkMode() const { return benchmark_mode_; }
+  bool ShouldForcePaintForBenchmark() {
+    return benchmark_mode_ >= PaintBenchmarkMode::kForcePaint;
+  }
 
   void SetFirstPainted();
   void SetTextPainted();
@@ -383,6 +413,10 @@
 
   void UpdateUMACounts();
 
+  void SetBenchmarkMode(PaintBenchmarkMode);
+  bool ShouldInvalidateDisplayItemForBenchmark();
+  bool ShouldInvalidateSubsequenceForBenchmark();
+
   Usage usage_;
 
   // The last paint artifact after CommitNewDisplayItems().
@@ -452,6 +486,10 @@
 
   wtf_size_t current_fragment_ = 0;
 
+  PaintBenchmarkMode benchmark_mode_ = PaintBenchmarkMode::kNormal;
+  int partial_invalidation_display_item_count_ = 0;
+  int partial_invalidation_subsequence_count_ = 0;
+
   // Accumulated counts for UMA metrics. Updated by UpdateUMACounts() and
   // UpdateUMACountsOnFullyCached(), and reported as UMA metrics and reset by
   // ReportUMACounts(). The accumulation is mainly for pre-CompositeAfterPaint
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index f7734f43..f7c4a557 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1006,7 +1006,7 @@
     {
       name: "LayoutNGForControls",
       depends_on: ["LayoutNG"],
-      status: "experimental",
+      status: "stable",
     },
     {
       name: "LayoutNGFragmentItem",
@@ -1637,7 +1637,7 @@
     // Enables the use of |RTCRtpTransceiver::stop()|
     {
       name: "RTCRtpTransceiverStop",
-      status: "experimental",
+      status: "stable",
     },
     {
       name: "RTCStatsRelativePacketArrivalDelay",
diff --git a/third_party/blink/renderer/platform/scheduler/common/scheduling_policy.cc b/third_party/blink/renderer/platform/scheduler/common/scheduling_policy.cc
index bd68c2f7..11758a5 100644
--- a/third_party/blink/renderer/platform/scheduler/common/scheduling_policy.cc
+++ b/third_party/blink/renderer/platform/scheduler/common/scheduling_policy.cc
@@ -29,6 +29,7 @@
     case Feature::kPortal:
     case Feature::kSpeechRecognizer:
     case Feature::kSpeechSynthesis:
+    case Feature::kWakeLock:
       return false;
     case Feature::kMainResourceHasCacheControlNoStore:
     case Feature::kMainResourceHasCacheControlNoCache:
@@ -50,7 +51,6 @@
     case Feature::kRequestedBackForwardCacheBlockedSensors:
     case Feature::kRequestedBackgroundWorkPermission:
     case Feature::kWebLocks:
-    case Feature::kWakeLock:
     case Feature::kRequestedStorageAccessGrant:
     case Feature::kWebNfc:
     case Feature::kWebFileSystem:
diff --git a/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.cc b/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.cc
index 96c4c11..1c02cf2c 100644
--- a/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.cc
+++ b/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.cc
@@ -40,10 +40,6 @@
 #include "third_party/blink/public/platform/scheduler/web_thread_scheduler.h"
 #include "ui/gfx/presentation_feedback.h"
 
-namespace base {
-class Value;
-}
-
 namespace cc {
 class Layer;
 }
@@ -344,6 +340,12 @@
                                         first_scroll_timestamp);
 }
 
+void LayerTreeView::RunPaintBenchmark(int repeat_count,
+                                      cc::PaintBenchmarkResult& result) {
+  if (delegate_)
+    delegate_->RunPaintBenchmark(repeat_count, result);
+}
+
 void LayerTreeView::DidScheduleBeginMainFrame() {
   if (!delegate_ || !web_main_thread_scheduler_)
     return;
diff --git a/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.h b/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.h
index 85c7510..9438140 100644
--- a/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.h
+++ b/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.h
@@ -12,7 +12,6 @@
 #include "base/memory/weak_ptr.h"
 #include "base/single_thread_task_runner.h"
 #include "base/time/time.h"
-#include "base/values.h"
 #include "cc/input/browser_controls_state.h"
 #include "cc/trees/layer_tree_host_client.h"
 #include "cc/trees/layer_tree_host_single_thread_client.h"
@@ -107,6 +106,8 @@
   void DidObserveFirstScrollDelay(
       base::TimeDelta first_scroll_delay,
       base::TimeTicks first_scroll_timestamp) override;
+  void RunPaintBenchmark(int repeat_count,
+                         cc::PaintBenchmarkResult& result) override;
 
   // cc::LayerTreeHostSingleThreadClient implementation.
   void DidSubmitCompositorFrame() override;
diff --git a/third_party/blink/renderer/platform/widget/compositing/layer_tree_view_delegate.h b/third_party/blink/renderer/platform/widget/compositing/layer_tree_view_delegate.h
index 680604a..52e3cdb 100644
--- a/third_party/blink/renderer/platform/widget/compositing/layer_tree_view_delegate.h
+++ b/third_party/blink/renderer/platform/widget/compositing/layer_tree_view_delegate.h
@@ -115,6 +115,9 @@
   // perform actual painting work.
   virtual void WillBeginMainFrame() = 0;
 
+  virtual void RunPaintBenchmark(int repeat_count,
+                                 cc::PaintBenchmarkResult& result) {}
+
  protected:
   virtual ~LayerTreeViewDelegate() {}
 };
diff --git a/third_party/blink/renderer/platform/widget/widget_base.cc b/third_party/blink/renderer/platform/widget/widget_base.cc
index 048460f..f7b9702 100644
--- a/third_party/blink/renderer/platform/widget/widget_base.cc
+++ b/third_party/blink/renderer/platform/widget/widget_base.cc
@@ -512,6 +512,11 @@
   UpdateTextInputState();
 }
 
+void WidgetBase::RunPaintBenchmark(int repeat_count,
+                                   cc::PaintBenchmarkResult& result) {
+  client_->RunPaintBenchmark(repeat_count, result);
+}
+
 void WidgetBase::SetCompositorVisible(bool visible) {
   if (never_composited_)
     return;
diff --git a/third_party/blink/renderer/platform/widget/widget_base.h b/third_party/blink/renderer/platform/widget/widget_base.h
index 6514596..cb841555 100644
--- a/third_party/blink/renderer/platform/widget/widget_base.h
+++ b/third_party/blink/renderer/platform/widget/widget_base.h
@@ -141,6 +141,8 @@
   void EndUpdateLayers() override;
   void UpdateVisualState() override;
   void WillBeginMainFrame() override;
+  void RunPaintBenchmark(int repeat_count,
+                         cc::PaintBenchmarkResult& result) override;
 
   cc::AnimationHost* AnimationHost() const;
   cc::LayerTreeHost* LayerTreeHost() const;
diff --git a/third_party/blink/renderer/platform/widget/widget_base_client.h b/third_party/blink/renderer/platform/widget/widget_base_client.h
index 8de569c..2b462c49 100644
--- a/third_party/blink/renderer/platform/widget/widget_base_client.h
+++ b/third_party/blink/renderer/platform/widget/widget_base_client.h
@@ -204,6 +204,9 @@
 
   // Inform the widget that it was shown.
   virtual void WasShown(bool was_evicted) {}
+
+  virtual void RunPaintBenchmark(int repeat_count,
+                                 cc::PaintBenchmarkResult& result) {}
 };
 
 }  // namespace blink
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 e9fa69e7..db0f24ef 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
@@ -599,6 +599,7 @@
             'cc::ActiveFrameSequenceTrackers',
             'cc::ApplyViewportChangesArgs',
             'cc::LayerTreeSettings',
+            'cc::PaintBenchmarkResult',
             'cc::TaskGraphRunner',
             'gfx::DisplayColorSpaces',
             'ui::ImeTextSpan',
@@ -1172,8 +1173,10 @@
     },
     {
         'paths': ['third_party/blink/renderer/core/frame/local_frame_view.cc'],
-        'allowed':
-        ['cc::frame_viewer_instrumentation::IsTracingLayerTreeSnapshots'],
+        'allowed': [
+            'base::LapTimer',
+            'cc::frame_viewer_instrumentation::IsTracingLayerTreeSnapshots',
+        ],
     },
     {
         'paths': [
diff --git a/third_party/blink/web_tests/SlowTests b/third_party/blink/web_tests/SlowTests
index b00ea4d..5c579df 100644
--- a/third_party/blink/web_tests/SlowTests
+++ b/third_party/blink/web_tests/SlowTests
@@ -538,3 +538,5 @@
 
 crbug.com/1104091 [ Linux ] webcodecs/videoframe-imagebitmap.html [ Slow ]
 crbug.com/1093478 external/wpt/quirks/unitless-length/limited-quirks.html [ Slow ]
+
+crbug.com/1133836 external/wpt/scroll-to-text-fragment/redirects.html [ Slow ]
diff --git a/third_party/blink/web_tests/external/wpt/svg/painting/mask-containing-image-with-clip-path.svg b/third_party/blink/web_tests/external/wpt/svg/painting/mask-containing-image-with-clip-path.svg
new file mode 100644
index 0000000..faeec28
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/svg/painting/mask-containing-image-with-clip-path.svg
@@ -0,0 +1,31 @@
+<svg class="reftest-wait" xmlns="http://www.w3.org/2000/svg"
+  xmlns:html="http://www.w3.org/1999/xhtml">
+  <html:script src="/common/reftest-wait.js"/>
+  <html:script src="/common/rendering-utils.js"/>
+  <html:link rel="match" href="../embedded/reference/green-rect-100x100.svg"/>
+  <script>
+<![CDATA[
+  function loadImage() {
+    waitForAtLeastOneFrame().then(() => {
+      var clip = document.getElementById('clip');
+      clip.setAttribute('width', '100');
+      clip.setAttribute('height', '100');
+      waitForAtLeastOneFrame().then(takeScreenshot);
+    });
+  }
+]]>
+  </script>
+  <defs>
+    <mask id="mask">
+      <g clip-path="url(#clip_path)">
+        <image onload="loadImage()" width="100px" height="100px" href="support/white-rect-100x100.svg"/>
+      </g>
+    </mask>
+    <clipPath id="clip_path">
+      <rect id="clip"/>
+    </clipPath>
+  </defs>
+  <g mask="url(#mask)">
+    <rect width="100" height="100" fill="green"/>
+  </g>
+</svg>
diff --git a/third_party/blink/web_tests/external/wpt/svg/painting/support/white-rect-100x100.svg b/third_party/blink/web_tests/external/wpt/svg/painting/support/white-rect-100x100.svg
new file mode 100644
index 0000000..6ee3841
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/svg/painting/support/white-rect-100x100.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+  <rect width="100" height="100" fill="white"/>
+</svg>
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 c858492b..90b779b 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
@@ -5583,6 +5583,7 @@
     getter stopped
     method constructor
     method setCodecPreferences
+    method stop
     setter direction
 interface RTCSctpTransport : EventTarget
     attribute @@toStringTag
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 4fc9e54..91a366d 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -24685,6 +24685,7 @@
   <int value="1510" label="ACCESSIBILITY_PRIVATE_MOVEMAGNIFIERTORECT"/>
   <int value="1511" label="FILEMANAGERPRIVATE_SINGLEPARTITIONFORMAT"/>
   <int value="1512" label="TABS_REMOVECSS"/>
+  <int value="1513" label="IDENTITY_CLEARALLCACHEDAUTHTOKENS"/>
 </enum>
 
 <enum name="ExtensionIconState">
@@ -30822,6 +30823,9 @@
   <int value="11" label="FORMAT_SUCCESS"/>
   <int value="12" label="FORMAT_FAIL"/>
   <int value="13" label="RENAME_FAIL"/>
+  <int value="14" label="PARTITION_START"/>
+  <int value="15" label="PARTITION_SUCCESS"/>
+  <int value="16" label="PARTITION_FAIL"/>
 </enum>
 
 <enum name="FileManagerNotificationUserAction">
diff --git a/tools/metrics/histograms/generate_expired_histograms_array.py b/tools/metrics/histograms/generate_expired_histograms_array.py
index bd0b321..c6591c3 100755
--- a/tools/metrics/histograms/generate_expired_histograms_array.py
+++ b/tools/metrics/histograms/generate_expired_histograms_array.py
@@ -233,7 +233,15 @@
       arguments.major_branch_date_filepath: File path for base date.
       arguments.milestone_filepath: File path for milestone information.
   """
-  descriptions = merge_xml.MergeFiles(histogram_paths.ALL_XMLS)
+  # TODO(sweilun): Assert that the |--inputs| is the same as
+  # |histogram_paths.ALL_XMLS| to make sure we have the most updated list of
+  # histogram descriptions. Otherwise, inform the cl owner to update the
+  # --inputs.
+  # assert histogram_paths.ALL_XMLS == arguments.inputs, "The --inputs is not "
+  # "sync with the most updated list of xmls. Please update the inputs in "
+  # "chrome/browser/metrics/BUILD.gn and ios/chrome/browser/metrics/BUILD.gn."
+
+  descriptions = merge_xml.MergeFiles(arguments.inputs)
   with open(arguments.major_branch_date_filepath, "r") as date_file:
     branch_file_content = date_file.read()
   with open(arguments.milestone_filepath, "r") as milestone_file:
@@ -278,6 +286,10 @@
       "-m",
       required=True,
       help="A path to the file with the milestone information.")
+  arg_parser.add_argument(
+      "inputs",
+      nargs="+",
+      help="Paths to .xml files with histogram descriptions.")
   return arg_parser.parse_args()
 
 
diff --git a/tools/metrics/histograms/histograms_xml/arc/histograms.xml b/tools/metrics/histograms/histograms_xml/arc/histograms.xml
index 3399fb0f..9c12b18 100644
--- a/tools/metrics/histograms/histograms_xml/arc/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/arc/histograms.xml
@@ -874,7 +874,7 @@
 <!-- Name completed by histogram_suffixes name="ArcPerformanceAppCategories" -->
 
   <owner>khmel@google.com</owner>
-  <owner>skuhne@google.com</owner>
+  <owner>camurcu@google.com</owner>
   <summary>Standard deviation for commit time delta from ideal time.</summary>
 </histogram>
 
@@ -883,16 +883,36 @@
 <!-- Name completed by histogram_suffixes name="ArcPerformanceAppCategories" -->
 
   <owner>khmel@google.com</owner>
-  <owner>skuhne@google.com</owner>
+  <owner>camurcu@google.com</owner>
   <summary>Render frames per second.</summary>
 </histogram>
 
+<histogram name="Arc.Runtime.Performance.Generic.FrameTime" units="ms"
+    expires_after="2021-09-28">
+  <owner>camurcu@google.com</owner>
+  <owner>khmel@google.com</owner>
+  <summary>
+    95 percent of the frames in the first 5 minutes after app launch took
+    shorter time (in ms) than this value.
+  </summary>
+</histogram>
+
+<histogram name="Arc.Runtime.Performance.Generic.Jankiness" units="%"
+    expires_after="2021-09-28">
+  <owner>camurcu@google.com</owner>
+  <owner>khmel@google.com</owner>
+  <summary>
+    Percentage ratio of janky frames to total frames recorded in a 5 minute
+    interval.
+  </summary>
+</histogram>
+
 <histogram name="Arc.Runtime.Performance.RenderQuality" units="%"
     expires_after="2021-03-11">
 <!-- Name completed by histogram_suffixes name="ArcPerformanceAppCategories" -->
 
   <owner>khmel@google.com</owner>
-  <owner>skuhne@google.com</owner>
+  <owner>camurcu@google.com</owner>
   <summary>Render quality with maximum 100%.</summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/histograms_xml/gpu/histograms.xml b/tools/metrics/histograms/histograms_xml/gpu/histograms.xml
index 3b26b772..1dd7dbf 100644
--- a/tools/metrics/histograms/histograms_xml/gpu/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/gpu/histograms.xml
@@ -366,6 +366,17 @@
   </summary>
 </histogram>
 
+<histogram name="GPU.DirectComposition.DCLayer.YUVOverlayCount"
+    units="overlays" expires_after="2021-03-15">
+  <owner>magchen@chromium.org</owner>
+  <owner>zmo@chromium.org</owner>
+  <summary>
+    The number of YUV overlays we are going to present in each frame if the
+    number is not 0. Recorded when the overlay processor is called for drawing a
+    frame.
+  </summary>
+</histogram>
+
 <histogram name="GPU.DirectComposition.DCLayerResult.Texture"
     enum="DCLayerResult" expires_after="2020-12-31">
   <owner>sunnyps@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml b/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
index 709ae1e..87c4339 100644
--- a/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
+++ b/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
@@ -13698,6 +13698,9 @@
 <histogram_suffixes name="PermissionPromptDisposition" separator=".">
   <suffix name="AnchoredBubble"
       label="A bubble under the site settings padlock"/>
+  <suffix name="LocationBarLeftChip"
+      label="A chip on the left-hand side of the location bar that shows a
+             bubble when clicked"/>
   <suffix name="LocationBarRightAnimatedIcon"
       label="An animated indicator on the right-hand side of the location bar"/>
   <suffix name="LocationBarRightStaticIcon"
@@ -13705,7 +13708,8 @@
   <suffix name="MiniInfobar"
       label="An initially-collapsed infobar at the bottom of the page"/>
   <suffix name="ModalDialog" label="A modal dialog"/>
-  <suffix name="NotApplicable" label="No permission prompt"/>
+  <suffix name="NoneVisible" label="There was no UI being shown"/>
+  <suffix name="NotApplicable" label="No permission prompt at all"/>
   <affected-histogram name="Permissions.Action.WithDisposition"/>
 </histogram_suffixes>
 
diff --git a/tools/metrics/histograms/histograms_xml/ios/histograms.xml b/tools/metrics/histograms/histograms_xml/ios/histograms.xml
index 8ca504d..ddaa28a 100644
--- a/tools/metrics/histograms/histograms_xml/ios/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/ios/histograms.xml
@@ -700,6 +700,22 @@
   </summary>
 </histogram>
 
+<histogram name="IOS.SiriShortcuts.Count" units="shortcuts"
+    expires_after="2021-02-28">
+  <owner>gujen@google.com</owner>
+  <owner>sebsg@chromium.org</owner>
+  <summary>
+    Counts the number of Chrome Siri Shortcuts that the user has created in the
+    Siri Shortcuts app. This is recorded once during startup. The histogram caps
+    at 20 shortcuts, which is an arbitrary but reasonable limit. Note that
+    shortcuts that have multiple actions are not counted if at least one action
+    isn't a Chrome-provided one. For example, a shortcut that opens URLs in
+    Chrome and then opens URLs in another app won't be counted. As such, this
+    metric undercounts the true number of Chrome shortcuts. This is a
+    restriction of the native Shortcuts API.
+  </summary>
+</histogram>
+
 <histogram name="IOS.Spotlight.Action" enum="IOSSpotlightAction"
     expires_after="2021-02-28">
   <owner>eugenebut@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/others/histograms.xml b/tools/metrics/histograms/histograms_xml/others/histograms.xml
index ee0e719d..e96d50f 100644
--- a/tools/metrics/histograms/histograms_xml/others/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/others/histograms.xml
@@ -6862,6 +6862,17 @@
   </summary>
 </histogram>
 
+<histogram name="IsolatedPrerender.SpareRenderer.CountStartedOnSRP"
+    units="count" expires_after="M90">
+  <owner>robertogden@chromium.org</owner>
+  <owner>ryansturm@chromium.org</owner>
+  <owner>tbansal@chromium.org</owner>
+  <summary>
+    Records the number of spare renderers that were attempted to be started on
+    the SRP, when the feature param is enabled.
+  </summary>
+</histogram>
+
 <histogram name="JSDialogs.OnBeforeUnloadStayVsLeave" enum="StayVsLeave"
     expires_after="M77">
   <owner>avi@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/sharing/histograms.xml b/tools/metrics/histograms/histograms_xml/sharing/histograms.xml
index 5a7059b0..222342c 100644
--- a/tools/metrics/histograms/histograms_xml/sharing/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/sharing/histograms.xml
@@ -22,7 +22,7 @@
 <histograms>
 
 <histogram name="Sharing.ClickToCallAppsToShow" units="apps"
-    expires_after="M88">
+    expires_after="M91">
 <!-- Name completed by histogram_suffixes name="SharingClickToCallUi" -->
 
   <owner>mvanouwerkerk@chromium.org</owner>
@@ -34,7 +34,7 @@
 </histogram>
 
 <histogram name="Sharing.ClickToCallDevicesToShow" units="devices"
-    expires_after="M88">
+    expires_after="M91">
 <!-- Name completed by histogram_suffixes name="SharingClickToCallUi" -->
 
   <owner>mvanouwerkerk@chromium.org</owner>
@@ -46,7 +46,7 @@
 </histogram>
 
 <histogram name="Sharing.ClickToCallDialerPresent" enum="BooleanPresent"
-    expires_after="M87">
+    expires_after="M91">
   <owner>mvanouwerkerk@chromium.org</owner>
   <owner>knollr@chromium.org</owner>
   <summary>
@@ -57,7 +57,7 @@
 </histogram>
 
 <histogram name="Sharing.ClickToCallDialogShown" enum="SharingDialogType"
-    expires_after="M88">
+    expires_after="M91">
   <owner>mvanouwerkerk@chromium.org</owner>
   <owner>knollr@chromium.org</owner>
   <summary>
@@ -68,6 +68,9 @@
 
 <histogram name="Sharing.ClickToCallPhoneNumberPrecompileTime" units="ms"
     expires_after="M87">
+  <obsolete>
+    Removed in M87.
+  </obsolete>
   <owner>knollr@chromium.org</owner>
   <owner>mvanouwerkerk@chromium.org</owner>
   <summary>
@@ -77,7 +80,7 @@
 </histogram>
 
 <histogram name="Sharing.ClickToCallSelectedAppIndex" units="index"
-    expires_after="M88">
+    expires_after="M91">
 <!-- Name completed by histogram_suffixes name="SharingClickToCallUi" -->
 
   <owner>mvanouwerkerk@chromium.org</owner>
@@ -89,7 +92,7 @@
 </histogram>
 
 <histogram name="Sharing.ClickToCallSelectedDeviceIndex" units="index"
-    expires_after="M88">
+    expires_after="M91">
 <!-- Name completed by histogram_suffixes name="SharingClickToCallUi" -->
 
   <owner>mvanouwerkerk@chromium.org</owner>
diff --git a/tools/perf/measurements/rasterize_and_record_micro.py b/tools/perf/measurements/rasterize_and_record_micro.py
index 75ab939..399404a 100644
--- a/tools/perf/measurements/rasterize_and_record_micro.py
+++ b/tools/perf/measurements/rasterize_and_record_micro.py
@@ -64,34 +64,26 @@
     data = tab.EvaluateJavaScript('window.benchmark_results.results')
 
     pixels_recorded = data['pixels_recorded']
-    record_time = data['record_time_ms']
     pixels_rasterized = data['pixels_rasterized']
-    rasterize_time = data['rasterize_time_ms']
     painter_memory_usage = data.get('painter_memory_usage', 0)
     paint_op_memory_usage = data.get('paint_op_memory_usage', 0)
     paint_op_count = data.get('paint_op_count', 0)
 
     results.AddMeasurement('pixels_recorded', 'count', pixels_recorded)
     results.AddMeasurement('pixels_rasterized', 'count', pixels_rasterized)
-    results.AddMeasurement('rasterize_time', 'ms', rasterize_time)
-    results.AddMeasurement('record_time', 'ms', record_time)
     results.AddMeasurement('painter_memory_usage', 'bytes',
                            painter_memory_usage)
     results.AddMeasurement('paint_op_memory_usage', 'bytes',
                            paint_op_memory_usage)
     results.AddMeasurement('paint_op_count', 'count', paint_op_count)
 
-    record_time_caching_disabled = data['record_time_caching_disabled_ms']
-    record_time_subsequence_caching_disabled = \
-        data['record_time_subsequence_caching_disabled_ms']
-    record_time_partial_invalidation = \
-        data['record_time_partial_invalidation_ms']
-    results.AddMeasurement('record_time_caching_disabled', 'ms',
-                           record_time_caching_disabled)
-    results.AddMeasurement('record_time_subsequence_caching_disabled', 'ms',
-                           record_time_subsequence_caching_disabled)
-    results.AddMeasurement('record_time_partial_invalidation', 'ms',
-                           record_time_partial_invalidation)
+    for metric in ('rasterize_time', 'record_time',
+                   'record_time_caching_disabled',
+                   'record_time_subsequence_caching_disabled',
+                   'record_time_partial_invalidation',
+                   'raster_invalidation_and_convert_time',
+                   'paint_artifact_compositor_update_time'):
+      results.AddMeasurement(metric, 'ms', data.get(metric + '_ms', 0))
 
     if self._report_detailed_results:
       for metric in ('pixels_rasterized_with_non_solid_color',
diff --git a/ui/android/java/src/org/chromium/ui/base/ViewUtils.java b/ui/android/java/src/org/chromium/ui/base/ViewUtils.java
index 3a5dc3c..3ddce06 100644
--- a/ui/android/java/src/org/chromium/ui/base/ViewUtils.java
+++ b/ui/android/java/src/org/chromium/ui/base/ViewUtils.java
@@ -193,4 +193,26 @@
         roundedIcon.setCornerRadius(cornerRadius);
         return roundedIcon;
     }
+
+    /**
+     * Translates the canvas to ensure the specified view's coordinates are at 0, 0.
+     *
+     * @param from The view the canvas is currently translated to.
+     * @param to The view to translate to.
+     * @param canvas The canvas to be translated.
+     *
+     * @throws IllegalArgumentException if {@code from} is not an ancestor of {@code to}.
+     */
+    public static void translateCanvasToView(View from, View to, Canvas canvas)
+            throws IllegalArgumentException {
+        assert from != null;
+        assert to != null;
+        while (to != from) {
+            canvas.translate(to.getLeft(), to.getTop());
+            if (!(to.getParent() instanceof View)) {
+                throw new IllegalArgumentException("View 'to' was not a desendent of 'from'.");
+            }
+            to = (View) to.getParent();
+        }
+    }
 }
diff --git a/ui/base/ui_base_features.cc b/ui/base/ui_base_features.cc
index d35c5b6..feb7606d 100644
--- a/ui/base/ui_base_features.cc
+++ b/ui/base/ui_base_features.cc
@@ -100,7 +100,8 @@
 // native apps on Windows.
 const base::Feature kExperimentalFlingAnimation {
   "ExperimentalFlingAnimation",
-#if defined(OS_WIN) || (defined(OS_LINUX) && !defined(OS_CHROMEOS))
+#if defined(OS_WIN) || \
+    (defined(OS_LINUX) && !defined(OS_CHROMEOS) && !BUILDFLAG(IS_LACROS))
       base::FEATURE_ENABLED_BY_DEFAULT
 #else
       base::FEATURE_DISABLED_BY_DEFAULT
diff --git a/ui/compositor/layer.cc b/ui/compositor/layer.cc
index cbba087..6031c51 100644
--- a/ui/compositor/layer.cc
+++ b/ui/compositor/layer.cc
@@ -1300,8 +1300,7 @@
   return gfx::Rect(size());
 }
 
-scoped_refptr<cc::DisplayItemList> Layer::PaintContentsToDisplayList(
-    ContentLayerClient::PaintingControlSetting painting_control) {
+scoped_refptr<cc::DisplayItemList> Layer::PaintContentsToDisplayList() {
   TRACE_EVENT1("ui", "Layer::PaintContentsToDisplayList", "name", name_);
   gfx::Rect local_bounds(bounds().size());
   gfx::Rect invalidation(
diff --git a/ui/compositor/layer.h b/ui/compositor/layer.h
index 8290f3f..805c313 100644
--- a/ui/compositor/layer.h
+++ b/ui/compositor/layer.h
@@ -441,8 +441,7 @@
 
   // ContentLayerClient implementation.
   gfx::Rect PaintableRegion() override;
-  scoped_refptr<cc::DisplayItemList> PaintContentsToDisplayList(
-      ContentLayerClient::PaintingControlSetting painting_control) override;
+  scoped_refptr<cc::DisplayItemList> PaintContentsToDisplayList() override;
   bool FillsBoundsCompletely() const override;
   size_t GetApproximateUnsharedMemoryUsage() const override;
 
diff --git a/ui/compositor/layer_unittest.cc b/ui/compositor/layer_unittest.cc
index fc21b32..0de22be7 100644
--- a/ui/compositor/layer_unittest.cc
+++ b/ui/compositor/layer_unittest.cc
@@ -1431,8 +1431,7 @@
   EXPECT_EQ(bound1, root->damaged_region_for_testing());
   root->SendDamagedRects();
   EXPECT_EQ(gfx::Rect(), root->cc_layer_for_testing()->update_rect());
-  root->PaintContentsToDisplayList(
-      cc::ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+  root->PaintContentsToDisplayList();
   EXPECT_EQ(gfx::Rect(), LastInvalidation());
 
   // During deferring paint request, a new invalid_rect will be accumulated.
@@ -1443,8 +1442,7 @@
   EXPECT_EQ(bound_union, root->damaged_region_for_testing().bounds());
   root->SendDamagedRects();
   EXPECT_EQ(gfx::Rect(), root->cc_layer_for_testing()->update_rect());
-  root->PaintContentsToDisplayList(
-      cc::ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+  root->PaintContentsToDisplayList();
   EXPECT_EQ(gfx::Rect(), LastInvalidation());
 
   // Remove deferring paint request.
@@ -1454,8 +1452,7 @@
   // paint, i.e. union of bound1 and bound2.
   root->SendDamagedRects();
   EXPECT_EQ(bound_union, root->cc_layer_for_testing()->update_rect());
-  root->PaintContentsToDisplayList(
-      cc::ContentLayerClient::PAINTING_BEHAVIOR_NORMAL);
+  root->PaintContentsToDisplayList();
   EXPECT_EQ(bound_union, LastInvalidation());
 }
 
diff --git a/ui/file_manager/file_manager/background/js/device_handler.js b/ui/file_manager/file_manager/background/js/device_handler.js
index ccec60c..778e55f 100644
--- a/ui/file_manager/file_manager/background/js/device_handler.js
+++ b/ui/file_manager/file_manager/background/js/device_handler.js
@@ -67,6 +67,11 @@
       case 'format_fail':
         this.handleFormatEvent_(event);
         break;
+      case 'partition_start':
+      case 'partition_success':
+      case 'partition_fail':
+        this.handlePartitionEvent_(event);
+        break;
       case 'rename_fail':
         DeviceHandler.Notification.RENAME_FAIL.show(event.devicePath);
         break;
@@ -122,6 +127,46 @@
   }
 
   /**
+   * Handles partition events and displays a notification in the progress
+   * center. As the partitioning is the first part of SinglePartitionFormat
+   * operation, just show errors that would stop the operation. Other part
+   * handled in format event flow.
+   * @param {chrome.fileManagerPrivate.DeviceEvent} event Device event.
+   * @private
+   */
+  handlePartitionEvent_(event) {
+    const item = new ProgressCenterItem();
+    item.id = 'partition:' + event.devicePath;
+    item.type = ProgressItemType.PARTITION;
+    item.itemCount = 1;
+    item.progressMax = 1;
+
+    let notificationType;
+    switch (event.type) {
+      case 'partition_start':
+      case 'partition_success':
+        // No op for start/success.
+        return;
+      case 'partition_fail':
+        item.state = ProgressItemState.ERROR;
+        item.message = strf('FORMAT_FAILURE_MESSAGE', event.deviceLabel);
+        item.progressValue = 0;
+        notificationType = DeviceHandler.Notification.Type.PARTITION_FAIL;
+        break;
+      default:
+        console.error('Unknown partition event type: ' + event.type);
+        break;
+    }
+
+    this.progressCenter_.updateItem(item);
+
+    requestIdleCallback(
+        () => metrics.recordEnum(
+            'Notification.Show', notificationType,
+            DeviceHandler.Notification.TypesForUMA));
+  }
+
+  /**
    * Handles mount completed events to show notifications for removable devices.
    * @param {chrome.fileManagerPrivate.MountCompletedEvent} event Mount
    *     completed event.
@@ -632,6 +677,9 @@
   FORMAT_SUCCESS: 'format_success',
   FORMAT_FAIL: 'format_fail',
   RENAME_FAIL: 'rename_fail',
+  PARTITION_START: 'partition_start',
+  PARTITION_SUCCESS: 'partition_success',
+  PARTITION_FAIL: 'partition_fail',
 };
 
 /**
@@ -656,6 +704,9 @@
   DeviceHandler.Notification.Type.FORMAT_SUCCESS,
   DeviceHandler.Notification.Type.FORMAT_FAIL,
   DeviceHandler.Notification.Type.RENAME_FAIL,
+  DeviceHandler.Notification.Type.PARTITION_START,
+  DeviceHandler.Notification.Type.PARTITION_SUCCESS,
+  DeviceHandler.Notification.Type.PARTITION_FAIL,
 ]);
 console.assert(
     Object.keys(DeviceHandler.Notification.Type).length ===
diff --git a/ui/file_manager/file_manager/background/js/device_handler_unittest.js b/ui/file_manager/file_manager/background/js/device_handler_unittest.js
index c8fb211..5a3dd57 100644
--- a/ui/file_manager/file_manager/background/js/device_handler_unittest.js
+++ b/ui/file_manager/file_manager/background/js/device_handler_unittest.js
@@ -607,6 +607,41 @@
       progressCenter.getItemById('format:/device/path').message);
 }
 
+function testPartitionSucceeded() {
+  mockChrome.fileManagerPrivate.onDeviceChanged.dispatch({
+    type: 'partition_start',
+    devicePath: '/device/path',
+    deviceLabel: 'label'
+  });
+  assertEquals(0, progressCenter.getItemCount());
+
+  mockChrome.fileManagerPrivate.onDeviceChanged.dispatch({
+    type: 'partition_success',
+    devicePath: '/device/path',
+    deviceLabel: 'label'
+  });
+  assertEquals(0, progressCenter.getItemCount());
+}
+
+function testPartitionFailed() {
+  mockChrome.fileManagerPrivate.onDeviceChanged.dispatch({
+    type: 'partition_start',
+    devicePath: '/device/path',
+    deviceLabel: 'label'
+  });
+  assertEquals(0, progressCenter.getItemCount());
+
+  mockChrome.fileManagerPrivate.onDeviceChanged.dispatch({
+    type: 'partition_fail',
+    devicePath: '/device/path',
+    deviceLabel: 'label'
+  });
+  assertEquals(1, progressCenter.getItemCount());
+  assertEquals(
+      'FORMAT_FAILURE_MESSAGE: label',
+      progressCenter.getItemById('partition:/device/path').message);
+}
+
 function testRenameSucceeded() {
   mockChrome.fileManagerPrivate.onDeviceChanged.dispatch(
       {type: 'rename_start', devicePath: '/device/path'});
diff --git a/ui/file_manager/file_manager/common/js/progress_center_common.js b/ui/file_manager/file_manager/common/js/progress_center_common.js
index 4e77d7d..0b88481 100644
--- a/ui/file_manager/file_manager/common/js/progress_center_common.js
+++ b/ui/file_manager/file_manager/common/js/progress_center_common.js
@@ -52,7 +52,9 @@
   // The item is external drive format operation.
   FORMAT: 'format',
   // The item is archive operation.
-  MOUNT_ARCHIVE: 'mount_archive'
+  MOUNT_ARCHIVE: 'mount_archive',
+  // The item is external drive partitioning operation.
+  PARTITION: 'partition'
 };
 Object.freeze(ProgressItemType);
 
diff --git a/ui/file_manager/file_manager/foreground/elements/files_format_dialog.html b/ui/file_manager/file_manager/foreground/elements/files_format_dialog.html
index f009fd4..d4655970 100644
--- a/ui/file_manager/file_manager/foreground/elements/files_format_dialog.html
+++ b/ui/file_manager/file_manager/foreground/elements/files_format_dialog.html
@@ -54,12 +54,13 @@
       }
     </style>
 
-    <cr-dialog id="dialog" close-text="[[i18n('CLOSE_LABEL')]]">
+    <cr-dialog id="dialog" close-text="[[i18n('CLOSE_LABEL')]]"
+               single-partition-format$="[[getSinglePartitionFormat()]]">
       <div slot="title">
-        [[i18n('FORMAT_DIALOG_TITLE', volumeInfo_.label)]]
+        [[i18n('FORMAT_DIALOG_TITLE', title)]]
       </div>
       <div slot="body">
-        <div>[[i18n('FORMAT_DIALOG_MESSAGE')]]</div>
+        <div>[[getDialogMessage_(isErase_)]]</div>
         <div id="warning-container" hidden="[[!space_used_]]" role="alert">
           <iron-icon id="warning-icon" icon="cr:warning"></iron-icon>
           <div id="warning-message">
@@ -87,7 +88,7 @@
         </cr-button>
         <cr-button class="action-button" on-click="format_"
             id="format-button">
-          [[i18n('FORMAT_DIALOG_CONFIRM_LABEL')]]
+          [[getConfirmLabel_(isErase_)]]
         </cr-button>
       </div>
     </cr-dialog>
diff --git a/ui/file_manager/file_manager/foreground/elements/files_format_dialog.js b/ui/file_manager/file_manager/foreground/elements/files_format_dialog.js
index 5786a6f..56e7f12 100644
--- a/ui/file_manager/file_manager/foreground/elements/files_format_dialog.js
+++ b/ui/file_manager/file_manager/foreground/elements/files_format_dialog.js
@@ -22,6 +22,11 @@
     space_used_: {
       type: String,
       value: '',
+    },
+
+    isErase_: {
+      type: Boolean,
+      value: false,
     }
   },
 
@@ -42,21 +47,79 @@
       this.$.label.invalid = true;
       return;
     }
-    chrome.fileManagerPrivate.formatVolume(
-        this.volumeInfo_.volumeId, this.formatType_, this.label_);
+
+    if (this.isErase_) {
+      chrome.fileManagerPrivate.singlePartitionFormat(
+          this.root_.devicePath_, this.formatType_, this.label_);
+    } else {
+      chrome.fileManagerPrivate.formatVolume(
+          this.volumeInfo_.volumeId, this.formatType_, this.label_);
+    }
     this.$.dialog.close();
   },
 
   /**
+   * Used to set "single-partition-format" attribute on element.
+   * It is used to check flag status in the tests.
+   * @return {string}
+   *
+   * @private
+   */
+  getSinglePartitionFormat() {
+    if (util.isSinglePartitionFormatEnabled()) {
+      return 'single-partition-format';
+    }
+    return '';
+  },
+
+  /**
+   * @param {!boolean} is_erase
+   * @return {string}
+   *
+   * @private
+   */
+  getConfirmLabel_: function(is_erase) {
+    if (util.isSinglePartitionFormatEnabled()) {
+      if (is_erase) {
+        return this.i18n('REPARTITION_DIALOG_CONFIRM_LABEL');
+      } else {
+        return this.i18n('FORMAT_DIALOG_CONFIRM_SHORT_LABEL');
+      }
+    } else {
+      return this.i18n('FORMAT_DIALOG_CONFIRM_LABEL');
+    }
+  },
+
+  /**
+   * @param {!boolean} is_erase
+   * @return {string}
+   *
+   * @private
+   */
+  getDialogMessage_: function(is_erase) {
+    if (util.isSinglePartitionFormatEnabled()) {
+      if (is_erase) {
+        return this.i18n('REPARTITION_DIALOG_MESSAGE');
+      } else {
+        return this.i18n('FORMAT_PARTITION_DIALOG_MESSAGE');
+      }
+    } else {
+      return this.i18n('FORMAT_DIALOG_MESSAGE');
+    }
+  },
+
+  /**
    * Shows the dialog for drive represented by |volumeInfo|.
    * @param {!VolumeInfo} volumeInfo
    */
   showModal: function(volumeInfo) {
+    this.isErase_ = false;
     this.label_ = '';
     this.formatType_ = chrome.fileManagerPrivate.FormatFileSystemType.VFAT;
     this.space_used_ = '';
 
     this.volumeInfo_ = volumeInfo;
+    this.title = this.volumeInfo_.label;
     if (volumeInfo.displayRoot) {
       chrome.fileManagerPrivate.getDirectorySize(
           volumeInfo.displayRoot, space_used_ => {
@@ -71,4 +134,44 @@
 
     this.$.dialog.showModal();
   },
+
+  /**
+   * Shows the dialog for erasing device.
+   * @param {!EntryList} root
+   */
+  showEraseModal: function(root) {
+    this.isErase_ = true;
+    this.label_ = '';
+    this.formatType_ = chrome.fileManagerPrivate.FormatFileSystemType.VFAT;
+    this.space_used_ = '';
+
+    this.root_ = root;
+    this.title = root.label;
+    const childVolumes =
+        /** @type {Array<VolumeEntry>} */ (this.root_.getUIChildren());
+    let totalSpaceUsed = 0;
+
+    const getSpaceUsedRequests = childVolumes.map((childVolume) => {
+      return new Promise((resolve) => {
+        const volumeInfo = childVolume.volumeInfo;
+        if (volumeInfo.displayRoot) {
+          chrome.fileManagerPrivate.getDirectorySize(
+              volumeInfo.displayRoot, space_used_ => {
+                totalSpaceUsed += space_used_;
+                if (totalSpaceUsed > 0) {
+                  this.space_used_ = util.bytesToString(totalSpaceUsed);
+                }
+                resolve();
+              });
+        }
+      });
+    });
+
+    Promise.all(getSpaceUsedRequests).then(() => {
+      if (window.IN_TEST) {
+        this.$['warning-container'].setAttribute('fully-initialized', '');
+      }
+    });
+    this.$.dialog.showModal();
+  },
 });
diff --git a/ui/file_manager/file_manager/foreground/js/file_manager_commands.js b/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
index 8a67c26..4948ece 100644
--- a/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
+++ b/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
@@ -384,6 +384,28 @@
   return false;
 };
 
+
+/**
+ * Extracts entry on which command event was dispatched.
+ *
+ * @param {!Event} event Command event to mark.
+ * @param {!CommandHandlerDeps} fileManager CommandHandlerDeps to use.
+ * @return {Entry|FilesAppDirEntry} Entry of the event node.
+ */
+CommandUtil.getEventEntry = (event, fileManager) => {
+  let entry;
+  if (fileManager.ui.directoryTree.contains(
+          /** @type {Node} */ (event.target))) {
+    // The command is executed from the directory tree context menu.
+    entry = CommandUtil.getCommandEntry(fileManager, event.target);
+  } else {
+    // The command is executed from the gear menu.
+    entry = fileManager.directoryModel.getCurrentDirEntry();
+  }
+  return entry;
+};
+
+
 /**
  * Handle of the command events.
  */
@@ -737,7 +759,59 @@
     const removableRoot = location && isRoot &&
         location.rootType === VolumeManagerCommon.RootType.REMOVABLE;
     event.canExecute = removableRoot && (isUnrecognizedVolume || writable);
-    event.command.setHidden(!removableRoot);
+
+    if (util.isSinglePartitionFormatEnabled()) {
+      let isDevice = false;
+      if (root && root instanceof EntryList) {
+        // root entry is device node if it has child (partition).
+        isDevice = !!removableRoot && root.getUIChildren().length > 0;
+      }
+      // Disable format command on device when SinglePartitionFormat on,
+      // erase command will be available.
+      event.command.setHidden(!removableRoot || isDevice);
+    } else {
+      event.command.setHidden(!removableRoot);
+    }
+  }
+};
+
+/**
+ * Deletes removable device partition, creates single partition and formats it.
+ */
+CommandHandler.COMMANDS_['erase-device'] = new class extends Command {
+  execute(event, fileManager) {
+    const root = CommandUtil.getEventEntry(event, fileManager);
+
+    if (root && root instanceof EntryList) {
+      /** @type {FilesFormatDialogElement} */ (fileManager.ui.formatDialog)
+          .showEraseModal(root);
+    }
+  }
+
+  /** @override */
+  canExecute(event, fileManager) {
+    if (!util.isSinglePartitionFormatEnabled()) {
+      event.canExecute = false;
+      event.command.setHidden(true);
+      return;
+    }
+    const root = CommandUtil.getEventEntry(event, fileManager);
+    const location = root && fileManager.volumeManager.getLocationInfo(root);
+    const writable = location && !location.isReadOnly;
+    const isRoot = location && location.isRootEntry;
+
+    const removableRoot = location && isRoot &&
+        location.rootType === VolumeManagerCommon.RootType.REMOVABLE;
+
+    let isDevice = false;
+    if (root && root instanceof EntryList) {
+      // root entry is device node if it has child (partition).
+      isDevice = !!removableRoot && root.getUIChildren().length > 0;
+    }
+
+    event.canExecute = removableRoot && !writable;
+    // Enable the command if this is a removable and device node.
+    event.command.setHidden(!removableRoot || !isDevice);
   }
 };
 
diff --git a/ui/file_manager/file_manager/foreground/js/navigation_list_model.js b/ui/file_manager/file_manager/foreground/js/navigation_list_model.js
index d7d6b9fe..21175a1d 100644
--- a/ui/file_manager/file_manager/foreground/js/navigation_list_model.js
+++ b/ui/file_manager/file_manager/foreground/js/navigation_list_model.js
@@ -717,7 +717,8 @@
             removableGroup[0].volumeInfo.driveLabel :
             /*default*/ 'External Drive';
         removableEntry = new EntryList(
-            rootLabel, VolumeManagerCommon.RootType.REMOVABLE, devicePath);
+            rootLabel, VolumeManagerCommon.RootType.REMOVABLE,
+            removableGroup[0].volumeInfo.devicePath);
         removableModel = new NavigationModelFakeItem(
             removableEntry.label, NavigationModelItemType.ENTRY_LIST,
             removableEntry);
diff --git a/ui/file_manager/file_manager/foreground/js/ui/BUILD.gn b/ui/file_manager/file_manager/foreground/js/ui/BUILD.gn
index d983cab..b2dc391 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/BUILD.gn
+++ b/ui/file_manager/file_manager/foreground/js/ui/BUILD.gn
@@ -285,6 +285,7 @@
     ":search_box",
     ":suggest_apps_dialog",
     "//ui/file_manager/file_manager/common/js:util",
+    "//ui/file_manager/file_manager/foreground/elements:files_format_dialog",
     "//ui/file_manager/file_manager/foreground/elements:files_message",
     "//ui/file_manager/file_manager/foreground/elements:files_password_dialog",
     "//ui/file_manager/file_manager/foreground/elements:files_spinner",
diff --git a/ui/file_manager/file_manager/main.html b/ui/file_manager/file_manager/main.html
index 9d27675c3..830284fd 100644
--- a/ui/file_manager/file_manager/main.html
+++ b/ui/file_manager/file_manager/main.html
@@ -111,6 +111,7 @@
       <command id="unmount" label="$i18n{UNMOUNT_DEVICE_BUTTON_LABEL}"
                shortcut="E|Shift|Ctrl">
       <command id="format" label="$i18n{FORMAT_DEVICE_BUTTON_LABEL}">
+      <command id="erase-device" label="$i18n{REPARTITION_DEVICE_BUTTON_LABEL}">
       <command id="configure" label="$i18n{CONFIGURE_VOLUME_BUTTON_LABEL}">
 
       <command id="volume-help" label="$i18n{DRIVE_MENU_HELP}">
@@ -223,6 +224,7 @@
         <cr-menu-item command="#configure"></cr-menu-item>
         <cr-menu-item command="#unmount"></cr-menu-item>
         <cr-menu-item command="#format"></cr-menu-item>
+        <cr-menu-item command="#erase-device"></cr-menu-item>
         <cr-menu-item command="#rename"></cr-menu-item>
         <cr-menu-item command="#unpin-folder"></cr-menu-item>
         <cr-menu-item command="#share-with-linux"></cr-menu-item>
@@ -297,6 +299,7 @@
         <cr-menu-item id="gear-menu-newservice" command="#new-service"
             sub-menu="#add-new-services-menu"></cr-menu-item>
         <cr-menu-item id="gear-menu-format" command="#format"></cr-menu-item>
+        <cr-menu-item id="gear-menu-erase" command="#erase-device"></cr-menu-item>
         <cr-menu-item id="volume-space-info" command="#volume-storage">
           <div id="volume-space-info-contents">
             <span id="volume-space-info-label"></span>
diff --git a/ui/file_manager/image_loader/piex/tests.js b/ui/file_manager/image_loader/piex/tests.js
index dc61d48f..5e0f23b 100644
--- a/ui/file_manager/image_loader/piex/tests.js
+++ b/ui/file_manager/image_loader/piex/tests.js
@@ -43,8 +43,14 @@
     console.log(puppeteer.defaultArgs());
   }
 
+  let args = [];
+  if (process.platform === 'linux') {
+    args = ['--no-sandbox'];
+  }
+
   const browser = await puppeteer.launch({
-    headless: !program.debug
+    headless: !program.debug,
+    args: [...args]
   });
 
   const page = await browser.newPage();
diff --git a/ui/file_manager/integration_tests/file_manager/background.js b/ui/file_manager/integration_tests/file_manager/background.js
index d98bc9a..becb357 100644
--- a/ui/file_manager/integration_tests/file_manager/background.js
+++ b/ui/file_manager/integration_tests/file_manager/background.js
@@ -649,3 +649,14 @@
   const cssClass = body.attributes['class'] || '';
   return cssClass.includes('files-ng');
 }
+
+/**
+ * Returns true if the SinglePartitionFormat flag is on.
+ * @param {string} appId Files app windowId.
+ */
+async function isSinglePartitionFormat(appId) {
+  const dialog = await remoteCall.waitForElement(
+      appId, ['files-format-dialog', 'cr-dialog']);
+  const flag = dialog.attributes['single-partition-format'] || '';
+  return !!flag;
+}
diff --git a/ui/file_manager/integration_tests/file_manager/directory_tree_context_menu.js b/ui/file_manager/integration_tests/file_manager/directory_tree_context_menu.js
index 197ac3c..923c77a 100644
--- a/ui/file_manager/integration_tests/file_manager/directory_tree_context_menu.js
+++ b/ui/file_manager/integration_tests/file_manager/directory_tree_context_menu.js
@@ -1278,6 +1278,33 @@
       ['#delete', true],
       ['#new-folder', true],
     ];
+    const ext4DeviceMenus = [
+      ['#unmount', true],
+      ['#erase-device', true],
+      ['#share-with-linux', true],
+    ];
+    const ext4PartitionMenus = [
+      ['#share-with-linux', true],
+      ['#format', true],
+      ['#rename', false],
+      ['#new-folder', true],
+    ];
+    const ntfsDeviceMenus = [
+      ['#unmount', true],
+      ['#erase-device', true],
+      ['#share-with-linux', true],
+    ];
+    const ntfsPartitionMenus = [
+      ['#share-with-linux', true],
+      ['#format', true],
+      ['#rename', true],
+      ['#new-folder', true],
+    ];
+    const deviceMenus = [
+      ['#unmount', true],
+      ['#erase-device', true],
+      ['#share-with-linux', true],
+    ];
 
     // Mount removable volumes.
     await sendTestMessage({name: 'mountUsbWithPartitions'});
@@ -1287,34 +1314,75 @@
     const appId = await setupAndWaitUntilReady(
         RootPath.DOWNLOADS, [ENTRIES.beautiful], []);
 
-    // Check the context menu for single partition ext4 USB.
-    await checkContextMenu(
-        appId, '/fake-usb', ext4UsbMenus, true /* rootMenu */);
+    if (await isSinglePartitionFormat(appId)) {
+      // Check the context menu for single partition drive.
+      await checkContextMenu(
+          appId, '/FAKEUSB', ext4DeviceMenus, true /* rootMenu */);
 
-    // Check the context menu for a folder inside a single USB partition.
-    await checkContextMenu(
-        appId, '/fake-usb/A', folderMenus, false /* rootMenu */);
+      // Check the context menu for single partition ext4 USB.
+      await checkContextMenu(
+          appId, '/FAKEUSB/fake-usb', ext4PartitionMenus, false /* rootMenu */);
 
-    // Check the context menu for multiple partitions USB (root).
-    await checkContextMenu(
-        appId, '/Drive Label', partitionsRootMenus, true /* rootMenu */);
+      // Check the context menu for a folder inside a single USB partition.
+      await checkContextMenu(
+          appId, '/FAKEUSB/fake-usb/A', folderMenus, false /* rootMenu */);
 
-    // Check the context menu for multiple partitions USB (actual partition).
-    await checkContextMenu(
-        appId, '/Drive Label/partition-1', partition1Menus,
-        false /* rootMenu */);
+      // Check the context menu for multiple partitions USB (root).
+      await checkContextMenu(
+          appId, '/Drive Label', deviceMenus, true /* rootMenu */);
 
-    // Check the context menu for a folder inside a partition1.
-    await checkContextMenu(
-        appId, '/Drive Label/partition-1/A', folderMenus, false /* rootMenu */);
+      // Check the context menu for multiple partitions USB (actual partition).
+      await checkContextMenu(
+          appId, '/Drive Label/partition-1', partition1Menus,
+          false /* rootMenu */);
 
-    // Remount the single partition ext4 USB as NTFS
-    await sendTestMessage({name: 'unmountUsb'});
-    await sendTestMessage({name: 'mountFakeUsb', filesystem: 'ntfs'});
+      // Check the context menu for a folder inside a partition1.
+      await checkContextMenu(
+          appId, '/Drive Label/partition-1/A', folderMenus,
+          false /* rootMenu */);
 
-    // Check the context menu for a single partition NTFS USB.
-    await checkContextMenu(
-        appId, '/fake-usb', ntfsUsbMenus, true /* rootMenu */);
+      // Remount the single partition ext4 USB as NTFS
+      await sendTestMessage({name: 'unmountUsb'});
+      await sendTestMessage({name: 'mountFakeUsb', filesystem: 'ntfs'});
+
+      // Check the context menu for a single partition NTFS USB.
+      await checkContextMenu(
+          appId, '/FAKEUSB', ntfsDeviceMenus, true /* rootMenu */);
+
+      // Check the context menu for a single partition NTFS USB.
+      await checkContextMenu(
+          appId, '/FAKEUSB/fake-usb', ntfsPartitionMenus, false /* rootMenu */);
+    } else {
+      // Check the context menu for single partition ext4 USB.
+      await checkContextMenu(
+          appId, '/fake-usb', ext4UsbMenus, true /* rootMenu */);
+
+      // Check the context menu for a folder inside a single USB partition.
+      await checkContextMenu(
+          appId, '/fake-usb/A', folderMenus, false /* rootMenu */);
+
+      // Check the context menu for multiple partitions USB (root).
+      await checkContextMenu(
+          appId, '/Drive Label', partitionsRootMenus, true /* rootMenu */);
+
+      // Check the context menu for multiple partitions USB (actual partition).
+      await checkContextMenu(
+          appId, '/Drive Label/partition-1', partition1Menus,
+          false /* rootMenu */);
+
+      // Check the context menu for a folder inside a partition1.
+      await checkContextMenu(
+          appId, '/Drive Label/partition-1/A', folderMenus,
+          false /* rootMenu */);
+
+      // Remount the single partition ext4 USB as NTFS
+      await sendTestMessage({name: 'unmountUsb'});
+      await sendTestMessage({name: 'mountFakeUsb', filesystem: 'ntfs'});
+
+      // Check the context menu for a single partition NTFS USB.
+      await checkContextMenu(
+          appId, '/fake-usb', ntfsUsbMenus, true /* rootMenu */);
+    }
   };
 
   /**
@@ -1336,6 +1404,12 @@
       ['#delete', true],
       ['#new-folder', true],
     ];
+    const deviceUsbMenus = [
+      ['#share-with-linux', true],
+      ['#format', true],
+      ['#rename', false],
+      ['#new-folder', true],
+    ];
 
     // Mount removable volumes.
     await sendTestMessage({name: 'mountFakeUsbDcim'});
@@ -1344,12 +1418,23 @@
     const appId = await setupAndWaitUntilReady(
         RootPath.DOWNLOADS, [ENTRIES.beautiful], []);
 
-    // Check the context menu for single partition USB.
-    await checkContextMenu(appId, '/fake-usb', usbMenus, true /* rootMenu */);
+    if (await isSinglePartitionFormat(appId)) {
+      // Check the context menu for single partition USB.
+      await checkContextMenu(
+          appId, '/FAKEUSB/fake-usb', deviceUsbMenus, false /* rootMenu */);
 
-    // Check the context menu for the DCIM folder inside USB.
-    await checkContextMenu(
-        appId, '/fake-usb/DCIM', dcimFolderMenus, false /* rootMenu */);
+      // Check the context menu for the DCIM folder inside USB.
+      await checkContextMenu(
+          appId, '/FAKEUSB/fake-usb/DCIM', dcimFolderMenus,
+          false /* rootMenu */);
+    } else {
+      // Check the context menu for single partition USB.
+      await checkContextMenu(appId, '/fake-usb', usbMenus, true /* rootMenu */);
+
+      // Check the context menu for the DCIM folder inside USB.
+      await checkContextMenu(
+          appId, '/fake-usb/DCIM', dcimFolderMenus, false /* rootMenu */);
+    }
   };
 
   /*
diff --git a/ui/file_manager/integration_tests/file_manager/drive_specific.js b/ui/file_manager/integration_tests/file_manager/drive_specific.js
index 556912b..3f62497 100644
--- a/ui/file_manager/integration_tests/file_manager/drive_specific.js
+++ b/ui/file_manager/integration_tests/file_manager/drive_specific.js
@@ -438,9 +438,14 @@
   // Wait for the USB mount.
   await remoteCall.waitForElement(appId, USB_VOLUME_QUERY);
 
-  // Navigate to the DCIM directory.
-  await remoteCall.navigateWithDirectoryTree(
-      appId, '/DCIM', 'fake-usb', 'removable');
+  if (await isSinglePartitionFormat(appId)) {
+    // Navigate to the DCIM directory.
+    await navigateWithDirectoryTree(appId, '/FAKEUSB/fake-usb/DCIM');
+  } else {
+    // Navigate to the DCIM directory.
+    await remoteCall.navigateWithDirectoryTree(
+        appId, '/DCIM', 'fake-usb', 'removable');
+  }
 
   // Wait for the import button to be ready.
   await remoteCall.waitForElement(
diff --git a/ui/file_manager/integration_tests/file_manager/file_display.js b/ui/file_manager/integration_tests/file_manager/file_display.js
index cc576be..cd18cdd9 100644
--- a/ui/file_manager/integration_tests/file_manager/file_display.js
+++ b/ui/file_manager/integration_tests/file_manager/file_display.js
@@ -268,14 +268,28 @@
   chrome.test.assertEq(
       'removable', fakeUsb.attributes['volume-type-for-testing']);
 
-  // Check unpartitioned USB does not have partitions as tree children.
-  const itemEntriesQuery =
-      ['[entry-label="fake-usb"] .tree-children .tree-item'];
-  const itemEntries = await remoteCall.callRemoteTestUtil(
-      'queryAllElements', appId, itemEntriesQuery);
-  chrome.test.assertEq(1, itemEntries.length);
-  const childVolumeType = itemEntries[0].attributes['volume-type-for-testing'];
-  chrome.test.assertTrue('removable' !== childVolumeType);
+  if (await isSinglePartitionFormat(appId)) {
+    // Check unpartitioned USB has single partition as tree child.
+    const itemEntriesQuery =
+        ['[entry-label="FAKEUSB"] .tree-children .tree-item'];
+    const itemEntries = await remoteCall.callRemoteTestUtil(
+        'queryAllElements', appId, itemEntriesQuery);
+    chrome.test.assertEq(1, itemEntries.length);
+    const childVolumeType =
+        itemEntries[0].attributes['volume-type-for-testing'];
+
+    chrome.test.assertTrue('removable' == childVolumeType);
+  } else {
+    // Check unpartitioned USB does not have partitions as tree children.
+    const itemEntriesQuery =
+        ['[entry-label="fake-usb"] .tree-children .tree-item'];
+    const itemEntries = await remoteCall.callRemoteTestUtil(
+        'queryAllElements', appId, itemEntriesQuery);
+    chrome.test.assertEq(1, itemEntries.length);
+    const childVolumeType =
+        itemEntries[0].attributes['volume-type-for-testing'];
+    chrome.test.assertTrue('removable' !== childVolumeType);
+  }
 };
 
 /**
diff --git a/ui/file_manager/integration_tests/file_manager/format_dialog.js b/ui/file_manager/integration_tests/file_manager/format_dialog.js
index fa13759c..42a67ef1 100644
--- a/ui/file_manager/integration_tests/file_manager/format_dialog.js
+++ b/ui/file_manager/integration_tests/file_manager/format_dialog.js
@@ -21,6 +21,11 @@
  * @param {string} usbLabel Label of usb to format.
  */
 async function openFormatDialog(appId, usbLabel) {
+  if (await isSinglePartitionFormat(appId)) {
+    await openFormatDialogWithSinglePartitionFormat(appId, usbLabel, 'FAKEUSB');
+    return;
+  }
+
   // Focus the directory tree.
   chrome.test.assertTrue(
       !!await remoteCall.callRemoteTestUtil(
@@ -46,6 +51,44 @@
 }
 
 /**
+ * Opens a format dialog for the USB with label |usbLabel| and device with
+ * label |deviceLabel|.
+ *
+ * @param {string} appId Files app window ID.
+ * @param {string} usbLabel Label of usb to format.
+ * @param {string} deviceLabel Label of the parent device of usb.
+ */
+async function openFormatDialogWithSinglePartitionFormat(
+    appId, usbLabel, deviceLabel) {
+  // Focus the directory tree.
+  chrome.test.assertTrue(
+      !!await remoteCall.callRemoteTestUtil(
+          'focus', appId, ['#directory-tree']),
+      'focus failed: #directory-tree');
+
+  // Expand device tree entry to access partition entry.
+  await remoteCall.expandTreeItemInDirectoryTree(
+      appId, `#directory-tree [entry-label="${deviceLabel}"]`);
+
+  // Right click on the USB's directory tree entry.
+  const treeQuery = `#directory-tree [entry-label="${usbLabel}"]`;
+  await remoteCall.waitForElement(appId, treeQuery);
+  chrome.test.assertTrue(
+      !!await remoteCall.callRemoteTestUtil(
+          'fakeMouseRightClick', appId, [treeQuery]),
+      'fakeMouseRightClick failed');
+
+  // Click on the format menu item.
+  const formatItemQuery = '#directory-tree-context-menu:not([hidden])' +
+      ' cr-menu-item[command="#format"]:not([hidden]):not([disabled])';
+  await remoteCall.waitAndClickElement(appId, formatItemQuery);
+
+  // Check the dialog is open.
+  await remoteCall.waitForElement(
+      appId, ['files-format-dialog', 'cr-dialog[open]']);
+}
+
+/**
  * Tests the format dialog for a sample USB with files on it.
  */
 testcase.formatDialog = async () => {
@@ -261,8 +304,12 @@
           'focus', appId, ['#directory-tree']),
       'focus failed: #directory-tree');
 
+  let usbNavigationPath = '/fake-usb';
+  if (await isSinglePartitionFormat(appId)) {
+    usbNavigationPath = '/FAKEUSB/fake-usb';
+  }
   // Navigate to the USB via the directory tree.
-  await navigateWithDirectoryTree(appId, '/fake-usb');
+  await navigateWithDirectoryTree(appId, usbNavigationPath);
 
   // Click on the gear menu button.
   await remoteCall.waitAndClickElement(appId, '#gear-button:not([hidden])');
diff --git a/ui/file_manager/integration_tests/file_manager/transfer.js b/ui/file_manager/integration_tests/file_manager/transfer.js
index 52b6eb6..7b9f014e 100644
--- a/ui/file_manager/integration_tests/file_manager/transfer.js
+++ b/ui/file_manager/integration_tests/file_manager/transfer.js
@@ -759,8 +759,12 @@
           'fakeDragAndDrop', appId, [source, target, skipDrop]),
       'fakeDragAndDrop failed');
 
+  let navigationPath = '/fake-usb';
+  if (await isSinglePartitionFormat(appId)) {
+    navigationPath = '/FAKEUSB/fake-usb';
+  }
   // Check: drag hovering should navigate the file list.
-  await remoteCall.waitUntilCurrentDirectoryIsChanged(appId, '/fake-usb');
+  await remoteCall.waitUntilCurrentDirectoryIsChanged(appId, navigationPath);
 };
 
 /**
@@ -984,8 +988,12 @@
   chrome.test.assertTrue(
       await remoteCall.callRemoteTestUtil('execCommand', appId, ['copy']));
 
+  let navigationPath = '/fake-usb';
+  if (await isSinglePartitionFormat(appId)) {
+    navigationPath = '/FAKEUSB/fake-usb';
+  }
   // Select USB volume.
-  await navigateWithDirectoryTree(appId, '/fake-usb');
+  await navigateWithDirectoryTree(appId, navigationPath);
 
   // Tell the background page to never finish the file copy.
   await remoteCall.callRemoteTestUtil(
diff --git a/ui/ozone/platform_selection.cc b/ui/ozone/platform_selection.cc
index eb8e85ed..1b49a08 100644
--- a/ui/ozone/platform_selection.cc
+++ b/ui/ozone/platform_selection.cc
@@ -33,9 +33,6 @@
     return g_selected_platform;
 
   std::string platform_name = GetPlatformName();
-  // TODO(b/169115289) remove once all Tast tests use "drm".
-  if (platform_name == "gbm")
-    platform_name = "drm";
 
   // Search for a matching platform in the list.
   for (int platform_id = 0; platform_id < kPlatformCount; ++platform_id) {
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 ed62aab..fb06449 100644
--- a/ui/webui/resources/cr_elements/chromeos/cr_picture/BUILD.gn
+++ b/ui/webui/resources/cr_elements/chromeos/cr_picture/BUILD.gn
@@ -7,13 +7,6 @@
 import("//ui/webui/resources/tools/js_modulizer.gni")
 import("../os_cr_elements.gni")
 
-# This file depends on the legacy global sources assignment filter. It should
-# be converted to check target platform before assigning source files to the
-# sources variable. Remove this import and set_sources_assignment_filter call
-# when the file has been converted. See https://crbug.com/1018739 for details.
-import("//build/config/deprecated_default_sources_assignment_filter.gni")
-set_sources_assignment_filter(deprecated_default_sources_assignment_filter)
-
 js_type_check("closure_compile") {
   deps = [
     ":cr_camera",
@@ -70,19 +63,20 @@
 }
 
 js_library("png.m") {
-  # Need to turn off default sources filtering by GN, otherwise |sources| is
-  # filtered on non-CrOS, because it contains the term "chromeos". This
-  # js_library() target is needed in non-CrOS builds as well (see
-  # chrome/browser/resources/settings/people_page:people_page.m)
-  set_sources_assignment_filter([])
   sources = [
     "$root_gen_dir/ui/webui/resources/cr_elements/chromeos/cr_picture/png.m.js",
   ]
   extra_deps = [ ":modulize" ]
 }
 
+# TODO(crbug.com/1134204): This target should probably only be defined if
+# target_os is "chromos" as it does not include any source files on other
+# platforms.
 js_library("cr_picture_types.m") {
-  sources = [ "$root_gen_dir/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_types.m.js" ]
+  sources = []
+  if (is_chromeos) {
+    sources += [ "$root_gen_dir/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_types.m.js" ]
+  }
   extra_deps = [ ":modulize" ]
 }
 
@@ -96,8 +90,14 @@
   ]
 }
 
+# TODO(crbug.com/1134204): This target should probably only be defined if
+# target_os is "chromos" as it does not include any source files on other
+# platforms.
 js_library("cr_picture_pane.m") {
-  sources = [ "$root_gen_dir/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_pane.m.js" ]
+  sources = []
+  if (is_chromeos) {
+    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",
@@ -106,14 +106,26 @@
   extra_deps = [ ":cr_picture_pane_module" ]
 }
 
+# TODO(crbug.com/1134204): This target should probably only be defined if
+# target_os is "chromos" as it does not include any source files on other
+# platforms.
 js_library("cr_camera.m") {
-  sources = [ "$root_gen_dir/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_camera.m.js" ]
+  sources = []
+  if (is_chromeos) {
+    sources += [ "$root_gen_dir/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_camera.m.js" ]
+  }
   deps = [ ":png.m" ]
   extra_deps = [ ":cr_camera_module" ]
 }
 
+# TODO(crbug.com/1134204): This target should probably only be defined if
+# target_os is "chromos" as it does not include any source files on other
+# platforms.
 js_library("cr_picture_list.m") {
-  sources = [ "$root_gen_dir/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_list.m.js" ]
+  sources = []
+  if (is_chromeos) {
+    sources += [ "$root_gen_dir/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_list.m.js" ]
+  }
   deps = [
     ":cr_picture_types.m",
     ":png.m",
diff --git a/weblayer/browser/android/javatests/BUILD.gn b/weblayer/browser/android/javatests/BUILD.gn
index a322694..fe48352e 100644
--- a/weblayer/browser/android/javatests/BUILD.gn
+++ b/weblayer/browser/android/javatests/BUILD.gn
@@ -10,6 +10,7 @@
   testonly = true
   sources = [
     "src/org/chromium/weblayer/test/BrowserFragmentLifecycleTest.java",
+    "src/org/chromium/weblayer/test/BrowserTest.java",
     "src/org/chromium/weblayer/test/CookieManagerTest.java",
     "src/org/chromium/weblayer/test/CrashReporterTest.java",
     "src/org/chromium/weblayer/test/DataClearingTest.java",
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserFragmentLifecycleTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserFragmentLifecycleTest.java
index 2cfb648..21e51fe 100644
--- a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserFragmentLifecycleTest.java
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserFragmentLifecycleTest.java
@@ -18,6 +18,7 @@
 
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.Browser;
 import org.chromium.weblayer.Navigation;
 import org.chromium.weblayer.NavigationCallback;
 import org.chromium.weblayer.NavigationController;
@@ -282,4 +283,25 @@
         });
         helper.waitForCallback(callCount, 1);
     }
+
+    @Test
+    @SmallTest
+    public void browserAndTabIsDestroyedWhenFragmentDestroyed() throws Throwable {
+        mActivityTestRule.launchShellWithUrl(mActivityTestRule.getTestDataURL("simple_page.html"));
+
+        CallbackHelper helper = new CallbackHelper();
+        Browser browser = TestThreadUtils.runOnUiThreadBlocking(
+                () -> { return mActivityTestRule.getActivity().getBrowser(); });
+        Tab tab = TestThreadUtils.runOnUiThreadBlocking(() -> { return browser.getActiveTab(); });
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Assert.assertFalse(browser.isDestroyed());
+            Assert.assertFalse(tab.isDestroyed());
+            destroyFragment(helper);
+        });
+        helper.waitForFirst();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Assert.assertTrue(browser.isDestroyed());
+            Assert.assertTrue(tab.isDestroyed());
+        });
+    }
 }
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserTest.java
new file mode 100644
index 0000000..39c8f4b
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserTest.java
@@ -0,0 +1,44 @@
+// 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.weblayer.test;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.Browser;
+import org.chromium.weblayer.Tab;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Tests for Browser.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class BrowserTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private InstrumentationActivity mActivity;
+
+    @Test
+    @SmallTest
+    public void testDestroyTab() {
+        String url = mActivityTestRule.getTestDataURL("before_unload.html");
+        mActivity = mActivityTestRule.launchShellWithUrl(url);
+        Assert.assertNotNull(mActivity);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Browser browser = mActivity.getBrowser();
+            Tab tab = browser.getActiveTab();
+            Assert.assertFalse(tab.isDestroyed());
+            browser.destroyTab(tab);
+            Assert.assertTrue(tab.isDestroyed());
+        });
+    }
+}
diff --git a/weblayer/public/java/org/chromium/weblayer/Browser.java b/weblayer/public/java/org/chromium/weblayer/Browser.java
index d568ed02..cc3718d 100644
--- a/weblayer/public/java/org/chromium/weblayer/Browser.java
+++ b/weblayer/public/java/org/chromium/weblayer/Browser.java
@@ -73,6 +73,14 @@
                                                    : null;
     }
 
+    /**
+     * Returns true if this Browser has been destroyed.
+     */
+    public boolean isDestroyed() {
+        ThreadCheck.ensureOnUiThread();
+        return mImpl == null;
+    }
+
     // Called prior to notifying IBrowser of destroy().
     void prepareForDestroy() {
         mFragment = null;
diff --git a/weblayer/public/java/org/chromium/weblayer/Tab.java b/weblayer/public/java/org/chromium/weblayer/Tab.java
index 32495e0..b9cb6ad 100644
--- a/weblayer/public/java/org/chromium/weblayer/Tab.java
+++ b/weblayer/public/java/org/chromium/weblayer/Tab.java
@@ -128,6 +128,14 @@
         mBrowser = browser;
     }
 
+    /**
+     * Returns true if this Tab has been destroyed.
+     */
+    public boolean isDestroyed() {
+        ThreadCheck.ensureOnUiThread();
+        return mImpl == null;
+    }
+
     @NonNull
     public Browser getBrowser() {
         ThreadCheck.ensureOnUiThread();