diff --git a/DEPS b/DEPS
index b2773db..a358ddd 100644
--- a/DEPS
+++ b/DEPS
@@ -172,11 +172,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '99b558b594a12ec1d09172f85a6586b17de75ccc',
+  'skia_revision': 'a53e47fe9483117e65e05a006e88c2cccf974a1a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '814c68a96da7afea756c9f8aea57244f017bd715',
+  'v8_revision': 'fdd7f4213eb74e4f796b3a9ef2cddb51e93bbfe9',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -184,11 +184,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'dc98ca69edd0a803eed76e16d650fa69ea5610f0',
+  'angle_revision': '50919254cbc716d09703f93c2d38a0368809dbda',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': 'd9ed1c2732ba9b1bd36461367d348e3030043a25',
+  'swiftshader_revision': 'c03ce008b9fe5056e3dc225473602419e987e94c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -243,7 +243,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': '7eee1a626bd162ff8eaa017c6a1f94a83613b6c3',
+  'devtools_frontend_revision': '80a89976c1420ca7cc12b6e4702cefb0eb7bfc29',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -896,7 +896,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '1917f7a099407369280b2cc74a33e44f4ed8c84c',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '039522eb577eb99dc9b931c2c63e327239aa0c8a',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1311,7 +1311,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '5e3eacac1ca1dcd6f8a18da9a39aeccf48aa13c5',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'e9709db85ca95f1cc0cc65d9a2a0928ea9184841',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
diff --git a/android_webview/variables.gni b/android_webview/variables.gni
index 8bbf434..3efed5b7 100644
--- a/android_webview/variables.gni
+++ b/android_webview/variables.gni
@@ -16,6 +16,7 @@
 upstream_only_webview_deps = [
   "//android_webview:platform_service_bridge_upstream_implementation_java",
   "//android_webview/apk:icon_resources",
+  "//weblayer/browser/java:gms_bridge_upstream_impl_java",
 ]
 
 webview_product_config_java_package = "org.chromium.android_webview"
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 2384f696e..6e79379 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -877,8 +877,44 @@
       <message name="IDS_ASH_STATUS_TRAY_NETWORK" desc="The label used in the network dialog header.">
         Network
       </message>
-      <message name="IDS_ASH_STATUS_TRAY_NETWORK_ACCESSIBILITY_LABEL" desc="Accessibility label for a network in the quick settings network list, to be read outloud by a screenreader or braile console.">
-        <ph name="NETWORK_NAME">$1<ex>GuestNetwork</ex></ph> - <ph name="CONNECTION_STATUS">$2<ex>Connecting</ex></ph>
+      <message name="IDS_ASH_STATUS_TRAY_ETHERNET_A11Y_LABEL_WITH_CONNECTION_STATUS" desc="Accessibility label for an ethernet network in the quick settings network list.">
+        <ph name="NETWORK_NAME">$1<ex>Ethernet</ex></ph>, <ph name="CONNECTION_STATUS">$2<ex>Connected</ex></ph>
+      </message>
+      <message name="IDS_ASH_STATUS_TRAY_ETHERNET_A11Y_LABEL_MANAGED" desc="Accessibility label for an enterprise managed ethernet network in the quick settings network list.">
+        <ph name="NETWORK_NAME">$1<ex>Ethernet</ex></ph>, Managed by your Administrator
+      </message>
+      <message name="IDS_ASH_STATUS_TRAY_ETHERNET_A11Y_LABEL_MANAGED_WITH_CONNECTION_STATUS" desc="Accessibility label for an enterprise managed ethernet network in the quick settings network list.  Includes connection status.">
+        <ph name="NETWORK_NAME">$1<ex>Ethernet</ex></ph>, <ph name="CONNECTION_STATUS">$2<ex>Connected</ex></ph>, Managed by your Administrator
+      </message>
+      <message name="IDS_ASH_STATUS_TRAY_CELLULAR_NETWORK_A11Y_LABEL" desc="Accessibility label for a cellular network in the quick settings network list.">
+        <ph name="NETWORK_NAME">$1<ex>Verizon Wireless</ex></ph>, Signal Strength <ph name="SIGNAL_STRENGTH">$2<ex>80%</ex></ph>
+      </message>
+      <message name="IDS_ASH_STATUS_TRAY_CELLULAR_NETWORK_A11Y_LABEL_WITH_CONNECTION_STATUS" desc="Accessibility label for a cellular network in the quick settings network list .  Includes connection status.">
+        <ph name="NETWORK_NAME">$1<ex>Verizon Wireless</ex></ph>, <ph name="CONNECTION_STATUS">$2<ex>Connecting...</ex></ph>, Signal Strength <ph name="SIGNAL_STRENGTH">$3<ex>80%</ex></ph>
+      </message>
+      <message name="IDS_ASH_STATUS_TRAY_CELLULAR_NETWORK_A11Y_LABEL_MANAGED" desc="Accessibility label for an enterprise managed cellular network in the quick settings network list.">
+        <ph name="NETWORK_NAME">$1<ex>Verizon Wireless</ex></ph>, Signal Strength <ph name="SIGNAL_STRENGTH">$2<ex>80%</ex></ph>, Managed by your Administrator
+      </message>
+      <message name="IDS_ASH_STATUS_TRAY_CELLULAR_NETWORK_A11Y_LABEL_MANAGED_WITH_CONNECTION_STATUS" desc="Accessibility label for an enterprise managed cellular network in the quick settings network list.  Includes connection status.">
+        <ph name="NETWORK_NAME">$1<ex>Verizon Wireless</ex></ph>, <ph name="CONNECTION_STATUS">$2<ex>Connecting...</ex></ph>, Signal Strength <ph name="SIGNAL_STRENGTH">$3<ex>80%</ex></ph>, Managed by your Administrator
+      </message>
+      <message name="IDS_ASH_STATUS_TRAY_WIFI_NETWORK_A11Y_LABEL" desc="Accessibility label for a Wi-Fi network in the quick settings network list.">
+        <ph name="NETWORK_NAME">$1<ex>GuestNetwork</ex></ph>, <ph name="SECURITY_STATUS">$2<ex>Unsecured</ex></ph>, Signal Strength <ph name="SIGNAL_STRENGTH">$3<ex>80%</ex></ph>
+      </message>
+      <message name="IDS_ASH_STATUS_TRAY_WIFI_NETWORK_A11Y_LABEL_WITH_CONNECTION_STATUS" desc="Accessibility label for a Wi-Fi network in the quick settings network list.  Includes connection status.">
+        <ph name="NETWORK_NAME">$1<ex>GuestNetwork</ex></ph>, <ph name="SECURITY_STATUS">$2<ex>Unsecured</ex></ph>, <ph name="CONNECTION_STATUS">$3<ex>Connecting</ex></ph>, Signal Strength <ph name="SIGNAL_STRENGTH">$4<ex>80%</ex></ph>
+      </message>
+      <message name="IDS_ASH_STATUS_TRAY_WIFI_NETWORK_A11Y_LABEL_MANAGED" desc="Accessibility label for an enterprise managed Wi-Fi network in the quick settings network list.">
+        <ph name="NETWORK_NAME">$1<ex>GuestNetwork</ex></ph>, <ph name="SECURITY_STATUS">$2<ex>Unsecured</ex></ph>, Signal Strength <ph name="SIGNAL_STRENGTH">$3<ex>80%</ex></ph>, Managed by your Administrator
+      </message>
+      <message name="IDS_ASH_STATUS_TRAY_WIFI_NETWORK_A11Y_LABEL_MANAGED_WITH_CONNECTION_STATUS" desc="Accessibility label for an enterprise managed Wi-Fi network in the quick settings network list.  Incldues connection status.">
+        <ph name="NETWORK_NAME">$1<ex>GuestNetwork</ex></ph>, <ph name="SECURITY_STATUS">$2<ex>Unsecured</ex></ph>, <ph name="CONNECTION_STATUS">$3<ex>Connecting</ex></ph>, Signal Strength <ph name="SIGNAL_STRENGTH">$4<ex>80%</ex></ph>, Managed by your Administrator
+      </message>
+      <message name="IDS_ASH_STATUS_TRAY_TETHER_NETWORK_A11Y_LABEL" desc="Accessibility label for a phone tether network in the quick settings network list.">
+        <ph name="PHONE_NAME">$1<ex>Pixel 4</ex></ph>, Signal Strength <ph name="SIGNAL_STRENGTH">$2<ex>80%</ex></ph>, Phone Battery <ph name="BATTERY_STATUS">$3<ex>80%</ex></ph>
+      </message>
+      <message name="IDS_ASH_STATUS_TRAY_TETHER_NETWORK_A11Y_LABEL_WITH_CONNECTION_STATUS" desc="Accessibility label for a phone tether network in the quick settings network list.  Includes connection status.">
+        <ph name="PHONE_NAME">$1<ex>Pixel 4</ex></ph>, <ph name="CONNECTION_STATUS">$2<ex>Connecting...</ex></ph>, Signal Strength <ph name="SIGNAL_STRENGTH">$3<ex>80%</ex></ph>, Phone Battery <ph name="BATTERY_STATUS">$4<ex>80%</ex></ph>
       </message>
       <message name="IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTED" desc="The label used when a network connection is connected.">
         Connected
@@ -886,6 +922,12 @@
       <message name="IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTING" desc="The label used when a network connection is connecting.">
         Connecting...
       </message>
+      <message name="IDS_ASH_STATUS_TRAY_NETWORK_STATUS_SECURED" desc="a11y label used when a Wi-Fi network is secured.">
+        Secured
+      </message>
+      <message name="IDS_ASH_STATUS_TRAY_NETWORK_STATUS_UNSECURED" desc="a11y label used when a Wi-Fi network connection is not secured.">
+        Unsecured
+      </message>
       <message name="IDS_ASH_STATUS_TRAY_VPN" desc="The label used in the VPN detailed view header.">
         Private network
       </message>
diff --git a/ash/autotest_private_api_utils.cc b/ash/autotest_private_api_utils.cc
index ee9c3ba..324179b 100644
--- a/ash/autotest_private_api_utils.cc
+++ b/ash/autotest_private_api_utils.cc
@@ -85,10 +85,6 @@
       ash::kAllDesks);
 }
 
-ash::HeaderView* GetHeaderViewForWindow(aura::Window* window) {
-  return ash::NonClientFrameViewAsh::Get(window)->GetHeaderView();
-}
-
 bool WaitForLauncherState(AppListViewState target_state,
                           base::Closure closure) {
   // In the tablet mode, some of the app-list state switching is handled
diff --git a/ash/home_screen/OWNERS b/ash/home_screen/OWNERS
new file mode 100644
index 0000000..c2248ac
--- /dev/null
+++ b/ash/home_screen/OWNERS
@@ -0,0 +1,5 @@
+manucornet@chromium.org
+newcomer@chromium.org
+sammiequon@chromium.org
+tbarzic@chromium.org
+xdai@chromium.org
diff --git a/ash/home_screen/window_transform_to_home_screen_animation.cc b/ash/home_screen/window_transform_to_home_screen_animation.cc
index c7b36f5..e9dd0a4 100644
--- a/ash/home_screen/window_transform_to_home_screen_animation.cc
+++ b/ash/home_screen/window_transform_to_home_screen_animation.cc
@@ -11,6 +11,7 @@
 #include "ash/screen_util.h"
 #include "ash/shell.h"
 #include "ash/wm/window_state.h"
+#include "ash/wm/window_util.h"
 #include "base/time/time.h"
 #include "ui/aura/window.h"
 #include "ui/compositor/layer.h"
@@ -65,9 +66,8 @@
 
 void WindowTransformToHomeScreenAnimation::OnImplicitAnimationsCompleted() {
   // Minimize the dragged window after transform animation is completed.
-  ScopedAnimationDisabler disable(window_);
-  window_->Hide();
-  WindowState::Get(window_)->Minimize();
+  window_util::HideAndMaybeMinimizeWithoutAnimation({window_},
+                                                    /*minimize=*/true);
 
   // Reset its transform to identity transform and its original backdrop mode.
   window_->layer()->SetTransform(gfx::Transform());
diff --git a/ash/public/cpp/autotest_private_api_utils.h b/ash/public/cpp/autotest_private_api_utils.h
index 02c6626..4068624 100644
--- a/ash/public/cpp/autotest_private_api_utils.h
+++ b/ash/public/cpp/autotest_private_api_utils.h
@@ -8,7 +8,6 @@
 #include <vector>
 
 #include "ash/ash_export.h"
-#include "ash/frame/header_view.h"
 #include "ash/public/cpp/app_list/app_list_types.h"
 #include "base/callback.h"
 
@@ -18,14 +17,10 @@
 
 // Utility functions for autotest private APIs and ShellTestAPI.
 namespace ash {
-class HeaderView;
 
 // Get application windows, windows that are shown in overview grid.
 ASH_EXPORT std::vector<aura::Window*> GetAppWindowList();
 
-// Get HeaderView from application windows.
-ASH_EXPORT ash::HeaderView* GetHeaderViewForWindow(aura::Window* window);
-
 // Runs the callback when the launcher state becomes |state| after
 // state transition animation. For clamshell launcher, it invokes closure
 // immediately if the state is already at the target state. For home
diff --git a/ash/public/cpp/caption_buttons/frame_caption_button_container_view.cc b/ash/public/cpp/caption_buttons/frame_caption_button_container_view.cc
index 43f4ce2..3d457b2dd 100644
--- a/ash/public/cpp/caption_buttons/frame_caption_button_container_view.cc
+++ b/ash/public/cpp/caption_buttons/frame_caption_button_container_view.cc
@@ -144,9 +144,10 @@
       case views::CAPTION_BUTTON_ICON_ZOOM:
         return false;
       case views::CAPTION_BUTTON_ICON_LOCATION:
+        // not used
+        return false;
       case views::CAPTION_BUTTON_ICON_COUNT:
         break;
-        // not used
     }
     NOTREACHED();
     return false;
diff --git a/ash/public/cpp/frame_header.cc b/ash/public/cpp/frame_header.cc
index 405daa0..b334f41 100644
--- a/ash/public/cpp/frame_header.cc
+++ b/ash/public/cpp/frame_header.cc
@@ -9,6 +9,7 @@
 #include "ash/public/cpp/vector_icons/vector_icons.h"
 #include "ash/public/cpp/window_properties.h"
 #include "base/logging.h"  // DCHECK
+#include "ui/base/class_property.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/color_utils.h"
 #include "ui/gfx/font_list.h"
@@ -21,9 +22,14 @@
 #include "ui/views/window/caption_button_layout_constants.h"
 #include "ui/views/window/vector_icons/vector_icons.h"
 
+DEFINE_UI_CLASS_PROPERTY_TYPE(ash::FrameHeader*)
+
 namespace ash {
 
 namespace {
+
+DEFINE_UI_CLASS_PROPERTY_KEY(FrameHeader*, kFrameHeaderKey, nullptr)
+
 // Returns the available bounds for the header's title given the views to the
 // left and right of the title, and the font used. |left_view| should be null
 // if there is no view to the left of the title.
@@ -82,7 +88,15 @@
 ///////////////////////////////////////////////////////////////////////////////
 // FrameHeader, public:
 
-FrameHeader::~FrameHeader() = default;
+// static
+FrameHeader* FrameHeader::Get(views::Widget* widget) {
+  return widget->GetNativeView()->GetProperty(kFrameHeaderKey);
+}
+
+FrameHeader::~FrameHeader() {
+  if (target_widget_->GetNativeView())
+    target_widget_->GetNativeView()->ClearProperty(kFrameHeaderKey);
+}
 
 int FrameHeader::GetMinimumHeaderWidth() const {
   // Ensure we have enough space for the window icon and buttons. We allow
@@ -172,6 +186,10 @@
   return back_button_;
 }
 
+const CaptionButtonModel* FrameHeader::GetCaptionButtonModel() const {
+  return caption_button_container_->model();
+}
+
 void FrameHeader::SetFrameTextOverride(
     const base::string16& frame_text_override) {
   frame_text_override_ = frame_text_override;
@@ -194,6 +212,7 @@
       view_(view) {
   DCHECK(target_widget);
   DCHECK(view);
+  target_widget_->GetNativeView()->SetProperty(kFrameHeaderKey, this);
 }
 
 gfx::Rect FrameHeader::GetPaintedBounds() const {
diff --git a/ash/public/cpp/frame_header.h b/ash/public/cpp/frame_header.h
index 13bfa68..6074eb2c 100644
--- a/ash/public/cpp/frame_header.h
+++ b/ash/public/cpp/frame_header.h
@@ -26,12 +26,15 @@
 }  // namespace views
 
 namespace ash {
+class CaptionButtonModel;
 
 // Helper class for managing the window header.
 class ASH_PUBLIC_EXPORT FrameHeader : public views::AnimationDelegateViews {
  public:
   enum Mode { MODE_ACTIVE, MODE_INACTIVE };
 
+  static FrameHeader* Get(views::Widget* widget);
+
   ~FrameHeader() override;
 
   const base::string16& frame_text_override() const {
@@ -69,6 +72,7 @@
   void SetLeftHeaderView(views::View* view);
   void SetBackButton(views::FrameCaptionButton* view);
   views::FrameCaptionButton* GetBackButton() const;
+  const CaptionButtonModel* GetCaptionButtonModel() const;
 
   // Updates the frame header painting to reflect a change in frame colors.
   virtual void UpdateFrameColors() = 0;
diff --git a/ash/public/cpp/immersive/immersive_fullscreen_controller.cc b/ash/public/cpp/immersive/immersive_fullscreen_controller.cc
index 41960266..7cac1a3 100644
--- a/ash/public/cpp/immersive/immersive_fullscreen_controller.cc
+++ b/ash/public/cpp/immersive/immersive_fullscreen_controller.cc
@@ -278,7 +278,7 @@
 }
 
 // static
-ImmersiveFullscreenController* ImmersiveFullscreenController::GetForTest(
+ImmersiveFullscreenController* ImmersiveFullscreenController::Get(
     views::Widget* widget) {
   return widget->GetNativeWindow()->GetProperty(
       kImmersiveFullscreenControllerKey);
diff --git a/ash/public/cpp/immersive/immersive_fullscreen_controller.h b/ash/public/cpp/immersive/immersive_fullscreen_controller.h
index c7cbc21..0e78095 100644
--- a/ash/public/cpp/immersive/immersive_fullscreen_controller.h
+++ b/ash/public/cpp/immersive/immersive_fullscreen_controller.h
@@ -136,7 +136,7 @@
 
   static void EnableForWidget(views::Widget* widget, bool enabled);
 
-  static ImmersiveFullscreenController* GetForTest(views::Widget* widget);
+  static ImmersiveFullscreenController* Get(views::Widget* widget);
 
  private:
   friend class ImmersiveFullscreenControllerTest;
diff --git a/ash/shelf/scrollable_shelf_view.cc b/ash/shelf/scrollable_shelf_view.cc
index 5b8bd676d..df5e42c 100644
--- a/ash/shelf/scrollable_shelf_view.cc
+++ b/ash/shelf/scrollable_shelf_view.cc
@@ -4,6 +4,7 @@
 
 #include "ash/shelf/scrollable_shelf_view.h"
 
+#include "ash/app_list/app_list_controller_impl.h"
 #include "ash/drag_drop/drag_image_view.h"
 #include "ash/public/cpp/shelf_config.h"
 #include "ash/screen_util.h"
@@ -13,6 +14,7 @@
 #include "ash/shelf/shelf_widget.h"
 #include "ash/system/status_area_widget.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
+#include "base/metrics/histogram_macros.h"
 #include "base/numerics/ranges.h"
 #include "chromeos/constants/chromeos_switches.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -215,6 +217,33 @@
 };
 
 ////////////////////////////////////////////////////////////////////////////////
+// ScrollableShelfAnimationMetricsReporter
+
+class ScrollableShelfAnimationMetricsReporter
+    : public ui::AnimationMetricsReporter {
+ public:
+  ScrollableShelfAnimationMetricsReporter() {}
+
+  ~ScrollableShelfAnimationMetricsReporter() override = default;
+
+  // ui::AnimationMetricsReporter:
+  void Report(int value) override {
+    UMA_HISTOGRAM_PERCENTAGE("Apps.ScrollableShelf.AnimationSmoothness", value);
+    if (Shell::Get()->app_list_controller()->IsVisible()) {
+      UMA_HISTOGRAM_PERCENTAGE(
+          "Apps.ScrollableShelf.AnimationSmoothness.VisibleHomeLauncher",
+          value);
+    } else {
+      UMA_HISTOGRAM_PERCENTAGE(
+          "Apps.ScrollableShelf.AnimationSmoothness.NotVisibleHomeLauncher",
+          value);
+    }
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(ScrollableShelfAnimationMetricsReporter);
+};
+
+////////////////////////////////////////////////////////////////////////////////
 // ScrollableShelfContainerView
 
 class ScrollableShelfContainerView : public ShelfContainerView,
@@ -342,7 +371,9 @@
                                 shelf,
                                 /*drag_and_drop_host=*/this,
                                 /*shelf_button_delegate=*/this)),
-      page_flip_time_threshold_(kShelfPageFlipDelay) {
+      page_flip_time_threshold_(kShelfPageFlipDelay),
+      animation_metrics_reporter_(
+          std::make_unique<ScrollableShelfAnimationMetricsReporter>()) {
   Shell::Get()->AddShellObserver(this);
   set_allow_deactivate_on_esc(true);
 }
@@ -512,6 +543,8 @@
   animation_settings.SetTweenType(gfx::Tween::EASE_OUT);
   animation_settings.SetPreemptionStrategy(
       ui::LayerAnimator::IMMEDIATELY_SET_NEW_TARGET);
+  animation_settings.SetAnimationMetricsReporter(
+      animation_metrics_reporter_.get());
   animation_settings.AddObserver(this);
   shelf_view_->layer()->SetTransform(current_transform);
 }
diff --git a/ash/shelf/scrollable_shelf_view.h b/ash/shelf/scrollable_shelf_view.h
index 4e41892..da1e5b8 100644
--- a/ash/shelf/scrollable_shelf_view.h
+++ b/ash/shelf/scrollable_shelf_view.h
@@ -375,6 +375,10 @@
 
   base::OneShotTimer page_flip_timer_;
 
+  // Metric reporter for scrolling animations.
+  const std::unique_ptr<ui::AnimationMetricsReporter>
+      animation_metrics_reporter_;
+
   DISALLOW_COPY_AND_ASSIGN(ScrollableShelfView);
 };
 
diff --git a/ash/system/network/network_info.h b/ash/system/network/network_info.h
index 5254248..2f1b538f 100644
--- a/ash/system/network/network_info.h
+++ b/ash/system/network/network_info.h
@@ -32,11 +32,13 @@
   base::string16 tooltip;
   gfx::ImageSkia image;
   bool disable = false;
+  bool secured = false;
   // Initialized in .cc file because full (non-forward) mojom headers are large.
   chromeos::network_config::mojom::ConnectionStateType connection_state;
   chromeos::network_config::mojom::NetworkType type;
   chromeos::network_config::mojom::OncSource source;
   int battery_percentage = 0;
+  int signal_strength = 0;
   std::string captive_portal_provider_name;
 };
 
diff --git a/ash/system/network/network_list_view.cc b/ash/system/network/network_list_view.cc
index 3c97357..38a14f1 100644
--- a/ash/system/network/network_list_view.cc
+++ b/ash/system/network/network_list_view.cc
@@ -76,6 +76,11 @@
       AshColorProvider::AshColorMode::kDark);
 }
 
+bool IsManagedByPolicy(const NetworkInfo& info) {
+  return info.source == OncSource::kDevicePolicy ||
+         info.source == OncSource::kUserPolicy;
+}
+
 }  // namespace
 
 // NetworkListView:
@@ -151,6 +156,8 @@
         break;
       case NetworkType::kWiFi:
         wifi_has_networks_ = true;
+        info->secured = network->type_state->get_wifi()->security !=
+                        chromeos::network_config::mojom::SecurityType::kNone;
         break;
       case NetworkType::kTether:
         mobile_has_networks_ = true;
@@ -175,6 +182,9 @@
 
     info->connection_state = connection_state;
 
+    info->signal_strength =
+        chromeos::network_config::GetWirelessSignalStrength(network.get());
+
     if (network->captive_portal_provider) {
       info->captive_portal_provider_name =
           network->captive_portal_provider->name;
@@ -379,9 +389,103 @@
       view->AddRightView(icon);
   }
 
+  view->SetAccessibleName(GenerateAccessibilityLabel(info));
+
   needs_relayout_ = true;
 }
 
+base::string16 NetworkListView::GenerateAccessibilityLabel(
+    const NetworkInfo& info) {
+  base::string16 connection_status;
+  if (StateIsConnected(info.connection_state) ||
+      info.connection_state == ConnectionStateType::kConnecting) {
+    connection_status = l10n_util::GetStringUTF16(
+        StateIsConnected(info.connection_state)
+            ? IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTED
+            : IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTING);
+  }
+
+  switch (info.type) {
+    case NetworkType::kEthernet:
+      if (!connection_status.empty()) {
+        if (IsManagedByPolicy(info)) {
+          return l10n_util::GetStringFUTF16(
+              IDS_ASH_STATUS_TRAY_ETHERNET_A11Y_LABEL_MANAGED_WITH_CONNECTION_STATUS,
+              info.label, connection_status);
+        }
+        return l10n_util::GetStringFUTF16(
+            IDS_ASH_STATUS_TRAY_ETHERNET_A11Y_LABEL_WITH_CONNECTION_STATUS,
+            info.label, connection_status);
+      }
+      if (IsManagedByPolicy(info)) {
+        return l10n_util::GetStringFUTF16(
+            IDS_ASH_STATUS_TRAY_ETHERNET_A11Y_LABEL_MANAGED, info.label,
+            connection_status);
+      }
+      return info.label;
+    case NetworkType::kWiFi: {
+      base::string16 security_label = l10n_util::GetStringUTF16(
+          info.secured ? IDS_ASH_STATUS_TRAY_NETWORK_STATUS_SECURED
+                       : IDS_ASH_STATUS_TRAY_NETWORK_STATUS_UNSECURED);
+      if (!connection_status.empty()) {
+        if (IsManagedByPolicy(info)) {
+          return l10n_util::GetStringFUTF16(
+              IDS_ASH_STATUS_TRAY_WIFI_NETWORK_A11Y_LABEL_MANAGED_WITH_CONNECTION_STATUS,
+              info.label, security_label, connection_status,
+              base::FormatPercent(info.signal_strength));
+        }
+        return l10n_util::GetStringFUTF16(
+            IDS_ASH_STATUS_TRAY_WIFI_NETWORK_A11Y_LABEL_WITH_CONNECTION_STATUS,
+            info.label, security_label, connection_status,
+            base::FormatPercent(info.signal_strength));
+      }
+      if (IsManagedByPolicy(info)) {
+        return l10n_util::GetStringFUTF16(
+            IDS_ASH_STATUS_TRAY_WIFI_NETWORK_A11Y_LABEL_MANAGED, info.label,
+            security_label, base::FormatPercent(info.signal_strength));
+      }
+      return l10n_util::GetStringFUTF16(
+          IDS_ASH_STATUS_TRAY_WIFI_NETWORK_A11Y_LABEL, info.label,
+          security_label, base::FormatPercent(info.signal_strength));
+    }
+    case NetworkType::kCellular:
+      if (!connection_status.empty()) {
+        if (IsManagedByPolicy(info)) {
+          return l10n_util::GetStringFUTF16(
+              IDS_ASH_STATUS_TRAY_CELLULAR_NETWORK_A11Y_LABEL_MANAGED_WITH_CONNECTION_STATUS,
+              info.label, connection_status,
+              base::FormatPercent(info.signal_strength));
+        }
+        return l10n_util::GetStringFUTF16(
+            IDS_ASH_STATUS_TRAY_CELLULAR_NETWORK_A11Y_LABEL_WITH_CONNECTION_STATUS,
+            info.label, connection_status,
+            base::FormatPercent(info.signal_strength));
+      }
+      if (IsManagedByPolicy(info)) {
+        return l10n_util::GetStringFUTF16(
+            IDS_ASH_STATUS_TRAY_CELLULAR_NETWORK_A11Y_LABEL_MANAGED, info.label,
+            base::FormatPercent(info.signal_strength));
+      }
+      return l10n_util::GetStringFUTF16(
+          IDS_ASH_STATUS_TRAY_CELLULAR_NETWORK_A11Y_LABEL, info.label,
+          base::FormatPercent(info.signal_strength));
+    case NetworkType::kTether:
+      if (!connection_status.empty()) {
+        return l10n_util::GetStringFUTF16(
+            IDS_ASH_STATUS_TRAY_TETHER_NETWORK_A11Y_LABEL_WITH_CONNECTION_STATUS,
+            info.label, connection_status,
+            base::FormatPercent(info.signal_strength),
+            base::FormatPercent(info.battery_percentage));
+      }
+      return l10n_util::GetStringFUTF16(
+          IDS_ASH_STATUS_TRAY_TETHER_NETWORK_A11Y_LABEL, info.label,
+          base::FormatPercent(info.signal_strength),
+          base::FormatPercent(info.battery_percentage));
+    default:
+      return info.label;
+  }
+}  // namespace tray
+
 views::View* NetworkListView::CreatePowerStatusView(const NetworkInfo& info) {
   // Mobile can be Cellular or Tether.
   if (!NetworkTypeMatchesType(info.type, NetworkType::kMobile))
diff --git a/ash/system/network/network_list_view.h b/ash/system/network/network_list_view.h
index e683ddb6..263d61a 100644
--- a/ash/system/network/network_list_view.h
+++ b/ash/system/network/network_list_view.h
@@ -128,6 +128,10 @@
   // otherwise false.
   bool NeedUpdateViewForNetwork(const NetworkInfo& info) const;
 
+  // Creates a label for the given network which includes all information
+  // that is shown in the ui.
+  base::string16 GenerateAccessibilityLabel(const NetworkInfo& info);
+
   bool needs_relayout_ = false;
 
   // Owned by the views heirarchy.
diff --git a/ash/system/tray/tray_detailed_view.cc b/ash/system/tray/tray_detailed_view.cc
index bd8b640b..900fe06 100644
--- a/ash/system/tray/tray_detailed_view.cc
+++ b/ash/system/tray/tray_detailed_view.cc
@@ -365,34 +365,24 @@
   base::string16 status;
 
   if (battery_percentage) {
-    status = l10n_util::GetStringFUTF16(
+    view->SetSubText(l10n_util::GetStringFUTF16(
         IDS_ASH_STATUS_TRAY_BLUETOOTH_DEVICE_CONNECTED_WITH_BATTERY_LABEL,
-        base::NumberToString16(battery_percentage.value()));
+        base::NumberToString16(battery_percentage.value())));
   } else {
-    status =
-        l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTED);
+    view->SetSubText(l10n_util::GetStringUTF16(
+        IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTED));
   }
-  view->SetSubText(status);
 
   TrayPopupItemStyle style(TrayPopupItemStyle::FontStyle::CAPTION);
   style.set_color_style(TrayPopupItemStyle::ColorStyle::CONNECTED);
   style.SetupLabel(view->sub_text_label());
-
-  view->SetAccessibleName(l10n_util::GetStringFUTF16(
-      IDS_ASH_STATUS_TRAY_NETWORK_ACCESSIBILITY_LABEL,
-      view->text_label()->GetText(), status));
 }
 
 void TrayDetailedView::SetupConnectingScrollListItem(HoverHighlightView* view) {
   DCHECK(view->is_populated());
 
-  base::string16 status =
-      l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTING);
-
-  view->SetSubText(status);
-  view->SetAccessibleName(l10n_util::GetStringFUTF16(
-      IDS_ASH_STATUS_TRAY_NETWORK_ACCESSIBILITY_LABEL,
-      view->text_label()->GetText(), status));
+  view->SetSubText(
+      l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTING));
 }
 
 TriView* TrayDetailedView::AddScrollListSubHeader(const gfx::VectorIcon& icon,
diff --git a/ash/wm/immersive_fullscreen_controller_unittest.cc b/ash/wm/immersive_fullscreen_controller_unittest.cc
index 0ce1eb69..a60d6d2 100644
--- a/ash/wm/immersive_fullscreen_controller_unittest.cc
+++ b/ash/wm/immersive_fullscreen_controller_unittest.cc
@@ -140,7 +140,7 @@
   ~ImmersiveFullscreenControllerTest() override = default;
 
   ImmersiveFullscreenController* controller() {
-    return ImmersiveFullscreenController::GetForTest(widget());
+    return ImmersiveFullscreenController::Get(widget());
   }
 
   views::NativeViewHost* content_view() { return content_view_; }
diff --git a/base/android/junit/src/org/chromium/base/task/AsyncTaskThreadTest.java b/base/android/junit/src/org/chromium/base/task/AsyncTaskThreadTest.java
index 5072c573..8bd26086 100644
--- a/base/android/junit/src/org/chromium/base/task/AsyncTaskThreadTest.java
+++ b/base/android/junit/src/org/chromium/base/task/AsyncTaskThreadTest.java
@@ -14,6 +14,7 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -171,6 +172,7 @@
 
     @Test
     @SmallTest
+    @Ignore("crbug.com/1022954")
     public void testCancel_MayInterrupt_TaskIsInterrupted() throws Exception {
         mTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
 
diff --git a/base/containers/intrusive_heap_unittest.cc b/base/containers/intrusive_heap_unittest.cc
index f0927243..65aee84 100644
--- a/base/containers/intrusive_heap_unittest.cc
+++ b/base/containers/intrusive_heap_unittest.cc
@@ -652,8 +652,9 @@
 TEST(IntrusiveHeapTest, CopyOnlyNoDefaultConstructorTest) {
   using ValueType = Value_dmC;
   ValidateValueType<ValueType, false, false, true>();
-  CopyStressTest<ValueType>();
-  GeneralStressTest<ValueType>();
+  // We cannot perform CopyStressTest nor GeneralStressTest here, because
+  // Value_dmC has deleted move constructor and assignment operator. See
+  // crbug.com/1022576.
 }
 
 TEST(IntrusiveHeapTest, CopyAndMoveNoDefaultConstructorTest) {
@@ -674,8 +675,9 @@
 TEST(IntrusiveHeapTest, CopyOnlyWithDefaultConstructorTest) {
   using ValueType = Value_DmC;
   ValidateValueType<ValueType, true, false, true>();
-  CopyStressTest<ValueType>();
-  GeneralStressTest<ValueType>();
+  // We cannot perform CopyStressTest nor GeneralStressTest here, because
+  // Value_DmC has deleted move constructor and assignment operator. See
+  // crbug.com/1022576.
 }
 
 TEST(IntrusiveHeapTest, CopyAndMoveWithDefaultConstructorTest) {
diff --git a/base/debug/stack_trace_unittest.cc b/base/debug/stack_trace_unittest.cc
index 673420f4..07f51a0 100644
--- a/base/debug/stack_trace_unittest.cc
+++ b/base/debug/stack_trace_unittest.cc
@@ -31,11 +31,25 @@
 typedef testing::Test StackTraceTest;
 #endif
 
-#if !defined(__UCLIBC__) && !defined(_AIX)
-// StackTrace::OutputToStream() is not implemented under uclibc, nor AIX.
-// See https://crbug.com/706728
+// TODO(https://crbug.com/999737): Rewrite this test for better clarity and
+// correctness.
+// Note: On Linux, this test currently only fully works on Debug builds.
+// See comments in the #ifdef soup if you intend to change this.
+#if defined(OS_WIN)
 
-TEST_F(StackTraceTest, OutputToStream) {
+// Always fails on Windows: crbug.com/32070
+#define MAYBE_OutputToStream DISABLED_OutputToStream
+
+#elif defined(OS_FUCHSIA) && defined(OFFICIAL_BUILD)
+
+// Backtraces aren't supported by Fuchsia release-optimized builds.
+#define MAYBE_OutputToStream DISABLED_OutputToStream
+
+#else
+#define MAYBE_OutputToStream OutputToStream
+#endif
+#if !defined(__UCLIBC__) && !defined(_AIX)
+TEST_F(StackTraceTest, MAYBE_OutputToStream) {
   StackTrace trace;
 
   // Dump the trace into a string.
@@ -46,54 +60,76 @@
   // ToString() should produce the same output.
   EXPECT_EQ(backtrace_message, trace.ToString());
 
-  size_t frames_found = 0;
-  const void* const* addresses = trace.Addresses(&frames_found);
-  ASSERT_TRUE(addresses);
-  ASSERT_GT(frames_found, 0u) << "No stack frames found.";
-
-#if defined(OFFICIAL_BUILD) && defined(OS_POSIX) && !defined(OS_MACOSX)
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && NDEBUG
   // Stack traces require an extra data table that bloats our binaries,
-  // so they're turned off for official builds. Stop the test here, so
-  // it at least verifies that StackTrace calls don't crash.
+  // so they're turned off for release builds.  We stop the test here,
+  // at least letting us verify that the calls don't crash.
   return;
-#endif  // defined(OFFICIAL_BUILD) && defined(OS_POSIX) && !defined(OS_MACOSX)
+#endif  // defined(OS_POSIX) && !defined(OS_MACOSX) && NDEBUG
 
-  ASSERT_GT(frames_found, 5u) << "Too few frames found.";
-
-#if defined(OS_FUCHSIA) || defined(OS_ANDROID)
-  // Under Fuchsia and Android, StackTrace emits executable build-Ids and
-  // address offsets which are symbolized on the test host system, rather than
-  // being symbolized in-process.
-  return;
-#endif  // defined(OS_FUCHSIA) || defined(OS_ANDROID)
-
-#if defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER)
-  // Configurations such as ASAN and TSan emit unsymbolized stacks.
-  return;
-#endif  // defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER)
+  size_t frames_found = 0;
+  trace.Addresses(&frames_found);
+  ASSERT_GE(frames_found, 5u) <<
+      "No stack frames found.  Skipping rest of test.";
 
   // Check if the output has symbol initialization warning.  If it does, fail.
   ASSERT_EQ(backtrace_message.find("Dumping unresolved backtrace"),
-            std::string::npos)
-      << "Unable to resolve symbols.";
+            std::string::npos) <<
+      "Unable to resolve symbols.  Skipping rest of test.";
 
+#if defined(OS_MACOSX)
+#if 0
+  // Disabled due to -fvisibility=hidden in build config.
+
+  // Symbol resolution via the backtrace_symbol function does not work well
+  // in OS X.
+  // See this thread:
+  //
+  //    http://lists.apple.com/archives/darwin-dev/2009/Mar/msg00111.html
+  //
+  // Just check instead that we find our way back to the "start" symbol
+  // which should be the first symbol in the trace.
+  //
+  // TODO(port): Find a more reliable way to resolve symbols.
+
+  // Expect to at least find main.
+  EXPECT_TRUE(backtrace_message.find("start") != std::string::npos)
+      << "Expected to find start in backtrace:\n"
+      << backtrace_message;
+
+#endif
+#elif defined(USE_SYMBOLIZE)
+  // This branch is for gcc-compiled code, but not Mac due to the
+  // above #if.
   // Expect a demangled symbol.
-  // Note that Windows Release builds omit the function parameters from the
-  // demangled stack output, otherwise this could be "testing::Test::Run()".
-  EXPECT_TRUE(backtrace_message.find("testing::Test::Run") != std::string::npos)
+  EXPECT_TRUE(backtrace_message.find("testing::Test::Run()") !=
+              std::string::npos)
       << "Expected a demangled symbol in backtrace:\n"
       << backtrace_message;
 
+#elif 0
+  // This is the fall-through case; it used to cover Windows.
+  // But it's disabled because of varying buildbot configs;
+  // some lack symbols.
+
   // Expect to at least find main.
   EXPECT_TRUE(backtrace_message.find("main") != std::string::npos)
       << "Expected to find main in backtrace:\n"
       << backtrace_message;
 
+#if defined(OS_WIN)
+// MSVC doesn't allow the use of C99's __func__ within C++, so we fake it with
+// MSVC's __FUNCTION__ macro.
+#define __func__ __FUNCTION__
+#endif
+
   // Expect to find this function as well.
   // Note: This will fail if not linked with -rdynamic (aka -export_dynamic)
   EXPECT_TRUE(backtrace_message.find(__func__) != std::string::npos)
       << "Expected to find " << __func__ << " in backtrace:\n"
       << backtrace_message;
+
+#endif  // define(OS_MACOSX)
 }
 
 #if !defined(OFFICIAL_BUILD) && !defined(NO_UNWIND_TABLES)
@@ -110,7 +146,7 @@
   truncated.Addresses(&count);
   EXPECT_EQ(2u, count);
 }
-#endif  // !defined(OFFICIAL_BUILD) && !defined(NO_UNWIND_TABLES)
+#endif  // !defined(OFFICIAL_BUILD)
 
 // The test is used for manual testing, e.g., to see the raw output.
 TEST_F(StackTraceTest, DebugOutputToStream) {
@@ -158,7 +194,7 @@
   trace.ToStringWithPrefix(nullptr);
 }
 
-#endif  // !defined(__UCLIBC__) && !defined(_AIX)
+#endif  // !defined(__UCLIBC__)
 
 #if defined(OS_POSIX) && !defined(OS_ANDROID)
 #if !defined(OS_IOS)
diff --git a/base/task/post_job.h b/base/task/post_job.h
index cf8ca1a..7c7014cd 100644
--- a/base/task/post_job.h
+++ b/base/task/post_job.h
@@ -84,6 +84,9 @@
   JobHandle(JobHandle&&);
   JobHandle& operator=(JobHandle&&);
 
+  // Returns true if associated with a Job.
+  explicit operator bool() const { return task_source_ != nullptr; }
+
   // Update this Job's priority.
   void UpdatePriority(TaskPriority new_priority);
 
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 8cc1458..f0ff35a 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-8897362889837801216
\ No newline at end of file
+8897331871501701008
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 640e107..6128bd99 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-8897363254003807056
\ No newline at end of file
+8897334129707393184
\ No newline at end of file
diff --git a/build/win/copy_cdb_to_output.py b/build/win/copy_cdb_to_output.py
index 1f0f22a..a0b99bb7 100755
--- a/build/win/copy_cdb_to_output.py
+++ b/build/win/copy_cdb_to_output.py
@@ -102,7 +102,13 @@
   _CopyImpl('exts.dll', dst_winxp_dir, src_winxp_dir)
   _CopyImpl('ntsdexts.dll', dst_winxp_dir, src_winxp_dir)
   if src_arch in ['x64', 'x86']:
-    _CopyImpl('api-ms-win-eventing-provider-l1-1-0.dll', output_dir, src_dir)
+    # Copy all UCRT files from the debuggers directory, for compatibility with
+    # the Windows 10 18362 SDK (one UCRT file) and later versions (two UCRT
+    # files). The new file is api-ms-win-downlevel-kernel32-l2-1-0.dll and
+    # should be added to the copy_cdb_to_output outputs when we require a newer
+    # SDK.
+    for file in glob.glob(os.path.join(src_dir, 'api-ms-win*.dll')):
+      _CopyImpl(os.path.split(file)[1], output_dir, src_dir)
     _CopyImpl('ucrtbase.dll', output_dir, src_crt_dir)
   for dll_path in glob.glob(os.path.join(src_crt_dir, 'api-ms-win-*.dll')):
     _CopyImpl(os.path.split(dll_path)[1], output_dir, src_crt_dir)
diff --git a/chrome/BUILD.gn b/chrome/BUILD.gn
index a834d4c..7b9cf47 100644
--- a/chrome/BUILD.gn
+++ b/chrome/BUILD.gn
@@ -1560,6 +1560,7 @@
 
   if (!is_android) {
     public_deps += [
+      "//chrome/browser/resources:bookmarks_resources",
       "//chrome/browser/resources:component_extension_resources",
       "//chrome/browser/resources:dev_ui_paks",
       "//chrome/browser/resources:downloads_resources",
diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
index 8133889..dfbf1c46 100644
--- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
+++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
@@ -6,6 +6,7 @@
 
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_FAKE_SEARCH_BOX_VISIBLE;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_INCOGNITO;
+import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_TAB_CAROUSEL_VISIBLE;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_VOICE_RECOGNITION_BUTTON_VISIBLE;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.MORE_TABS_CLICK_LISTENER;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.MV_TILES_VISIBLE;
@@ -32,6 +33,8 @@
 import org.chromium.chrome.browser.night_mode.NightModeStateProvider;
 import org.chromium.chrome.browser.ntp.FakeboxDelegate;
 import org.chromium.chrome.browser.omnibox.UrlFocusChangeListener;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tabmodel.EmptyTabModelObserver;
 import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver;
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
@@ -67,6 +70,7 @@
 
     private final ObserverList<StartSurface.OverviewModeObserver> mObservers = new ObserverList<>();
     private final TabSwitcher.Controller mController;
+    private final TabModelSelector mTabModelSelector;
     @Nullable
     private final PropertyModel mPropertyModel;
     @Nullable
@@ -95,6 +99,7 @@
             @SurfaceMode int surfaceMode, @Nullable FakeboxDelegate fakeboxDelegate,
             NightModeStateProvider nightModeStateProvider) {
         mController = controller;
+        mTabModelSelector = tabModelSelector;
         mPropertyModel = propertyModel;
         mFeedSurfaceCreator = feedSurfaceCreator;
         mSecondaryTasksSurfaceInitializer = secondaryTasksSurfaceInitializer;
@@ -107,8 +112,9 @@
                     || mSurfaceMode == SurfaceMode.TASKS_ONLY;
             assert mFakeboxDelegate != null;
 
-            mIsIncognito = tabModelSelector.isIncognitoSelected();
-            tabModelSelector.addObserver(new EmptyTabModelSelectorObserver() {
+            mIsIncognito = mTabModelSelector.isIncognitoSelected();
+
+            mTabModelSelector.addObserver(new EmptyTabModelSelectorObserver() {
                 @Override
                 public void onTabModelSelected(TabModel newModel, TabModel oldModel) {
                     // TODO(crbug.com/982018): Optimize to not listen for selected Tab model change
@@ -148,6 +154,25 @@
 
             if (mSurfaceMode == SurfaceMode.SINGLE_PANE) {
                 mPropertyModel.set(MORE_TABS_CLICK_LISTENER, this);
+
+                // Hide tab carousel, which does not exist in incognito mode, when closing all
+                // normal tabs.
+                TabModel normalTabModel = mTabModelSelector.getModel(false);
+                normalTabModel.addObserver(new EmptyTabModelObserver() {
+                    @Override
+                    public void willCloseTab(Tab tab, boolean animate) {
+                        if (normalTabModel.getCount() <= 1
+                                || mPropertyModel.get(IS_SHOWING_OVERVIEW)) {
+                            setTabCarouselVisibility(false);
+                        }
+                    }
+                    @Override
+                    public void tabClosureUndone(Tab tab) {
+                        if (mPropertyModel.get(IS_SHOWING_OVERVIEW)) {
+                            setTabCarouselVisibility(true);
+                        }
+                    }
+                });
             }
 
             // Set the initial state.
@@ -241,6 +266,7 @@
                     setSecondaryTasksSurfaceVisibility(true);
                 } else {
                     setExploreSurfaceVisibility(true);
+                    setTabCarouselVisibility(mTabModelSelector.getModel(false).getCount() > 0);
                 }
             } else if (mSurfaceMode == SurfaceMode.TWO_PANES) {
                 RecordUserAction.record("StartSurface.TwoPanes");
@@ -467,4 +493,19 @@
         // Do not show Tab switcher toolbar when focusing the Omnibox.
         return mPropertyModel.get(IS_FAKE_SEARCH_BOX_VISIBLE);
     }
+
+    private void setTabCarouselVisibility(boolean isVisible) {
+        assert !mIsIncognito;
+
+        if (isVisible == mPropertyModel.get(IS_TAB_CAROUSEL_VISIBLE)) return;
+
+        // Hide the more Tabs view when the last Tab is closed.
+        if (!isVisible && mSecondaryTasksSurfaceController != null
+                && mSecondaryTasksSurfaceController.overviewVisible()) {
+            setSecondaryTasksSurfaceVisibility(false);
+            setExploreSurfaceVisibility(true);
+        }
+
+        mPropertyModel.set(IS_TAB_CAROUSEL_VISIBLE, isVisible);
+    }
 }
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutTest.java
index 2f9bab8..6a35731c 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutTest.java
@@ -13,7 +13,6 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
-import static org.chromium.base.test.util.CallbackHelper.WAIT_TIMEOUT_SECONDS;
 import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.areAnimatorsEnabled;
 import static org.chromium.chrome.browser.util.UrlConstants.NTP_URL;
 import static org.chromium.content_public.browser.test.util.CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL;
@@ -49,12 +48,9 @@
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.compositor.layouts.Layout;
-import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
 import org.chromium.chrome.browser.flags.FeatureUtilities;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabFeatureUtilities;
-import org.chromium.chrome.browser.tabmodel.TabModel;
-import org.chromium.chrome.browser.tabmodel.TabSelectionType;
 import org.chromium.chrome.browser.tasks.tab_management.TabSwitcher;
 import org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper;
 import org.chromium.chrome.tab_ui.R;
@@ -72,7 +68,6 @@
 import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.ui.test.util.UiRestriction;
 
-import java.io.File;
 import java.io.IOException;
 import java.lang.ref.WeakReference;
 import java.util.LinkedList;
@@ -137,9 +132,7 @@
                         .getCurrentTabModelFilter()::isTabModelRestored));
 
         assertEquals(0, mTabListDelegate.getBitmapFetchCountForTesting());
-        // Only skip thumbnail releasing assertion when "warm" (large soft-cleanup-delay) or in
-        // RenderTest.
-        // TODO(wychen): figure out why thumbnails are not released in RenderTest.
+        // Only skip thumbnail releasing assertion when "warm" (large soft-cleanup-delay).
         mSkipAssertThumbnailsAreReleased = false;
     }
 
@@ -153,16 +146,14 @@
     @Feature({"RenderTest"})
     @CommandLineFlags.Add({BASE_PARAMS})
     public void testRenderGrid_3WebTabs() throws InterruptedException, IOException {
-        mSkipAssertThumbnailsAreReleased = true;
-
         prepareTabs(3, 0, mUrl);
         TabUiTestHelper.enterTabSwitcher(mActivityTestRule.getActivity());
         TabUiTestHelper.clickFirstCardFromTabSwitcher(mActivityTestRule.getActivity());
 
         enterGTS();
-        mRenderTestRule.render(mActivityTestRule.getActivity().findViewById(
-                                       org.chromium.chrome.tab_ui.R.id.tab_list_view),
-                "3_web_tabs");
+        mRenderTestRule.render(
+                mActivityTestRule.getActivity().findViewById(R.id.tab_list_view), "3_web_tabs");
+        leaveGTS();
     }
 
     @Test
@@ -170,16 +161,14 @@
     @Feature({"RenderTest"})
     @CommandLineFlags.Add({BASE_PARAMS})
     public void testRenderGrid_10WebTabs() throws InterruptedException, IOException {
-        mSkipAssertThumbnailsAreReleased = true;
-
         prepareTabs(10, 0, mUrl);
         TabUiTestHelper.enterTabSwitcher(mActivityTestRule.getActivity());
         TabUiTestHelper.clickFirstCardFromTabSwitcher(mActivityTestRule.getActivity());
 
         enterGTS();
-        mRenderTestRule.render(mActivityTestRule.getActivity().findViewById(
-                                       org.chromium.chrome.tab_ui.R.id.tab_list_view),
-                "10_web_tabs");
+        mRenderTestRule.render(
+                mActivityTestRule.getActivity().findViewById(R.id.tab_list_view), "10_web_tabs");
+        leaveGTS();
     }
 
     @Test
@@ -187,8 +176,6 @@
     @Feature({"RenderTest"})
     @CommandLineFlags.Add({BASE_PARAMS})
     public void testRenderGrid_10WebTabs_InitialScroll() throws InterruptedException, IOException {
-        mSkipAssertThumbnailsAreReleased = true;
-
         prepareTabs(10, 0, mUrl);
         TabUiTestHelper.enterTabSwitcher(mActivityTestRule.getActivity());
         TabUiTestHelper.clickNthCardFromTabSwitcher(mActivityTestRule.getActivity(),
@@ -197,9 +184,9 @@
 
         enterGTS();
         // Make sure the grid tab switcher is scrolled down to show the selected tab.
-        mRenderTestRule.render(mActivityTestRule.getActivity().findViewById(
-                                       org.chromium.chrome.tab_ui.R.id.tab_list_view),
+        mRenderTestRule.render(mActivityTestRule.getActivity().findViewById(R.id.tab_list_view),
                 "10_web_tabs-select_last");
+        leaveGTS();
     }
 
     @Test
@@ -207,8 +194,6 @@
     @Feature({"RenderTest"})
     @CommandLineFlags.Add({BASE_PARAMS})
     public void testRenderGrid_Incognito() throws InterruptedException, IOException {
-        mSkipAssertThumbnailsAreReleased = true;
-
         // Prepare some incognito tabs and enter tab switcher.
         prepareTabs(1, 3, mUrl);
         assertTrue(mActivityTestRule.getActivity().getCurrentTabModel().isIncognito());
@@ -216,9 +201,28 @@
         TabUiTestHelper.clickFirstCardFromTabSwitcher(mActivityTestRule.getActivity());
 
         enterGTS();
-        mRenderTestRule.render(mActivityTestRule.getActivity().findViewById(
-                                       org.chromium.chrome.tab_ui.R.id.tab_list_view),
+        mRenderTestRule.render(mActivityTestRule.getActivity().findViewById(R.id.tab_list_view),
                 "3_incognito_web_tabs");
+        leaveGTS();
+    }
+
+    @Test
+    @MediumTest
+    @Feature({"RenderTest"})
+    @CommandLineFlags.Add({BASE_PARAMS})
+    public void testRenderGrid_3IncognitoNTPs() throws InterruptedException, IOException {
+        // Prepare some incognito native tabs and enter tab switcher.
+        // NTP in incognito mode is chosen for its consistency in look, and we don't have to mock
+        // away the MV tiles, login promo, feed, etc.
+        prepareTabs(1, 3, null);
+        assertTrue(mActivityTestRule.getActivity().getCurrentTabModel().isIncognito());
+        TabUiTestHelper.enterTabSwitcher(mActivityTestRule.getActivity());
+        TabUiTestHelper.clickFirstCardFromTabSwitcher(mActivityTestRule.getActivity());
+
+        enterGTS();
+        mRenderTestRule.render(mActivityTestRule.getActivity().findViewById(R.id.tab_list_view),
+                "3_incognito_ntps");
+        leaveGTS();
     }
 
     @Test
@@ -311,119 +315,18 @@
 
     /**
      * Make Chrome have {@code numTabs} of regular Tabs and {@code numIncognitoTabs} of incognito
-     * tabs with {@code url} loaded.
+     * tabs with {@code url} loaded, and assert no bitmap fetching occurred.
      *
      * @param numTabs The number of regular tabs.
      * @param numIncognitoTabs The number of incognito tabs.
      * @param url The URL to load.
      */
     private void prepareTabs(int numTabs, int numIncognitoTabs, @Nullable String url) {
-        assertTrue(numTabs >= 1);
-        assertTrue(numIncognitoTabs >= 0);
-
         int oldCount = mTabListDelegate.getBitmapFetchCountForTesting();
-        assertEquals(1,
-                mActivityTestRule.getActivity().getTabModelSelector().getModel(false).getCount());
-        assertEquals(
-                0, mActivityTestRule.getActivity().getTabModelSelector().getModel(true).getCount());
-
-        if (numTabs == 1) {
-            if (url != null) mActivityTestRule.loadUrl(url);
-        } else {
-            // When Chrome started, there is already one Tab created by default.
-            createTabs(numTabs - 1, url, true, false);
-        }
-        if (numIncognitoTabs > 0) createTabs(numIncognitoTabs, url, true, true);
-
-        assertEquals(numTabs,
-                mActivityTestRule.getActivity().getTabModelSelector().getModel(false).getCount());
-        assertEquals(numIncognitoTabs,
-                mActivityTestRule.getActivity().getTabModelSelector().getModel(true).getCount());
+        TabUiTestHelper.prepareTabsWithThumbnail(mActivityTestRule, numTabs, numIncognitoTabs, url);
         assertEquals(0, mTabListDelegate.getBitmapFetchCountForTesting() - oldCount);
     }
 
-    /**
-     * When Chrome started, there is already one Tab created by default. This method is used to add
-     * additional {@code numTabs} of {@link Tab}s with {@code url} loaded to Chrome.
-     * @param numTabs The number of tabs to create.
-     * @param url The URL to load. Skip loading when null, but the thumbnail for the NTP might not
-     *            be saved.
-     * @param waitForLoading Whether wait for URL loading.
-     * @param isIncognito Whether the tab is incognito tab.
-     */
-    private void createTabs(
-            int numTabs, @Nullable String url, boolean waitForLoading, boolean isIncognito) {
-        assertTrue(numTabs >= 1);
-
-        if (url != null) mActivityTestRule.loadUrl(url);
-
-        int previousTabCount = mActivityTestRule.getActivity()
-                                       .getTabModelSelector()
-                                       .getModel(isIncognito)
-                                       .getCount();
-
-        for (int i = 0; i < numTabs; i++) {
-            TabModel previousTabModel =
-                    mActivityTestRule.getActivity().getTabModelSelector().getCurrentModel();
-            int previousTabIndex = previousTabModel.index();
-            Tab previousTab = previousTabModel.getTabAt(previousTabIndex);
-
-            ChromeTabUtils.newTabFromMenu(InstrumentationRegistry.getInstrumentation(),
-                    mActivityTestRule.getActivity(), isIncognito, waitForLoading);
-
-            if (url != null) mActivityTestRule.loadUrl(url);
-            if (!waitForLoading) continue;
-
-            TabModel currentTabModel =
-                    mActivityTestRule.getActivity().getTabModelSelector().getCurrentModel();
-            int currentTabIndex = currentTabModel.index();
-
-            boolean fixPendingReadbacks = mActivityTestRule.getActivity()
-                                                  .getTabContentManager()
-                                                  .getPendingReadbacksForTesting()
-                    != 0;
-
-            // When there are pending readbacks due to detached Tabs, try to fix it by switching
-            // back to that tab.
-            if (fixPendingReadbacks && previousTabIndex != TabModel.INVALID_TAB_INDEX) {
-                // clang-format off
-                TestThreadUtils.runOnUiThreadBlocking(() ->
-                        previousTabModel.setIndex(previousTabIndex, TabSelectionType.FROM_USER)
-                );
-                // clang-format on
-            }
-
-            checkThumbnailsExist(previousTab);
-
-            if (fixPendingReadbacks) {
-                // clang-format off
-                TestThreadUtils.runOnUiThreadBlocking(() -> currentTabModel.setIndex(
-                        currentTabIndex, TabSelectionType.FROM_USER)
-                );
-                // clang-format on
-            }
-        }
-
-        ChromeTabUtils.waitForTabPageLoaded(mActivityTestRule.getActivity().getActivityTab(), null,
-                null, WAIT_TIMEOUT_SECONDS * 10);
-
-        assertEquals(numTabs + previousTabCount,
-                mActivityTestRule.getActivity()
-                        .getTabModelSelector()
-                        .getModel(isIncognito)
-                        .getCount());
-
-        if (waitForLoading) {
-            // clang-format off
-            CriteriaHelper.pollUiThread(Criteria.equals(0, () ->
-                mActivityTestRule.getActivity()
-                        .getTabContentManager()
-                        .getPendingReadbacksForTesting()
-            ));
-            // clang-format on
-        }
-    }
-
     private void testTabToGrid(String fromUrl) throws InterruptedException {
         mActivityTestRule.loadUrl(fromUrl);
 
@@ -476,10 +379,12 @@
         if (!isEmulator()) return;
 
         for (int i = 0; i < 10; i++) {
-            mActivityTestRule.loadUrl(mUrl);
             // Quickly create some tabs, navigate to web pages, and don't wait for thumbnail
             // capturing.
-            createTabs(1, mUrl, false, false);
+            mActivityTestRule.loadUrl(mUrl);
+            ChromeTabUtils.newTabFromMenu(InstrumentationRegistry.getInstrumentation(),
+                    mActivityTestRule.getActivity(), false, false);
+            mActivityTestRule.loadUrl(mUrl);
             // Hopefully we are in a state where some pending readbacks are stuck because their tab
             // is not attached to the view.
             if (mActivityTestRule.getActivity()
@@ -563,7 +468,7 @@
                 waitForCaptureRateControl();
             }
             int count = getCaptureCount();
-            onView(withId(org.chromium.chrome.tab_ui.R.id.tab_list_view))
+            onView(withId(R.id.tab_list_view))
                     .perform(RecyclerViewActions.actionOnItemAtPosition(targetIndex, click()));
             CriteriaHelper.pollUiThread(() -> {
                 boolean doneHiding =
@@ -686,16 +591,16 @@
     public void testIncognitoEnterGts() throws InterruptedException {
         prepareTabs(1, 1, null);
         enterGTS();
-        onView(withId(org.chromium.chrome.tab_ui.R.id.tab_list_view))
+        onView(withId(R.id.tab_list_view))
                 .check(TabCountAssertion.havingTabCount(1));
 
-        onView(withId(org.chromium.chrome.tab_ui.R.id.tab_list_view))
+        onView(withId(R.id.tab_list_view))
                 .perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));
         CriteriaHelper.pollInstrumentationThread(
                 () -> !mActivityTestRule.getActivity().getLayoutManager().overviewVisible());
 
         enterGTS();
-        onView(withId(org.chromium.chrome.tab_ui.R.id.tab_list_view))
+        onView(withId(R.id.tab_list_view))
                 .check(TabCountAssertion.havingTabCount(1));
     }
 
@@ -708,16 +613,16 @@
         // Prepare two incognito tabs and enter tab switcher.
         prepareTabs(1, 2, mUrl);
         enterGTS();
-        onView(withId(org.chromium.chrome.tab_ui.R.id.tab_list_view))
+        onView(withId(R.id.tab_list_view))
                 .check(TabCountAssertion.havingTabCount(2));
 
         for (int i = 0; i < mRepeat; i++) {
             switchTabModel(false);
-            onView(withId(org.chromium.chrome.tab_ui.R.id.tab_list_view))
+            onView(withId(R.id.tab_list_view))
                     .check(TabCountAssertion.havingTabCount(1));
 
             switchTabModel(true);
-            onView(withId(org.chromium.chrome.tab_ui.R.id.tab_list_view))
+            onView(withId(R.id.tab_list_view))
                     .check(TabCountAssertion.havingTabCount(2));
         }
         leaveGTS();
@@ -819,7 +724,7 @@
             }
         }
         checkCaptureCount(delta, count);
-        if (checkThumbnail) checkThumbnailsExist(currentTab);
+        if (checkThumbnail) TabUiTestHelper.checkThumbnailsExist(currentTab);
     }
 
     private void leaveGTS() {
@@ -859,18 +764,6 @@
                 Criteria.equals(expectedDelta, () -> getCaptureCount() - initCount));
     }
 
-    private void checkThumbnailsExist(Tab tab) {
-        File etc1File = TabContentManager.getTabThumbnailFileEtc1(tab);
-        CriteriaHelper.pollInstrumentationThread(etc1File::exists,
-                "The thumbnail " + etc1File.getName() + " is not found",
-                DEFAULT_MAX_TIME_TO_POLL * 10, DEFAULT_POLLING_INTERVAL);
-
-        File jpegFile = TabContentManager.getTabThumbnailFileJpeg(tab);
-        CriteriaHelper.pollInstrumentationThread(jpegFile::exists,
-                "The thumbnail " + jpegFile.getName() + " is not found",
-                DEFAULT_MAX_TIME_TO_POLL * 10, DEFAULT_POLLING_INTERVAL);
-    }
-
     private int getCaptureCount() {
         return RecordHistogram.getHistogramTotalCountForTesting("Compositing.CopyFromSurfaceTime");
     }
diff --git a/chrome/android/features/start_surface/internal/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java b/chrome/android/features/start_surface/internal/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java
index 714a414..e2a6ebe5 100644
--- a/chrome/android/features/start_surface/internal/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java
+++ b/chrome/android/features/start_surface/internal/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java
@@ -8,11 +8,13 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_FAKE_SEARCH_BOX_VISIBLE;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_INCOGNITO;
+import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_TAB_CAROUSEL_VISIBLE;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_VOICE_RECOGNITION_BUTTON_VISIBLE;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.MV_TILES_VISIBLE;
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_EXPLORE_SURFACE_VISIBLE;
@@ -34,7 +36,10 @@
 import org.chromium.chrome.browser.ntp.FakeboxDelegate;
 import org.chromium.chrome.browser.omnibox.LocationBarVoiceRecognitionHandler;
 import org.chromium.chrome.browser.omnibox.UrlFocusChangeListener;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tabmodel.EmptyTabModelObserver;
 import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver;
+import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tasks.TasksSurfaceProperties;
 import org.chromium.chrome.browser.tasks.tab_management.TabSwitcher;
@@ -57,14 +62,20 @@
     @Mock
     private TabModelSelector mTabModelSelector;
     @Mock
+    private TabModel mNormalTabModel;
+    @Mock
     private FakeboxDelegate mFakeBoxDelegate;
     @Mock
+    private ExploreSurfaceCoordinator.FeedSurfaceCreator mFeedSurfaceCreator;
+    @Mock
     private NightModeStateProvider mNightModeStateProvider;
     @Mock
     private LocationBarVoiceRecognitionHandler mLocationBarVoiceRecognitionHandler;
     @Captor
     private ArgumentCaptor<EmptyTabModelSelectorObserver> mTabModelSelectorObserverCaptor;
     @Captor
+    private ArgumentCaptor<EmptyTabModelObserver> mTabModelObserverCaptor;
+    @Captor
     private ArgumentCaptor<OverviewModeObserver> mOverviewModeObserverCaptor;
     @Captor
     private ArgumentCaptor<UrlFocusChangeListener> mUrlFocusChangeListenerCaptor;
@@ -78,6 +89,7 @@
                 new ArrayList<>(Arrays.asList(TasksSurfaceProperties.ALL_KEYS));
         allProperties.addAll(Arrays.asList(StartSurfaceProperties.ALL_KEYS));
         mPropertyModel = new PropertyModel(allProperties);
+        doReturn(mNormalTabModel).when(mTabModelSelector).getModel(false);
     }
 
     @After
@@ -154,10 +166,71 @@
     }
 
     // TODO(crbug.com/1020223): Test SurfaceMode.SINGLE_PANE and SurfaceMode.TWO_PANES modes.
+    @Test
+    public void hideTabCarouselWithNoTabs() {
+        doReturn(false).when(mTabModelSelector).isIncognitoSelected();
+        doReturn(mLocationBarVoiceRecognitionHandler)
+                .when(mFakeBoxDelegate)
+                .getLocationBarVoiceRecognitionHandler();
+        doReturn(true).when(mLocationBarVoiceRecognitionHandler).isVoiceSearchEnabled();
+
+        StartSurfaceMediator mediator = createStartSurfaceMediator(SurfaceMode.SINGLE_PANE);
+        verify(mNormalTabModel).addObserver(mTabModelObserverCaptor.capture());
+
+        doReturn(0).when(mNormalTabModel).getCount();
+        mediator.showOverview(false);
+        assertThat(mPropertyModel.get(IS_SHOWING_OVERVIEW), equalTo(true));
+        assertThat(mPropertyModel.get(IS_TAB_CAROUSEL_VISIBLE), equalTo(false));
+    }
+
+    @Test
+    public void hideTabCarouselWhenClosingLastTab() {
+        doReturn(false).when(mTabModelSelector).isIncognitoSelected();
+        doReturn(mLocationBarVoiceRecognitionHandler)
+                .when(mFakeBoxDelegate)
+                .getLocationBarVoiceRecognitionHandler();
+        doReturn(true).when(mLocationBarVoiceRecognitionHandler).isVoiceSearchEnabled();
+
+        StartSurfaceMediator mediator = createStartSurfaceMediator(SurfaceMode.SINGLE_PANE);
+        verify(mNormalTabModel).addObserver(mTabModelObserverCaptor.capture());
+
+        doReturn(1).when(mNormalTabModel).getCount();
+        mediator.showOverview(false);
+        assertThat(mPropertyModel.get(IS_SHOWING_OVERVIEW), equalTo(true));
+        assertThat(mPropertyModel.get(IS_TAB_CAROUSEL_VISIBLE), equalTo(true));
+
+        doReturn(0).when(mNormalTabModel).getCount();
+        mTabModelObserverCaptor.getValue().willCloseTab(mock(Tab.class), false);
+        assertThat(mPropertyModel.get(IS_SHOWING_OVERVIEW), equalTo(true));
+        assertThat(mPropertyModel.get(IS_TAB_CAROUSEL_VISIBLE), equalTo(false));
+    }
+
+    @Test
+    public void reshowTabCarouselWhenTabClosureUndone() {
+        doReturn(false).when(mTabModelSelector).isIncognitoSelected();
+        doReturn(mLocationBarVoiceRecognitionHandler)
+                .when(mFakeBoxDelegate)
+                .getLocationBarVoiceRecognitionHandler();
+        doReturn(true).when(mLocationBarVoiceRecognitionHandler).isVoiceSearchEnabled();
+
+        StartSurfaceMediator mediator = createStartSurfaceMediator(SurfaceMode.SINGLE_PANE);
+        verify(mNormalTabModel).addObserver(mTabModelObserverCaptor.capture());
+
+        doReturn(1).when(mNormalTabModel).getCount();
+        mediator.showOverview(false);
+        mTabModelObserverCaptor.getValue().willCloseTab(mock(Tab.class), false);
+        assertThat(mPropertyModel.get(IS_SHOWING_OVERVIEW), equalTo(true));
+        assertThat(mPropertyModel.get(IS_TAB_CAROUSEL_VISIBLE), equalTo(false));
+
+        mTabModelObserverCaptor.getValue().tabClosureUndone(mock(Tab.class));
+        assertThat(mPropertyModel.get(IS_SHOWING_OVERVIEW), equalTo(true));
+        assertThat(mPropertyModel.get(IS_TAB_CAROUSEL_VISIBLE), equalTo(true));
+    }
 
     private StartSurfaceMediator createStartSurfaceMediator(@SurfaceMode int mode) {
         return new StartSurfaceMediator(mMainTabGridController, mTabModelSelector,
-                mode == SurfaceMode.NO_START_SURFACE ? null : mPropertyModel, null, null, mode,
+                mode == SurfaceMode.NO_START_SURFACE ? null : mPropertyModel,
+                mode == SurfaceMode.SINGLE_PANE ? mFeedSurfaceCreator : null, null, mode,
                 mFakeBoxDelegate, mNightModeStateProvider);
     }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceMediator.java
index 16ca0ab..409941d 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceMediator.java
@@ -7,7 +7,7 @@
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.FAKE_SEARCH_BOX_CLICK_LISTENER;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.FAKE_SEARCH_BOX_TEXT_WATCHER;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_FAKE_SEARCH_BOX_VISIBLE;
-import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_TAB_CAROUSEL;
+import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_TAB_CAROUSEL_VISIBLE;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_VOICE_RECOGNITION_BUTTON_VISIBLE;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.VOICE_SEARCH_BUTTON_CLICK_LISTENER;
 
@@ -36,7 +36,7 @@
         assert mFakeboxDelegate != null;
 
         mModel = model;
-        mModel.set(IS_TAB_CAROUSEL, isTabCarousel);
+        mModel.set(IS_TAB_CAROUSEL_VISIBLE, isTabCarousel);
         mModel.set(FAKE_SEARCH_BOX_CLICK_LISTENER, new View.OnClickListener() {
             @Override
             public void onClick(View v) {
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceProperties.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceProperties.java
index b931fdb..cf541dd 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceProperties.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceProperties.java
@@ -20,7 +20,7 @@
             new PropertyModel.WritableBooleanPropertyKey();
     public static final PropertyModel.WritableBooleanPropertyKey IS_INCOGNITO =
             new PropertyModel.WritableBooleanPropertyKey();
-    public static final PropertyModel.WritableBooleanPropertyKey IS_TAB_CAROUSEL =
+    public static final PropertyModel.WritableBooleanPropertyKey IS_TAB_CAROUSEL_VISIBLE =
             new PropertyModel.WritableBooleanPropertyKey();
     public static final PropertyModel
             .WritableBooleanPropertyKey IS_VOICE_RECOGNITION_BUTTON_VISIBLE =
@@ -39,7 +39,7 @@
             .WritableObjectPropertyKey<View.OnClickListener> VOICE_SEARCH_BUTTON_CLICK_LISTENER =
             new PropertyModel.WritableObjectPropertyKey<View.OnClickListener>();
     public static final PropertyKey[] ALL_KEYS = new PropertyKey[] {IS_FAKE_SEARCH_BOX_VISIBLE,
-            IS_INCOGNITO, IS_TAB_CAROUSEL, IS_VOICE_RECOGNITION_BUTTON_VISIBLE,
+            IS_INCOGNITO, IS_TAB_CAROUSEL_VISIBLE, IS_VOICE_RECOGNITION_BUTTON_VISIBLE,
             FAKE_SEARCH_BOX_CLICK_LISTENER, FAKE_SEARCH_BOX_TEXT_WATCHER, MORE_TABS_CLICK_LISTENER,
             MV_TILES_VISIBLE, VOICE_SEARCH_BUTTON_CLICK_LISTENER};
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksView.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksView.java
index 67095c2..4c884c7 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksView.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksView.java
@@ -59,15 +59,12 @@
     }
 
     /**
-     * Sets whether the tasks view should behave in Carousel mode.
-     * @param isTabCarousel whether the tab switcher is in .CAROUSEL mode
+     * Set the visibility of the tab carousel.
+     * @param isVisible Whether it's visible.
      */
-    void setIsTabCarousel(boolean isTabCarousel) {
-        if (isTabCarousel) {
-            // TODO(crbug.com/982018): Change view according to incognito and dark mode.
-            findViewById(R.id.tab_switcher_title).setVisibility(View.VISIBLE);
-            mCarouselTabSwitcherContainer.setVisibility(View.VISIBLE);
-        }
+    void setTabCarouselVisibility(boolean isVisible) {
+        mCarouselTabSwitcherContainer.setVisibility(isVisible ? View.VISIBLE : View.GONE);
+        findViewById(R.id.tab_switcher_title).setVisibility(isVisible ? View.VISIBLE : View.GONE);
     }
 
     /**
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksViewBinder.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksViewBinder.java
index 3016070..033dd49 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksViewBinder.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksViewBinder.java
@@ -8,7 +8,7 @@
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.FAKE_SEARCH_BOX_TEXT_WATCHER;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_FAKE_SEARCH_BOX_VISIBLE;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_INCOGNITO;
-import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_TAB_CAROUSEL;
+import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_TAB_CAROUSEL_VISIBLE;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_VOICE_RECOGNITION_BUTTON_VISIBLE;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.MORE_TABS_CLICK_LISTENER;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.MV_TILES_VISIBLE;
@@ -30,8 +30,8 @@
             view.setFakeSearchBoxVisibility(model.get(IS_FAKE_SEARCH_BOX_VISIBLE));
         } else if (propertyKey == IS_INCOGNITO) {
             view.setIncognitoMode(model.get(IS_INCOGNITO));
-        } else if (propertyKey == IS_TAB_CAROUSEL) {
-            view.setIsTabCarousel(model.get(IS_TAB_CAROUSEL));
+        } else if (propertyKey == IS_TAB_CAROUSEL_VISIBLE) {
+            view.setTabCarouselVisibility(model.get(IS_TAB_CAROUSEL_VISIBLE));
         } else if (propertyKey == IS_VOICE_RECOGNITION_BUTTON_VISIBLE) {
             view.setVoiceRecognitionButtonVisibility(
                     model.get(IS_VOICE_RECOGNITION_BUTTON_VISIBLE));
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
index 1e83aae8..c67f1dd 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
@@ -129,6 +129,13 @@
             }, TabGridViewBinder::bindClosableTab);
 
             recyclerListener = (holder) -> {
+                int holderItemViewType = holder.getItemViewType();
+
+                if (holderItemViewType != UiType.CLOSABLE
+                        && holderItemViewType != UiType.SELECTABLE) {
+                    return;
+                }
+
                 ViewLookupCachingFrameLayout root = (ViewLookupCachingFrameLayout) holder.itemView;
                 ImageView thumbnail = (ImageView) root.fastFindViewById(R.id.tab_thumbnail);
                 if (thumbnail == null) return;
@@ -305,4 +312,13 @@
             PropertyModelChangeProcessor.ViewBinder<PropertyModel, T, PropertyKey> binder) {
         mAdapter.registerType(typeId, builder, binder);
     }
+
+    /**
+     * Inserts a special {@link org.chromium.ui.modelutil.MVCListAdapter.ListItem} at given index of
+     * the model list.
+     * @see TabListMediator#addSpecialItemToModel(int, int, PropertyModel).
+     */
+    void addSpecialListItem(int index, @UiType int uiType, PropertyModel model) {
+        mMediator.addSpecialItemToModel(index, uiType, model);
+    }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
index 23082cd..4b2aaed 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
@@ -1155,4 +1155,17 @@
     void setTabRestoreCompletedForTesting(boolean isRestored) {
         mTabRestoreCompleted = isRestored;
     }
+
+    /**
+     * Inserts a special {@link org.chromium.ui.modelutil.MVCListAdapter.ListItem} at given index of
+     * the current {@link TabListModel}.
+     *
+     * @param index The index of the {@link org.chromium.ui.modelutil.MVCListAdapter.ListItem} to be
+     *              inserted.
+     * @param uiType The view type the model will bind to.
+     * @param model The model that will be bound to a view.
+     */
+    void addSpecialItemToModel(int index, @UiType int uiType, PropertyModel model) {
+        mModel.add(index, new SimpleRecyclerViewAdapter.ListItem(uiType, model));
+    }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabProperties.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabProperties.java
index 9e15da9f..921b9b7 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabProperties.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabProperties.java
@@ -23,13 +23,14 @@
  */
 public class TabProperties {
     /** IDs for possible types of UI in the tab list. */
-    @IntDef({UiType.SELECTABLE, UiType.CLOSABLE, UiType.STRIP, UiType.MESSAGE})
+    @IntDef({UiType.SELECTABLE, UiType.CLOSABLE, UiType.STRIP, UiType.MESSAGE, UiType.DIVIDER})
     @Retention(RetentionPolicy.SOURCE)
     public @interface UiType {
         int SELECTABLE = 0;
         int CLOSABLE = 1;
         int STRIP = 2;
         int MESSAGE = 3;
+        int DIVIDER = 4;
     }
 
     public static final PropertyModel.WritableIntPropertyKey TAB_ID =
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorCoordinator.java
index 22ade65..964e68e0 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorCoordinator.java
@@ -5,6 +5,8 @@
 package org.chromium.chrome.browser.tasks.tab_management;
 
 import android.content.Context;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
 import android.view.View;
 
@@ -38,6 +40,14 @@
         void show(List<Tab> tabs);
 
         /**
+         * Shows the TabSelectionEditor with the given {@Link Tab}s, and the first
+         * {@code preSelectedTabCount} tabs being selected.
+         * @param tabs List of {@link Tab}s to show.
+         * @param preSelectedTabCount Number of selected {@link Tab}s.
+         */
+        void show(List<Tab> tabs, int preSelectedTabCount);
+
+        /**
          * Hides the TabSelectionEditor.
          */
         void hide();
@@ -79,10 +89,32 @@
         mContext = context;
         mParentView = parentView;
         mTabModelSelector = tabModelSelector;
+
         mTabListCoordinator = new TabListCoordinator(TabListCoordinator.TabListMode.GRID, context,
                 mTabModelSelector, tabContentManager::getTabThumbnailWithCallback, null, false,
                 null, null, null, TabProperties.UiType.SELECTABLE, this::getSelectionDelegate, null,
                 null, false, COMPONENT_NAME);
+        mTabListCoordinator.registerItemType(TabProperties.UiType.DIVIDER, () -> {
+            return LayoutInflater.from(context).inflate(R.layout.divider_preference, null, false);
+        }, (model, view, propertyKey) -> {});
+        RecyclerView.LayoutManager layoutManager =
+                mTabListCoordinator.getContainerView().getLayoutManager();
+        if (layoutManager instanceof GridLayoutManager) {
+            ((GridLayoutManager) layoutManager)
+                    .setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
+                        @Override
+                        public int getSpanSize(int i) {
+                            int itemType = mTabListCoordinator.getContainerView()
+                                                   .getAdapter()
+                                                   .getItemViewType(i);
+
+                            if (itemType == TabProperties.UiType.DIVIDER) {
+                                return ((GridLayoutManager) layoutManager).getSpanCount();
+                            }
+                            return 1;
+                        }
+                    });
+        }
 
         mTabSelectionEditorLayout = LayoutInflater.from(context)
                 .inflate(R.layout.tab_selection_editor_layout, null)
@@ -108,9 +140,16 @@
     /**
      * Resets {@link TabListCoordinator} with the provided list.
      * @param tabs List of {@link Tab}s to reset.
+     * @param preSelectedCount First {@code preSelectedCount} {@code tabs} are pre-selected.
      */
-    void resetWithListOfTabs(@Nullable List<Tab> tabs) {
+    void resetWithListOfTabs(@Nullable List<Tab> tabs, int preSelectedCount) {
         mTabListCoordinator.resetWithListOfTabs(tabs);
+
+        if (tabs != null && preSelectedCount > 0) {
+            assert preSelectedCount < tabs.size();
+            mTabListCoordinator.addSpecialListItem(
+                    preSelectedCount, TabProperties.UiType.DIVIDER, new PropertyModel());
+        }
     }
 
     /**
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorMediator.java
index 58ee797..3abfada 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorMediator.java
@@ -29,7 +29,9 @@
 import org.chromium.ui.modelutil.PropertyModel;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * This class is the mediator that contains all business logic for TabSelectionEditor component. It
@@ -45,8 +47,9 @@
         /**
          * Handles the reset event.
          * @param tabs List of {@link Tab}s to reset.
+         * @param preSelectedCount First {@code preSelectedCount} {@code tabs} are pre-selected.
          */
-        void resetWithListOfTabs(@Nullable List<Tab> tabs);
+        void resetWithListOfTabs(@Nullable List<Tab> tabs, int preSelectedCount);
     }
 
     /**
@@ -181,8 +184,27 @@
      */
     @Override
     public void show(List<Tab> tabs) {
-        mResetHandler.resetWithListOfTabs(tabs);
+        show(tabs, 0);
+    }
+
+    @Override
+    public void show(List<Tab> tabs, int preSelectedTabCount) {
         mSelectionDelegate.setSelectionModeEnabledForZeroItems(true);
+
+        if (preSelectedTabCount > 0) {
+            assert preSelectedTabCount <= tabs.size();
+
+            Set<Integer> preSelectedTabIds = new HashSet<>();
+
+            for (int i = 0; i < preSelectedTabCount; i++) {
+                preSelectedTabIds.add(tabs.get(i).getId());
+            }
+
+            mSelectionDelegate.setSelectedItems(preSelectedTabIds);
+        }
+
+        mResetHandler.resetWithListOfTabs(tabs, preSelectedTabCount);
+
         if (mPositionProvider != null) {
             mModel.set(TabSelectionEditorProperties.SELECTION_EDITOR_POSITION_RECT,
                     mPositionProvider.getSelectionEditorPositionRect());
@@ -221,7 +243,7 @@
 
     @Override
     public void hide() {
-        mResetHandler.resetWithListOfTabs(null);
+        mResetHandler.resetWithListOfTabs(null, 0);
         mModel.set(TabSelectionEditorProperties.IS_VISIBLE, false);
     }
 
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/RecyclerViewMatcherUtils.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/RecyclerViewMatcherUtils.java
index fd9d81b..5d24075e 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/RecyclerViewMatcherUtils.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/RecyclerViewMatcherUtils.java
@@ -10,6 +10,7 @@
 
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
 
 /**
  * Contains useful RecyclerViewMatcher.
@@ -66,4 +67,75 @@
             }
         };
     }
+
+    /**
+     * This matcher matches RecyclerView that has a ViewHolder that matches the given view holder
+     * matcher at the given adapter position.
+     *
+     * @param position The adapter position.
+     * @param viewHolderMatcher A view holder to match.
+     * @return A matcher that matches view at adapter position and matches the given viewHolder
+     *         matcher.
+     */
+    public static Matcher<View> atPositionWithViewHolder(
+            int position, Matcher<RecyclerView.ViewHolder> viewHolderMatcher) {
+        return new BoundedMatcher<View, RecyclerView>(RecyclerView.class) {
+            @Override
+            protected boolean matchesSafely(RecyclerView recyclerView) {
+                recyclerView.scrollToPosition(position);
+                RecyclerView.ViewHolder viewHolder =
+                        recyclerView.findViewHolderForAdapterPosition(position);
+
+                if (viewHolder == null) return false;
+
+                return viewHolderMatcher.matches(viewHolder);
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText(viewHolderMatcher + " at position " + position);
+            }
+        };
+    }
+
+    /**
+     * This matcher matches the ViewHolder that has an adapter position equals to the given
+     * position.
+     *
+     * @param position The position to match.
+     * @return A matcher that matches the viewHolder at the given position.
+     */
+    public static Matcher<RecyclerView.ViewHolder> withViewHolderAtPosition(int position) {
+        return new TypeSafeMatcher<RecyclerView.ViewHolder>() {
+            @Override
+            protected boolean matchesSafely(RecyclerView.ViewHolder viewHolder) {
+                return viewHolder.getAdapterPosition() == position;
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("ViewHolder with adapter position: " + position);
+            }
+        };
+    }
+
+    /**
+     * This matcher matches a ViewHolder that has the given item type.
+     *
+     * @param itemType The item type to match.
+     * @return A matcher that matches a ViewHolder with the given item type.
+     */
+    public static Matcher<RecyclerView.ViewHolder> withItemType(int itemType) {
+        return new TypeSafeMatcher<RecyclerView.ViewHolder>() {
+            @Override
+            protected boolean matchesSafely(RecyclerView.ViewHolder viewHolder) {
+                return viewHolder.getItemViewType() == itemType;
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("ViewHolder with item type: " + itemType);
+            }
+        };
+    }
 }
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorTest.java
index 1565ff4..0964ab50 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorTest.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.tasks.tab_management;
 
 import android.os.Build;
+import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
 
 import org.junit.Before;
@@ -24,6 +25,7 @@
 import org.chromium.chrome.tab_ui.R;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.chrome.test.util.browser.Features;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.test.util.UiRestriction;
@@ -183,6 +185,58 @@
         mRobot.resultRobot.verifyToolbarActionButtonDisabled();
     }
 
+    @Test
+    @MediumTest
+    public void testShowTabsWithPreSelectedTabs() {
+        List<Tab> tabs = getTabsInCurrentTabModel();
+        int preSelectedTabCount = 1;
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mTabSelectionEditorController.show(tabs, preSelectedTabCount); });
+
+        mRobot.resultRobot.verifyTabSelectionEditorIsVisible()
+                .verifyToolbarActionButtonDisabled()
+                .verifyToolbarActionButtonWithResourceId(R.string.tab_selection_editor_group)
+                .verifyToolbarSelectionText("1 selected")
+                .verifyHasAtLeastNItemVisible(tabs.size() + 1)
+                .verifyItemSelectedAtAdapterPosition(0)
+                .verifyHasItemViewTypeAtAdapterPosition(1, TabProperties.UiType.DIVIDER)
+                .verifyDividerAlwaysStartsAtTheEdgeOfScreen();
+    }
+
+    @Test
+    @MediumTest
+    public void testShowTabsWithPreSelectedTabs_10Tabs() {
+        int preSelectedTabCount = 10;
+        int additionalTabCount =
+                preSelectedTabCount + 1 - mTabModelSelector.getCurrentModel().getCount();
+
+        for (int i = 0; i < additionalTabCount; i++) {
+            ChromeTabUtils.newTabFromMenu(InstrumentationRegistry.getInstrumentation(),
+                    mActivityTestRule.getActivity(), mTabModelSelector.isIncognitoSelected(), true);
+        }
+
+        List<Tab> tabs = getTabsInCurrentTabModel();
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mTabSelectionEditorController.show(tabs, preSelectedTabCount); });
+
+        mRobot.resultRobot.verifyToolbarSelectionText("10 selected")
+                .verifyHasItemViewTypeAtAdapterPosition(
+                        preSelectedTabCount, TabProperties.UiType.DIVIDER)
+                .verifyDividerAlwaysStartsAtTheEdgeOfScreenAtPosition(preSelectedTabCount);
+    }
+
+    @Test
+    @MediumTest
+    public void testDividerIsNotClickable() {
+        List<Tab> tabs = getTabsInCurrentTabModel();
+        int preSelectedTabCount = 1;
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mTabSelectionEditorController.show(tabs, preSelectedTabCount); });
+
+        mRobot.resultRobot.verifyDividerNotClickableNotFocusable();
+    }
+
     private List<Tab> getTabsInCurrentTabModel() {
         List<Tab> tabs = new ArrayList<>();
 
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorTestingRobot.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorTestingRobot.java
index dc501bfd3..821ba65 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorTestingRobot.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorTestingRobot.java
@@ -8,9 +8,12 @@
 import static android.support.test.espresso.action.ViewActions.click;
 import static android.support.test.espresso.assertion.ViewAssertions.matches;
 import static android.support.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition;
+import static android.support.test.espresso.contrib.RecyclerViewActions.scrollToPosition;
 import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
+import static android.support.test.espresso.matcher.ViewMatchers.isClickable;
 import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static android.support.test.espresso.matcher.ViewMatchers.isEnabled;
+import static android.support.test.espresso.matcher.ViewMatchers.isFocusable;
 import static android.support.test.espresso.matcher.ViewMatchers.withClassName;
 import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription;
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
@@ -21,6 +24,10 @@
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.not;
 
+import static org.chromium.chrome.browser.tasks.tab_management.RecyclerViewMatcherUtils.atPosition;
+import static org.chromium.chrome.browser.tasks.tab_management.RecyclerViewMatcherUtils.atPositionWithViewHolder;
+import static org.chromium.chrome.browser.tasks.tab_management.RecyclerViewMatcherUtils.withItemType;
+
 import android.os.Build;
 import android.support.test.espresso.NoMatchingRootException;
 import android.support.test.espresso.Root;
@@ -41,7 +48,7 @@
     /**
      * @return A root matcher that matches the TabSelectionEditor popup decor view.
      */
-    private static Matcher<Root> isTabSelectionEditorPopup() {
+    public static Matcher<Root> isTabSelectionEditorPopup() {
         return new TypeSafeMatcher<Root>() {
             @Override
             public void describeTo(Description description) {
@@ -66,7 +73,7 @@
     /**
      * @return A view matcher that matches the item is selected.
      */
-    private static Matcher<View> itemIsSelected() {
+    public static Matcher<View> itemIsSelected() {
         return new BoundedMatcher<View, SelectableTabGridView>(SelectableTabGridView.class) {
             private SelectableTabGridView mSelectableTabGridView;
             @Override
@@ -105,6 +112,23 @@
         };
     }
 
+    /**
+     * @return A view matcher that matches a divider view.
+     */
+    public static Matcher<View> isDivider() {
+        return new TypeSafeMatcher<View>() {
+            @Override
+            protected boolean matchesSafely(View view) {
+                return view.getId() == org.chromium.chrome.tab_ui.R.id.divider_view;
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("is divider");
+            }
+        };
+    }
+
     public final TabSelectionEditorTestingRobot.Result resultRobot;
     public final TabSelectionEditorTestingRobot.Action actionRobot;
 
@@ -252,5 +276,67 @@
             onView(withText(text)).check(matches(isDisplayed()));
             return this;
         }
+
+        Result verifyDividerAlwaysStartsAtTheEdgeOfScreen() {
+            onView(allOf(isDivider(),
+                           withParent(withId(org.chromium.chrome.tab_ui.R.id.tab_list_view))))
+                    .inRoot(isTabSelectionEditorPopup())
+                    .check(matches(isDisplayed()))
+                    .check((v, noMatchException) -> {
+                        if (noMatchException != null) throw noMatchException;
+
+                        View parentView = (View) v.getParent();
+                        Assert.assertEquals(parentView.getPaddingStart(), (int) v.getX());
+                    });
+            return this;
+        }
+
+        Result verifyDividerAlwaysStartsAtTheEdgeOfScreenAtPosition(int position) {
+            onView(withId(org.chromium.chrome.tab_ui.R.id.tab_list_view))
+                    .inRoot(isTabSelectionEditorPopup())
+                    .perform(scrollToPosition(position));
+
+            onView(atPosition(position, isDivider()))
+                    .inRoot(isTabSelectionEditorPopup())
+                    .check(matches(isDisplayed()))
+                    .check((v, noMatchException) -> {
+                        if (noMatchException != null) throw noMatchException;
+
+                        View parentView = (View) v.getParent();
+                        Assert.assertEquals(parentView.getPaddingStart(), (int) v.getX());
+                    });
+
+            return this;
+        }
+
+        Result verifyDividerNotClickableNotFocusable() {
+            onView(allOf(isDivider(),
+                           withParent(withId(org.chromium.chrome.tab_ui.R.id.tab_list_view))))
+                    .inRoot(isTabSelectionEditorPopup())
+                    .check(matches(not(isClickable())))
+                    .check(matches(not(isFocusable())));
+            return this;
+        }
+
+        /**
+         * Verifies the TabSelectionEditor has an ItemView at given position that matches the given
+         * targetItemViewType.
+         *
+         * First this method scrolls to the given adapter position to make sure ViewHolder for the
+         * given position is visible.
+         *
+         * @param position Adapter position.
+         * @param targetItemViewType The item view type to be matched.
+         * @return {@link Result} to do chain verification.
+         */
+        Result verifyHasItemViewTypeAtAdapterPosition(int position, int targetItemViewType) {
+            onView(withId(org.chromium.chrome.tab_ui.R.id.tab_list_view))
+                    .inRoot(isTabSelectionEditorPopup())
+                    .perform(scrollToPosition(position));
+            onView(atPositionWithViewHolder(position, withItemType(targetItemViewType)))
+                    .inRoot(isTabSelectionEditorPopup())
+                    .check(matches(isDisplayed()));
+            return this;
+        }
     }
 }
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabUiTestHelper.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabUiTestHelper.java
index cf8a3254..1a77451f 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabUiTestHelper.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabUiTestHelper.java
@@ -14,10 +14,15 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import static org.chromium.base.test.util.CallbackHelper.WAIT_TIMEOUT_SECONDS;
+import static org.chromium.content_public.browser.test.util.CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL;
+import static org.chromium.content_public.browser.test.util.CriteriaHelper.DEFAULT_POLLING_INTERVAL;
+
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.os.Build;
 import android.provider.Settings;
+import android.support.annotation.Nullable;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.espresso.NoMatchingViewException;
 import android.support.test.espresso.ViewAssertion;
@@ -27,15 +32,20 @@
 
 import org.chromium.base.ContextUtils;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
+import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tabmodel.TabModel;
+import org.chromium.chrome.browser.tabmodel.TabSelectionType;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
 import org.chromium.chrome.tab_ui.R;
+import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.chrome.test.util.OverviewModeBehaviorWatcher;
 import org.chromium.content_public.browser.test.util.Criteria;
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
+import java.io.File;
 import java.util.List;
 
 /**
@@ -194,6 +204,121 @@
     }
 
     /**
+     * Make Chrome have {@code numTabs} of regular Tabs and {@code numIncognitoTabs} of incognito
+     * tabs with {@code url} loaded.
+     * @param rule The {@link ChromeTabbedActivityTestRule}.
+     * @param numTabs The number of regular tabs.
+     * @param numIncognitoTabs The number of incognito tabs.
+     * @param url The URL to load.
+     */
+    public static void prepareTabsWithThumbnail(ChromeTabbedActivityTestRule rule, int numTabs,
+            int numIncognitoTabs, @Nullable String url) {
+        assertTrue(numTabs >= 1);
+        assertTrue(numIncognitoTabs >= 0);
+
+        assertEquals(1, rule.getActivity().getTabModelSelector().getModel(false).getCount());
+        assertEquals(0, rule.getActivity().getTabModelSelector().getModel(true).getCount());
+
+        if (url != null) rule.loadUrl(url);
+        if (numTabs > 1) {
+            // When Chrome started, there is already one Tab created by default.
+            createTabsWithThumbnail(rule, numTabs - 1, url, false);
+        }
+        if (numIncognitoTabs > 0) createTabsWithThumbnail(rule, numIncognitoTabs, url, true);
+
+        assertEquals(numTabs, rule.getActivity().getTabModelSelector().getModel(false).getCount());
+        assertEquals(numIncognitoTabs,
+                rule.getActivity().getTabModelSelector().getModel(true).getCount());
+        if (url != null) {
+            verifyAllTabsHaveUrl(rule.getActivity().getTabModelSelector().getModel(false), url);
+            verifyAllTabsHaveUrl(rule.getActivity().getTabModelSelector().getModel(true), url);
+        }
+    }
+
+    private static void verifyAllTabsHaveUrl(TabModel tabModel, String url) {
+        for (int i = 0; i < tabModel.getCount(); i++) {
+            assertEquals(url, tabModel.getTabAt(i).getUrl());
+        }
+    }
+
+    /**
+     * Create {@code numTabs} of {@link Tab}s with {@code url} loaded to Chrome.
+     * Note that if the test doesn't care about thumbnail, use {@link TabUiTestHelper#createTabs}
+     * instead since it's faster.
+     *
+     * @param rule The {@link ChromeTabbedActivityTestRule}.
+     * @param numTabs The number of tabs to create.
+     * @param url The URL to load. Skip loading when null, but the thumbnail for the NTP might not
+     *            be saved.
+     * @param isIncognito Whether the tab is incognito tab.
+     */
+    private static void createTabsWithThumbnail(ChromeTabbedActivityTestRule rule, int numTabs,
+            @Nullable String url, boolean isIncognito) {
+        assertTrue(numTabs >= 1);
+
+        int previousTabCount =
+                rule.getActivity().getTabModelSelector().getModel(isIncognito).getCount();
+
+        for (int i = 0; i < numTabs; i++) {
+            TabModel previousTabModel = rule.getActivity().getTabModelSelector().getCurrentModel();
+            int previousTabIndex = previousTabModel.index();
+            Tab previousTab = previousTabModel.getTabAt(previousTabIndex);
+
+            ChromeTabUtils.newTabFromMenu(InstrumentationRegistry.getInstrumentation(),
+                    rule.getActivity(), isIncognito, true);
+
+            if (url != null) rule.loadUrl(url);
+
+            TabModel currentTabModel = rule.getActivity().getTabModelSelector().getCurrentModel();
+            int currentTabIndex = currentTabModel.index();
+
+            boolean fixPendingReadbacks =
+                    rule.getActivity().getTabContentManager().getPendingReadbacksForTesting() != 0;
+
+            // When there are pending readbacks due to detached Tabs, try to fix it by switching
+            // back to that tab.
+            if (fixPendingReadbacks && previousTabIndex != TabModel.INVALID_TAB_INDEX) {
+                // clang-format off
+                TestThreadUtils.runOnUiThreadBlocking(() ->
+                        previousTabModel.setIndex(previousTabIndex, TabSelectionType.FROM_USER)
+                );
+                // clang-format on
+            }
+
+            checkThumbnailsExist(previousTab);
+
+            if (fixPendingReadbacks) {
+                // clang-format off
+                TestThreadUtils.runOnUiThreadBlocking(() -> currentTabModel.setIndex(
+                        currentTabIndex, TabSelectionType.FROM_USER)
+                );
+                // clang-format on
+            }
+        }
+
+        ChromeTabUtils.waitForTabPageLoaded(
+                rule.getActivity().getActivityTab(), null, null, WAIT_TIMEOUT_SECONDS * 10);
+
+        assertEquals(numTabs + previousTabCount,
+                rule.getActivity().getTabModelSelector().getModel(isIncognito).getCount());
+
+        CriteriaHelper.pollUiThread(Criteria.equals(0,
+                () -> rule.getActivity().getTabContentManager().getPendingReadbacksForTesting()));
+    }
+
+    public static void checkThumbnailsExist(Tab tab) {
+        File etc1File = TabContentManager.getTabThumbnailFileEtc1(tab);
+        CriteriaHelper.pollInstrumentationThread(etc1File::exists,
+                "The thumbnail " + etc1File.getName() + " is not found",
+                DEFAULT_MAX_TIME_TO_POLL * 10, DEFAULT_POLLING_INTERVAL);
+
+        File jpegFile = TabContentManager.getTabThumbnailFileJpeg(tab);
+        CriteriaHelper.pollInstrumentationThread(jpegFile::exists,
+                "The thumbnail " + jpegFile.getName() + " is not found",
+                DEFAULT_MAX_TIME_TO_POLL * 10, DEFAULT_POLLING_INTERVAL);
+    }
+
+    /**
      * Implementation of {@link ViewAssertion} to verify the {@link RecyclerView} has correct number
      * of children, and children are showing correctly.
      */
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/TasksSurfaceMediatorUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/TasksSurfaceMediatorUnitTest.java
index a677391..48994053 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/TasksSurfaceMediatorUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/TasksSurfaceMediatorUnitTest.java
@@ -15,7 +15,7 @@
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.FAKE_SEARCH_BOX_CLICK_LISTENER;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.FAKE_SEARCH_BOX_TEXT_WATCHER;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_FAKE_SEARCH_BOX_VISIBLE;
-import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_TAB_CAROUSEL;
+import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_TAB_CAROUSEL_VISIBLE;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_VOICE_RECOGNITION_BUTTON_VISIBLE;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.VOICE_SEARCH_BUTTON_CLICK_LISTENER;
 
@@ -75,7 +75,7 @@
 
     @Test
     public void initialization() {
-        verify(mPropertyModel).set(eq(IS_TAB_CAROUSEL), eq(true));
+        verify(mPropertyModel).set(eq(IS_TAB_CAROUSEL_VISIBLE), eq(true));
         verify(mPropertyModel)
                 .set(eq(FAKE_SEARCH_BOX_CLICK_LISTENER), mFakeboxClickListenerCaptor.capture());
         verify(mPropertyModel)
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
index b43dd77..97f72375 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
@@ -7,9 +7,12 @@
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -1355,6 +1358,30 @@
         verify(mRemoveEditor).apply();
     }
 
+    @Test
+    public void addSpecialItem() {
+        mMediator.addSpecialItemToModel(0, TabProperties.UiType.DIVIDER, new PropertyModel());
+
+        assertTrue(mModel.size() > 0);
+        assertEquals(TabProperties.UiType.DIVIDER, mModel.get(0).type);
+    }
+
+    @Test
+    public void addSpecialItem_notPersistOnReset() {
+        mMediator.addSpecialItemToModel(0, TabProperties.UiType.DIVIDER, new PropertyModel());
+        assertEquals(TabProperties.UiType.DIVIDER, mModel.get(0).type);
+
+        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        mMediator.resetWithListOfTabs(tabs, false, false);
+        assertThat(mModel.size(), equalTo(2));
+        assertNotEquals(TabProperties.UiType.DIVIDER, mModel.get(0).type);
+        assertNotEquals(TabProperties.UiType.DIVIDER, mModel.get(1).type);
+
+        mMediator.addSpecialItemToModel(1, TabProperties.UiType.DIVIDER, new PropertyModel());
+        assertThat(mModel.size(), equalTo(3));
+        assertEquals(TabProperties.UiType.DIVIDER, mModel.get(1).type);
+    }
+
     private void initAndAssertAllProperties() {
         List<Tab> tabs = new ArrayList<>();
         for (int i = 0; i < mTabModel.getCount(); i++) {
diff --git a/chrome/android/java/res/layout/divider_preference.xml b/chrome/android/java/res/layout/divider_preference.xml
index 7035011..571811c 100644
--- a/chrome/android/java/res/layout/divider_preference.xml
+++ b/chrome/android/java/res/layout/divider_preference.xml
@@ -3,7 +3,8 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<View 
+<View
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/divider_view"
     style="@style/HorizontalDivider"
     android:importantForAccessibility="no" />
diff --git a/chrome/android/java/res/layout/ephemeral_tab_toolbar.xml b/chrome/android/java/res/layout/ephemeral_tab_toolbar.xml
index 9229373..29eb4724 100644
--- a/chrome/android/java/res/layout/ephemeral_tab_toolbar.xml
+++ b/chrome/android/java/res/layout/ephemeral_tab_toolbar.xml
@@ -64,6 +64,7 @@
                 android:layout_marginTop="16dp"
                 android:layout_toStartOf="@id/open_in_new_tab"
                 android:layout_toEndOf="@id/favicon"
+                android:textAlignment="viewStart"
                 android:ellipsize="end"
                 android:singleLine="true"
                 android:textAppearance="@style/TextAppearance.BlackBodyDefault" />
@@ -85,6 +86,7 @@
                 android:layout_toEndOf="@id/security_icon"
                 android:layout_below="@id/ephemeral_tab_text"
                 android:layout_marginLeft="4dp"
+                android:textAlignment="viewStart"
                 android:ellipsize="start"
                 android:singleLine="true"
                 android:textAppearance="@style/TextAppearance.BlackHint2" />
diff --git a/chrome/android/java/res_download/layout/download_storage_summary.xml b/chrome/android/java/res_download/layout/download_storage_summary.xml
index 73d0cf7..4d3d415c 100644
--- a/chrome/android/java/res_download/layout/download_storage_summary.xml
+++ b/chrome/android/java/res_download/layout/download_storage_summary.xml
@@ -5,13 +5,13 @@
 
 <TextView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/download_storage_summary"
     android:paddingStart="@dimen/list_item_default_margin"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:singleLine="true"
     android:textAppearance="@style/TextAppearance.BlackDisabledText2"
+    android:textAlignment="viewStart"
     android:maxLines="1"
     android:paddingTop="8dp"
     android:paddingBottom="8dp" />
\ No newline at end of file
diff --git a/chrome/android/java/res_download/values-v17/styles.xml b/chrome/android/java/res_download/values-v17/styles.xml
index fb40daf6..cc965b9 100644
--- a/chrome/android/java/res_download/values-v17/styles.xml
+++ b/chrome/android/java/res_download/values-v17/styles.xml
@@ -9,6 +9,7 @@
     <style name="DownloadItemText">
         <item name="android:layout_width">0dp</item>
         <item name="android:layout_height">wrap_content</item>
+        <item name="android:textAlignment">viewStart</item>
         <item name="android:maxLines">1</item>
         <item name="android:ellipsize">end</item>
         <item name="android:singleLine">true</item>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/SingleTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/SingleTabActivity.java
index 731c0f1..a9ea4ca 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/SingleTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/SingleTabActivity.java
@@ -7,10 +7,6 @@
 import android.os.Bundle;
 import android.util.Pair;
 
-import org.chromium.base.ActivityState;
-import org.chromium.base.ApplicationStatus;
-import org.chromium.base.CommandLine;
-import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.dependency_injection.ChromeActivityComponent;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabBuilder;
@@ -21,7 +17,6 @@
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabSelectionType;
 import org.chromium.chrome.browser.tabmodel.document.TabDelegate;
-import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 /**
  * Base class for task-focused activities that need to display a single tab.
@@ -32,8 +27,6 @@
  */
 public abstract class SingleTabActivity<C extends ChromeActivityComponent>
         extends ChromeActivity<C> {
-    private static final int PREWARM_RENDERER_DELAY_MS = 500;
-
     protected static final String BUNDLE_TAB_ID = "tabId";
 
     @Override
@@ -136,31 +129,4 @@
 
     @Override
     public void onUpdateStateChanged() {}
-
-    @Override
-    public void onStopWithNative() {
-        super.onStopWithNative();
-        if (CommandLine.getInstance().hasSwitch(ChromeSwitches.AGGRESSIVELY_PREWARM_RENDERERS)) {
-            PostTask.postDelayedTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
-                @Override
-                public void run() {
-                    // If we're not still stopped, we don't need the spare WebContents.
-                    if (ApplicationStatus.getStateForActivity(SingleTabActivity.this)
-                            == ActivityState.STOPPED) {
-                        WarmupManager.getInstance().createSpareWebContents(!WarmupManager.FOR_CCT);
-                    }
-                }
-            }, PREWARM_RENDERER_DELAY_MS);
-        }
-    }
-
-    @Override
-    public void onTrimMemory(int level) {
-        super.onTrimMemory(level);
-        if (CommandLine.getInstance().hasSwitch(ChromeSwitches.AGGRESSIVELY_PREWARM_RENDERERS)) {
-            if (ChromeApplication.isSevereMemorySignal(level)) {
-                WarmupManager.getInstance().destroySpareWebContents();
-            }
-        }
-    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/NavigationSheetCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/NavigationSheetCoordinator.java
index 27f11d1b..6574914 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/NavigationSheetCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/NavigationSheetCoordinator.java
@@ -195,6 +195,8 @@
 
     @Override
     public boolean startAndExpand(boolean forward, boolean animate) {
+        // Called from activity for navigation popup. No need to check
+        // bottom sheet controller since it is guaranteed to available.
         start(forward, /* showCloseIndicator= */ false);
         mOpenedAsPopup = true;
 
@@ -242,6 +244,7 @@
 
     @Override
     public void close(boolean animate) {
+        if (mBottomSheetController.get() == null) return;
         if (!isHidden()) mBottomSheetController.get().hideContent(this, animate);
         mBottomSheetController.get().removeObserver(mSheetObserver);
         mMediator.clear();
@@ -309,7 +312,9 @@
 
     @Override
     public int getPeekHeight() {
-        if (mOpenedAsPopup) return BottomSheetContent.HeightMode.DISABLED;
+        if (mBottomSheetController.get() == null || mOpenedAsPopup) {
+            return BottomSheetContent.HeightMode.DISABLED;
+        }
         // Makes peek state as 'not present' when bottom sheet is in expanded state (i.e. animating
         // from expanded to close state). It avoids the sheet animating in two distinct steps, which
         // looks awkward.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
index be1989f..5e09720 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
@@ -1396,7 +1396,7 @@
         }
 
         if (details.modifiers != null) {
-            if (details.modifiers.length == 0) mModifiers.clear();
+            if (details.modifiers.length == 0 && mModifiers != null) mModifiers.clear();
 
             for (int i = 0; i < details.modifiers.length; i++) {
                 PaymentDetailsModifier modifier = details.modifiers[i];
diff --git a/chrome/android/java_templates/ChromeSwitches.java.tmpl b/chrome/android/java_templates/ChromeSwitches.java.tmpl
index 1c32232..a1e48a0 100644
--- a/chrome/android/java_templates/ChromeSwitches.java.tmpl
+++ b/chrome/android/java_templates/ChromeSwitches.java.tmpl
@@ -78,12 +78,6 @@
      */
     public static final String DISABLE_TAB_MERGING_FOR_TESTING = "disable-tab-merging";
 
-    /**
-     * Aggressively pre-warms renderers when Chrome is backgrounded. Currently only works for
-     * SingleTabActivity-derived Activities.
-     */
-    public static final String AGGRESSIVELY_PREWARM_RENDERERS = "aggressively-prewarm-renderers";
-
     ///////////////////////////////////////////////////////////////////////////////////////////////
     // Native Switches
     ///////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/android/vr/arcore_device/arcore_device.cc b/chrome/browser/android/vr/arcore_device/arcore_device.cc
index 875a4c2..e4b222d 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_device.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_device.cc
@@ -38,13 +38,7 @@
                                             const gfx::Size& frame_size) {
   mojom::VRDisplayInfoPtr device = mojom::VRDisplayInfo::New();
   device->id = device_id;
-  device->display_name = "ARCore VR Device";
   device->webxr_default_framebuffer_scale = 1.0;
-  device->capabilities = mojom::VRDisplayCapabilities::New();
-  device->capabilities->has_position = true;
-  device->capabilities->has_external_display = false;
-  device->capabilities->can_present = false;
-  device->capabilities->can_provide_environment_integration = true;
   device->left_eye = mojom::VREyeParameters::New();
   device->right_eye = nullptr;
   mojom::VREyeParametersPtr& left_eye = device->left_eye;
diff --git a/chrome/browser/apps/guest_view/web_view_interactive_browsertest.cc b/chrome/browser/apps/guest_view/web_view_interactive_browsertest.cc
index e0d7db6..053f502 100644
--- a/chrome/browser/apps/guest_view/web_view_interactive_browsertest.cc
+++ b/chrome/browser/apps/guest_view/web_view_interactive_browsertest.cc
@@ -1073,6 +1073,51 @@
              NEEDS_TEST_SERVER);
 }
 
+// Ensure that when one <webview> makes a window.open() call that references
+// another <webview> by name, the opener is updated without a crash. Regression
+// test for https://crbug.com/1013553.
+IN_PROC_BROWSER_TEST_F(WebViewNewWindowInteractiveTest,
+                       NewWindow_UpdateOpener) {
+  TestHelper("testNewWindowAndUpdateOpener", "web_view/newwindow",
+             NEEDS_TEST_SERVER);
+
+  // The first <webview> tag in the test will run window.open(), which the
+  // embedder will translate into an injected second <webview> tag, after which
+  // test control will return here.  Wait until there are two guests; i.e.,
+  // until the second <webview>'s guest is also created.
+  GetGuestViewManager()->WaitForNumGuestsCreated(2);
+
+  std::vector<content::WebContents*> guest_contents_list;
+  GetGuestViewManager()->GetGuestWebContentsList(&guest_contents_list);
+  ASSERT_EQ(2u, guest_contents_list.size());
+  content::WebContents* guest1 = guest_contents_list[0];
+  content::WebContents* guest2 = guest_contents_list[1];
+  EXPECT_TRUE(content::WaitForLoadStop(guest1));
+  EXPECT_TRUE(content::WaitForLoadStop(guest2));
+  ASSERT_NE(guest1, guest2);
+
+  // Change first guest's window.name to "foo" and check that it does not
+  // have an opener to start with.
+  EXPECT_TRUE(content::ExecJs(guest1, "window.name = 'foo'"));
+  EXPECT_EQ("foo", content::EvalJs(guest1, "window.name"));
+  EXPECT_EQ(true, content::EvalJs(guest1, "window.opener == null"));
+
+  // Create a subframe in the second guest.  This is needed because the crash
+  // in crbug.com/1013553 only happened when trying to incorrectly create
+  // proxies for a subframe.
+  EXPECT_TRUE(content::ExecuteScript(
+      guest2, "document.body.appendChild(document.createElement('iframe'));"));
+
+  // Update the opener of |guest1| to point to |guest2|.  This triggers
+  // creation of proxies on the new opener chain, which should not crash.
+  EXPECT_TRUE(content::ExecuteScript(guest2, "window.open('', 'foo');"));
+
+  // Ensure both guests have the proper opener relationship set up.  Namely,
+  // each guest's opener should point to the other guest, creating a cycle.
+  EXPECT_EQ(true, content::EvalJs(guest1, "window.opener.opener === window"));
+  EXPECT_EQ(true, content::EvalJs(guest2, "window.opener.opener === window"));
+}
+
 // There is a problem of missing keyup events with the command key after
 // the NSEvent is sent to NSApplication in ui/base/test/ui_controls_mac.mm .
 // This test is disabled on only the Mac until the problem is resolved.
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index fb05f6d..c2582b9 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -20,57 +20,6 @@
         <structure name="IDR_NEW_TAB_4_HTML" file="resources\ntp4\new_tab.html" compress="gzip" flattenhtml="true" type="chrome_html" />
         <structure name="IDR_NEW_TAB_4_THEME_CSS" file="resources\ntp4\new_tab_theme.css" compress="gzip" flattenhtml="true" type="chrome_html" />
 
-        <!-- Bookmarks WebUI. -->
-        <if expr="optimize_webui">
-          <then>
-            <structure name="IDR_BOOKMARKS_VULCANIZED_HTML" file="${root_gen_dir}\chrome\browser\resources\bookmarks\vulcanized.html" use_base_dir="false" preprocess="true" type="chrome_html" compress="gzip" />
-            <structure name="IDR_BOOKMARKS_CRISPER_JS" file="${root_gen_dir}\chrome\browser\resources\bookmarks\crisper.js" use_base_dir="false" preprocess="true" type="chrome_html" compress="gzip" />
-          </then>
-          <else>
-            <structure name="IDR_BOOKMARKS_ACTIONS_HTML" file="resources\bookmarks\actions.html" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_ACTIONS_JS" file="resources\bookmarks\actions.js" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_API_LISTENER_HTML" file="resources\bookmarks\api_listener.html" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_API_LISTENER_JS" file="resources\bookmarks\api_listener.js" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_APP_HTML" file="resources\bookmarks\app.html" type="chrome_html" preprocess="true" />
-            <structure name="IDR_BOOKMARKS_APP_JS" file="resources\bookmarks\app.js" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_BOOKMARKS_HTML" file="resources\bookmarks\bookmarks.html" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_COMMAND_MANAGER_HTML" file="resources\bookmarks\command_manager.html" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_COMMAND_MANAGER_JS" file="resources\bookmarks\command_manager.js" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_CONSTANTS_HTML" file="resources\bookmarks\constants.html" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_CONSTANTS_JS" file="resources\bookmarks\constants.js" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_DEBOUNCER_HTML" file="resources\bookmarks\debouncer.html" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_DEBOUNCER_JS" file="resources\bookmarks\debouncer.js" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_DIALOG_FOCUS_MANAGER_HTML" file="resources\bookmarks\dialog_focus_manager.html" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_DIALOG_FOCUS_MANAGER_JS" file="resources\bookmarks\dialog_focus_manager.js" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_DND_MANAGER_HTML" file="resources\bookmarks\dnd_manager.html" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_DND_MANAGER_JS" file="resources\bookmarks\dnd_manager.js" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_EDIT_DIALOG_HTML" file="resources\bookmarks\edit_dialog.html" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_EDIT_DIALOG_JS" file="resources\bookmarks\edit_dialog.js" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_FOLDER_NODE_HTML" file="resources\bookmarks\folder_node.html" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_FOLDER_NODE_JS" file="resources\bookmarks\folder_node.js" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_ITEM_HTML" file="resources\bookmarks\item.html" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_ITEM_JS" file="resources\bookmarks\item.js" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_LIST_HTML" file="resources\bookmarks\list.html" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_LIST_JS" file="resources\bookmarks\list.js" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_MOUSE_FOCUS_BEHAVIOR_HTML" file="resources\bookmarks\mouse_focus_behavior.html" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_MOUSE_FOCUS_BEHAVIOR_JS" file="resources\bookmarks\mouse_focus_behavior.js" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_REDUCERS_HTML" file="resources\bookmarks\reducers.html" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_REDUCERS_JS" file="resources\bookmarks\reducers.js" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_ROUTER_HTML" file="resources\bookmarks\router.html" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_ROUTER_JS" file="resources\bookmarks\router.js" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_SHARED_STYLE_HTML" file="resources\bookmarks\shared_style.html" preprocess="true" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_SHARED_VARS_HTML" file="resources\bookmarks\shared_vars.html" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_STORE_CLIENT_HTML" file="resources\bookmarks\store_client.html" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_STORE_CLIENT_JS" file="resources\bookmarks\store_client.js" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_STORE_HTML" file="resources\bookmarks\store.html" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_STORE_JS" file="resources\bookmarks\store.js" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_STRINGS_HTML" file="resources\bookmarks\strings.html" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_TOOLBAR_HTML" file="resources\bookmarks\toolbar.html" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_TOOLBAR_JS" file="resources\bookmarks\toolbar.js" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_UTIL_HTML" file="resources\bookmarks\util.html" type="chrome_html" />
-            <structure name="IDR_BOOKMARKS_UTIL_JS" file="resources\bookmarks\util.js" type="chrome_html" />
-          </else>
-        </if>
       </if>
       <if expr="chromeos">
         <structure name="IDR_FIRST_RUN_HTML" file="resources\chromeos\first_run\first_run.html" compress="gzip" flattenhtml="true" type="chrome_html" />
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 8dc95a1..b31c1b5 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -3987,7 +3987,7 @@
     const std::string& key_system,
     const base::flat_set<media::CdmProxy::Protocol>& cdm_proxy_protocols,
     base::flat_set<media::VideoCodec>* video_codecs,
-    base::flat_set<media::EncryptionMode>* encryption_schemes) {
+    base::flat_set<media::EncryptionScheme>* encryption_schemes) {
 #if defined(OS_WIN) && BUILDFLAG(ENABLE_LIBRARY_CDMS) && \
     BUILDFLAG(ENABLE_WIDEVINE)
   if (key_system == kWidevineKeySystem) {
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index f0e1c292..64826f0 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -445,7 +445,7 @@
       const std::string& key_system,
       const base::flat_set<media::CdmProxy::Protocol>& cdm_proxy_protocols,
       base::flat_set<media::VideoCodec>* video_codecs,
-      base::flat_set<media::EncryptionMode>* encryption_schemes) override;
+      base::flat_set<media::EncryptionScheme>* encryption_schemes) override;
   ::rappor::RapporService* GetRapporService() override;
 #if BUILDFLAG(ENABLE_MEDIA_REMOTING)
   void CreateMediaRemoter(
diff --git a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
index b77fbc2..2e15bae 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
@@ -17,6 +17,7 @@
 #include "ash/public/cpp/default_frame_header.h"
 #include "ash/public/cpp/desks_helper.h"
 #include "ash/public/cpp/frame_header.h"
+#include "ash/public/cpp/immersive/immersive_fullscreen_controller.h"
 #include "ash/public/cpp/login_screen.h"
 #include "ash/public/cpp/overview_test_api.h"
 #include "ash/public/cpp/shelf_item.h"
@@ -3056,36 +3057,43 @@
         static_cast<int>(ash::AppType::ARC_APP)) {
       window_info.arc_package_name = std::make_unique<std::string>(
           *window->GetProperty(ash::kArcPackageNameKey));
-
-      ash::HeaderView* header_view = ash::GetHeaderViewForWindow(window);
-      ash::DefaultFrameHeader* frame_header = header_view->GetFrameHeader();
-      window_info.caption_height = frame_header->GetHeaderHeight();
-
-      const ash::CaptionButtonModel* buttonModel =
-          header_view->caption_button_container()->model();
-      int caption_button_enabled_status = 0;
-      int caption_button_visible_status = 0;
-      constexpr views::CaptionButtonIcon all_button_icons[] = {
-          views::CAPTION_BUTTON_ICON_MINIMIZE,
-          views::CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE,
-          views::CAPTION_BUTTON_ICON_CLOSE,
-          views::CAPTION_BUTTON_ICON_LEFT_SNAPPED,
-          views::CAPTION_BUTTON_ICON_RIGHT_SNAPPED,
-          views::CAPTION_BUTTON_ICON_BACK,
-          views::CAPTION_BUTTON_ICON_LOCATION,
-          views::CAPTION_BUTTON_ICON_MENU,
-          views::CAPTION_BUTTON_ICON_ZOOM,
-          views::CAPTION_BUTTON_ICON_COUNT};
-
-      for (const auto button : all_button_icons) {
-        if (buttonModel->IsEnabled(button))
-          caption_button_enabled_status |= (1 << button);
-        if (buttonModel->IsVisible(button))
-          caption_button_visible_status |= (1 << button);
-      }
-      window_info.caption_button_enabled_status = caption_button_enabled_status;
-      window_info.caption_button_visible_status = caption_button_visible_status;
     }
+
+    // Frame information
+    auto* widget = views::Widget::GetWidgetForNativeWindow(window);
+    auto* immersive_controller =
+        ash::ImmersiveFullscreenController::Get(widget);
+    // The widget that hosts the immersive frame can be different from the
+    // application's widget itself. Use the widget from the immersive
+    // controller to obtain the FrameHeader.
+    auto* frame_header = ash::FrameHeader::Get(immersive_controller->widget());
+    window_info.caption_height = frame_header->GetHeaderHeight();
+
+    const ash::CaptionButtonModel* button_model =
+        frame_header->GetCaptionButtonModel();
+    int caption_button_enabled_status = 0;
+    int caption_button_visible_status = 0;
+
+    constexpr views::CaptionButtonIcon all_button_icons[] = {
+        views::CAPTION_BUTTON_ICON_MINIMIZE,
+        views::CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE,
+        views::CAPTION_BUTTON_ICON_CLOSE,
+        views::CAPTION_BUTTON_ICON_LEFT_SNAPPED,
+        views::CAPTION_BUTTON_ICON_RIGHT_SNAPPED,
+        views::CAPTION_BUTTON_ICON_BACK,
+        views::CAPTION_BUTTON_ICON_LOCATION,
+        views::CAPTION_BUTTON_ICON_MENU,
+        views::CAPTION_BUTTON_ICON_ZOOM};
+
+    for (const auto button : all_button_icons) {
+      if (button_model->IsEnabled(button))
+        caption_button_enabled_status |= (1 << button);
+      if (button_model->IsVisible(button))
+        caption_button_visible_status |= (1 << button);
+    }
+    window_info.caption_button_enabled_status = caption_button_enabled_status;
+    window_info.caption_button_visible_status = caption_button_visible_status;
+
     result_list.emplace_back(std::move(window_info));
   }
   return RespondNow(ArgumentList(
diff --git a/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.cc b/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.cc
index 6e6c686..d0c59bb 100644
--- a/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.cc
+++ b/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.cc
@@ -162,6 +162,14 @@
   return node_data;
 }
 
+bool HasPermanentNodes(const std::vector<const BookmarkNode*>& list) {
+  for (const BookmarkNode* node : list) {
+    if (node->is_permanent_node())
+      return true;
+  }
+  return false;
+}
+
 }  // namespace
 
 BookmarkManagerPrivateEventRouter::BookmarkManagerPrivateEventRouter(
@@ -316,6 +324,10 @@
     error_ = bookmark_keys::kModifyManagedError;
     return false;
   }
+  if (cut && HasPermanentNodes(nodes)) {
+    error_ = bookmark_keys::kModifySpecialError;
+    return false;
+  }
   bookmarks::CopyToClipboard(model, nodes, cut);
   return true;
 }
diff --git a/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api_unittest.cc b/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api_unittest.cc
index b5099b0..1994167 100644
--- a/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api_unittest.cc
+++ b/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.h"
 
+#include "base/memory/scoped_refptr.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
@@ -33,6 +34,7 @@
     node_id_ = base::NumberToString(node->id());
   }
 
+  const bookmarks::BookmarkModel* model() const { return model_; }
   std::string node_id() const { return node_id_; }
 
  private:
@@ -47,15 +49,14 @@
 // Regression test for https://crbug.com/739260.
 TEST_F(BookmarkManagerPrivateApiUnitTest, RunOnDeletedNode) {
   // Remove our only bookmark node.
-  scoped_refptr<BookmarksRemoveFunction> remove_function(
-      new BookmarksRemoveFunction());
+  auto remove_function = base::MakeRefCounted<BookmarksRemoveFunction>();
   api_test_utils::RunFunction(remove_function.get(),
                               base::StringPrintf("[\"%s\"]", node_id().c_str()),
                               profile());
 
   // Call bookmarkManagerPrivate.copy() with the removed bookmark node's id.
-  scoped_refptr<BookmarkManagerPrivateCopyFunction> copy_function(
-      new BookmarkManagerPrivateCopyFunction());
+  auto copy_function =
+      base::MakeRefCounted<BookmarkManagerPrivateCopyFunction>();
   EXPECT_EQ(
       "Could not find bookmark nodes with given ids.",
       api_test_utils::RunFunctionAndReturnError(
@@ -63,4 +64,17 @@
           base::StringPrintf("[[\"%s\"]]", node_id().c_str()), profile()));
 }
 
+// Tests that calling bookmarkManagerPrivate.cut() to cut a permanent bookmark
+// node into the clipboard gracefully fails.
+// Regression test for https://crbug.com/1021829.
+TEST_F(BookmarkManagerPrivateApiUnitTest, RunCutOnPermanentNode) {
+  auto cut_function = base::MakeRefCounted<BookmarkManagerPrivateCutFunction>();
+  std::string node_id =
+      base::NumberToString(model()->bookmark_bar_node()->id());
+  EXPECT_EQ("Can't modify the root bookmark folders.",
+            api_test_utils::RunFunctionAndReturnError(
+                cut_function.get(),
+                base::StringPrintf("[[\"%s\"]]", node_id.c_str()), profile()));
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/extensions/service_worker_apitest.cc b/chrome/browser/extensions/service_worker_apitest.cc
index 4b3d7cde..8fe4fe3 100644
--- a/chrome/browser/extensions/service_worker_apitest.cc
+++ b/chrome/browser/extensions/service_worker_apitest.cc
@@ -1190,8 +1190,9 @@
 // Tests that updating a packed extension with modified scripts works
 // properly -- we expect that the new script will execute, rather than the
 // previous one.
+// Flaky on all platforms: https://crbug.com/1003244
 IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest,
-                       UpdatePackedExtension) {
+                       DISABLED_UpdatePackedExtension) {
   constexpr char kManifest1[] =
       R"({
            "name": "Test Extension",
diff --git a/chrome/browser/heavy_ad_intervention/heavy_ad_service.cc b/chrome/browser/heavy_ad_intervention/heavy_ad_service.cc
index 85a2420b..e819ecf 100644
--- a/chrome/browser/heavy_ad_intervention/heavy_ad_service.cc
+++ b/chrome/browser/heavy_ad_intervention/heavy_ad_service.cc
@@ -54,3 +54,14 @@
   heavy_ad_blocklist_ = std::make_unique<HeavyAdBlocklist>(
       std::move(opt_out_store), base::DefaultClock::GetInstance(), this);
 }
+
+void HeavyAdService::InitializeOffTheRecord() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  if (!base::FeatureList::IsEnabled(features::kHeavyAdPrivacyMitigations))
+    return;
+
+  // Providing a null out_out_store which sets up the blocklist in-memory only.
+  heavy_ad_blocklist_ = std::make_unique<HeavyAdBlocklist>(
+      nullptr /* opt_out_store */, base::DefaultClock::GetInstance(), this);
+}
diff --git a/chrome/browser/heavy_ad_intervention/heavy_ad_service.h b/chrome/browser/heavy_ad_intervention/heavy_ad_service.h
index 3d387a3..7c6e2e35 100644
--- a/chrome/browser/heavy_ad_intervention/heavy_ad_service.h
+++ b/chrome/browser/heavy_ad_intervention/heavy_ad_service.h
@@ -29,6 +29,9 @@
   // disk.
   void Initialize(const base::FilePath& profile_path);
 
+  // Initializes the blocklist with no backing store for incognito mode.
+  void InitializeOffTheRecord();
+
   HeavyAdBlocklist* heavy_ad_blocklist() { return heavy_ad_blocklist_.get(); }
 
  private:
diff --git a/chrome/browser/heavy_ad_intervention/heavy_ad_service_factory.cc b/chrome/browser/heavy_ad_intervention/heavy_ad_service_factory.cc
index 211949d..58a3e3f9 100644
--- a/chrome/browser/heavy_ad_intervention/heavy_ad_service_factory.cc
+++ b/chrome/browser/heavy_ad_intervention/heavy_ad_service_factory.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/heavy_ad_intervention/heavy_ad_service_factory.h"
 
 #include "chrome/browser/heavy_ad_intervention/heavy_ad_service.h"
+#include "chrome/browser/profiles/incognito_helpers.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "content/public/browser/browser_context.h"
@@ -39,3 +40,8 @@
     content::BrowserContext* context) const {
   return new HeavyAdService();
 }
+
+content::BrowserContext* HeavyAdServiceFactory::GetBrowserContextToUse(
+    content::BrowserContext* context) const {
+  return chrome::GetBrowserContextOwnInstanceInIncognito(context);
+}
diff --git a/chrome/browser/heavy_ad_intervention/heavy_ad_service_factory.h b/chrome/browser/heavy_ad_intervention/heavy_ad_service_factory.h
index d2afd08..2a97731 100644
--- a/chrome/browser/heavy_ad_intervention/heavy_ad_service_factory.h
+++ b/chrome/browser/heavy_ad_intervention/heavy_ad_service_factory.h
@@ -34,6 +34,8 @@
   // BrowserContextKeyedServiceFactory:
   KeyedService* BuildServiceInstanceFor(
       content::BrowserContext* context) const override;
+  content::BrowserContext* GetBrowserContextToUse(
+      content::BrowserContext* context) const override;
 
   DISALLOW_COPY_AND_ASSIGN(HeavyAdServiceFactory);
 };
diff --git a/chrome/browser/media/widevine_hardware_caps_win.cc b/chrome/browser/media/widevine_hardware_caps_win.cc
index eadfcc1..357cb82 100644
--- a/chrome/browser/media/widevine_hardware_caps_win.cc
+++ b/chrome/browser/media/widevine_hardware_caps_win.cc
@@ -110,7 +110,7 @@
 void GetWidevineHardwareCaps(
     const base::flat_set<media::CdmProxy::Protocol>& cdm_proxy_protocols,
     base::flat_set<media::VideoCodec>* video_codecs,
-    base::flat_set<media::EncryptionMode>* encryption_schemes) {
+    base::flat_set<media::EncryptionScheme>* encryption_schemes) {
   DCHECK(!cdm_proxy_protocols.empty());
   DCHECK(video_codecs->empty());
   DCHECK(encryption_schemes->empty());
@@ -155,5 +155,5 @@
   }
 
   if (!video_codecs->empty())
-    encryption_schemes->insert(media::EncryptionMode::kCenc);
+    encryption_schemes->insert(media::EncryptionScheme::kCenc);
 }
diff --git a/chrome/browser/media/widevine_hardware_caps_win.h b/chrome/browser/media/widevine_hardware_caps_win.h
index 0bd4118f..fe159a6 100644
--- a/chrome/browser/media/widevine_hardware_caps_win.h
+++ b/chrome/browser/media/widevine_hardware_caps_win.h
@@ -10,7 +10,7 @@
 #include "media/cdm/cdm_proxy.h"
 
 namespace media {
-enum class EncryptionMode;
+enum class EncryptionScheme;
 }
 
 // Get supported Widevine hardware capabilities, including supported
@@ -18,6 +18,6 @@
 void GetWidevineHardwareCaps(
     const base::flat_set<media::CdmProxy::Protocol>& cdm_proxy_protocols,
     base::flat_set<media::VideoCodec>* video_codecs,
-    base::flat_set<media::EncryptionMode>* encryption_schemes);
+    base::flat_set<media::EncryptionScheme>* encryption_schemes);
 
 #endif  // CHROME_BROWSER_MEDIA_WIDEVINE_HARDWARE_CAPS_WIN_H_
diff --git a/chrome/browser/media/widevine_hardware_caps_win_unittest.cc b/chrome/browser/media/widevine_hardware_caps_win_unittest.cc
index 7475e04..7468826 100644
--- a/chrome/browser/media/widevine_hardware_caps_win_unittest.cc
+++ b/chrome/browser/media/widevine_hardware_caps_win_unittest.cc
@@ -13,7 +13,7 @@
   base::flat_set<media::CdmProxy::Protocol> cdm_proxy_protocols = {
       media::CdmProxy::Protocol::kIntel};
   base::flat_set<media::VideoCodec> video_codecs;
-  base::flat_set<media::EncryptionMode> encryption_schemes;
+  base::flat_set<media::EncryptionScheme> encryption_schemes;
 
   // Not checking the results since it's hardware dependent.
   GetWidevineHardwareCaps(cdm_proxy_protocols, &video_codecs,
diff --git a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.cc
index 4f44f8a..5ff8708 100644
--- a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.cc
@@ -1103,8 +1103,7 @@
 
   auto* blocklist = GetHeavyAdBlocklist();
 
-  // Treat instances where the blocklist is unavailable as blocklisted. This
-  // includes incognito profiles, which do not create a blocklist service.
+  // Treat instances where the blocklist is unavailable as blocklisted.
   if (!blocklist) {
     heavy_ads_blocklist_blocklisted_ = true;
     return true;
diff --git a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc
index 6e34e3ad..a492349c 100644
--- a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc
+++ b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc
@@ -1130,10 +1130,10 @@
           ->trial_name()));
 }
 
-// Verifies that when the blacklist is at threshold, the heavy ad intervention
+// Verifies that when the blocklist is at threshold, the heavy ad intervention
 // does not trigger.
 IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceBrowserTest,
-                       HeavyAdInterventionBlacklistFull_InterventionBlocked) {
+                       HeavyAdInterventionBlocklistFull_InterventionBlocked) {
   base::HistogramTester histogram_tester;
   auto large_resource_1 =
       std::make_unique<net::test_server::ControllableHttpResponse>(
@@ -1196,6 +1196,43 @@
                                       FrameData::HeavyAdStatus::kNetwork, 1);
 }
 
+// Verifies that the blocklist is setup correctly and the intervention triggers
+// in incognito mode.
+IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceBrowserTest,
+                       HeavyAdInterventionIncognitoMode_InterventionFired) {
+  base::HistogramTester histogram_tester;
+  auto incomplete_resource_response =
+      std::make_unique<net::test_server::ControllableHttpResponse>(
+          embedded_test_server(), "/ads_observer/incomplete_resource.js",
+          true /*relative_url_is_prefix*/);
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  Browser* incognito_browser = CreateIncognitoBrowser();
+  content::WebContents* web_contents =
+      incognito_browser->tab_strip_model()->GetActiveWebContents();
+
+  // Create a navigation observer that will watch for the intervention to
+  // navigate the frame.
+  content::TestNavigationObserver error_observer(web_contents,
+                                                 net::ERR_BLOCKED_BY_CLIENT);
+
+  // Create a waiter for the incognito contents.
+  auto waiter = std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>(
+      web_contents);
+  GURL url = embedded_test_server()->GetURL(
+      "/ads_observer/ad_with_incomplete_resource.html");
+  ui_test_utils::NavigateToURL(incognito_browser, url);
+
+  // Load a resource large enough to trigger the intervention.
+  LoadLargeResource(incomplete_resource_response.get(), kMaxHeavyAdNetworkSize);
+
+  // Wait for the intervention page navigation to finish on the frame.
+  error_observer.WaitForNavigationFinished();
+
+  // Check that the ad frame was navigated to the intervention page.
+  EXPECT_FALSE(error_observer.last_navigation_succeeded());
+}
+
 // Verify that UKM metrics are recorded correctly.
 IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceBrowserTest,
                        RecordedUKMMetrics) {
diff --git a/chrome/browser/payments/empty_parameters_browsertest.cc b/chrome/browser/payments/empty_parameters_browsertest.cc
new file mode 100644
index 0000000..0edbbdb
--- /dev/null
+++ b/chrome/browser/payments/empty_parameters_browsertest.cc
@@ -0,0 +1,57 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "content/public/test/browser_test_utils.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_ANDROID)
+#include "chrome/test/base/android/android_browser_test.h"
+#else
+#include "chrome/test/base/in_process_browser_test.h"
+#endif
+
+namespace content {
+class WebContents;
+}  // namespace content
+
+namespace payments {
+namespace {
+
+class EmptyParametersTest : public PlatformBrowserTest {
+ public:
+  EmptyParametersTest() : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+  ~EmptyParametersTest() override {}
+
+  void SetUpOnMainThread() override {
+    https_server_.ServeFilesFromSourceDirectory(
+        "components/test/data/payments");
+    ASSERT_TRUE(https_server_.Start());
+
+    ASSERT_TRUE(content::NavigateToURL(
+        GetActiveWebContents(),
+        https_server_.GetURL("/empty_parameters_test.html")));
+
+    PlatformBrowserTest::SetUpOnMainThread();
+  }
+
+  content::WebContents* GetActiveWebContents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+ private:
+  net::EmbeddedTestServer https_server_;
+
+  DISALLOW_COPY_AND_ASSIGN(EmptyParametersTest);
+};
+
+IN_PROC_BROWSER_TEST_F(EmptyParametersTest, NoCrash) {
+  EXPECT_EQ(true, content::EvalJs(GetActiveWebContents(), "runTest()"));
+}
+
+}  // namespace
+}  // namespace payments
diff --git a/chrome/browser/pdf/pdf_extension_test.cc b/chrome/browser/pdf/pdf_extension_test.cc
index f974c9b..170b888 100644
--- a/chrome/browser/pdf/pdf_extension_test.cc
+++ b/chrome/browser/pdf/pdf_extension_test.cc
@@ -208,11 +208,11 @@
       FAIL() << catcher.message();
   }
 
-  // Load the PDF at the given URL and use the PDFScriptingAPI to ensure it has
-  // finished loading. Return true if it loads successfully or false if it
-  // fails. If it doesn't finish loading the test will hang. This is done from
-  // outside of the BrowserPlugin guest to ensure the PDFScriptingAPI works
-  // correctly from there.
+  // Load the PDF at the given URL and ensure it has finished loading. Return
+  // true if it loads successfully or false if it fails. If it doesn't finish
+  // loading the test will hang. This is done from outside of the BrowserPlugin
+  // guest to ensure sending messages to/from the plugin works correctly from
+  // there, since the PDFScriptingAPI relies on doing this as well.
   bool LoadPdf(const GURL& url) {
     ui_test_utils::NavigateToURL(browser(), url);
     WebContents* web_contents = GetActiveWebContents();
@@ -1110,10 +1110,10 @@
   ASSERT_TRUE(guest_contents);
 
   WebContents* web_contents = GetActiveWebContents();
-  CHECK(content::ExecuteScript(web_contents,
-                               "var scriptingAPI = new PDFScriptingAPI(window, "
-                               "    document.getElementsByTagName('embed')[0]);"
-                               "scriptingAPI.selectAll();"));
+  CHECK(content::ExecuteScript(
+      web_contents,
+      "document.getElementsByTagName('embed')[0].postMessage("
+      "{type: 'selectAll'});"));
 
   EnableAccessibilityForWebContents(guest_contents);
   WaitForAccessibilityTreeToContainNodeWithName(guest_contents,
diff --git a/chrome/browser/pdf/pdf_extension_test_util.cc b/chrome/browser/pdf/pdf_extension_test_util.cc
index cd1dc783..b30a0a12 100644
--- a/chrome/browser/pdf/pdf_extension_test_util.cc
+++ b/chrome/browser/pdf/pdf_extension_test_util.cc
@@ -4,27 +4,25 @@
 
 #include "chrome/browser/pdf/pdf_extension_test_util.h"
 
-#include "chrome/grit/component_extension_resources.h"
 #include "content/public/test/browser_test_utils.h"
-#include "ui/base/resource/resource_bundle.h"
 
 namespace pdf_extension_test_util {
 
 bool EnsurePDFHasLoaded(content::WebContents* web_contents) {
-  std::string scripting_api_js =
-      ui::ResourceBundle::GetSharedInstance()
-          .GetRawDataResource(IDR_PDF_PDF_SCRIPTING_API_JS)
-          .as_string();
-  CHECK(content::ExecuteScript(web_contents, scripting_api_js));
-
   bool load_success = false;
   CHECK(content::ExecuteScriptAndExtractBool(
       web_contents,
-      "var scriptingAPI = new PDFScriptingAPI(window, "
-      "    document.getElementsByTagName('embed')[0]);"
-      "scriptingAPI.setLoadCallback(function(success) {"
-      "  window.domAutomationController.send(success);"
-      "});",
+      "window.addEventListener('message', event => {"
+      "  if (event.origin !="
+      "          'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai' ||"
+      "      event.data.type != 'documentLoaded') {"
+      "    return;"
+      "  }"
+      "  window.domAutomationController.send("
+      "       event.data.load_state == 'success');"
+      "});"
+      "document.getElementsByTagName('embed')[0].postMessage("
+      "    {type: 'initialize'});",
       &load_success));
   return load_success;
 }
diff --git a/chrome/browser/pdf/pdf_extension_test_util.h b/chrome/browser/pdf/pdf_extension_test_util.h
index 2c5c8b8..3853a296 100644
--- a/chrome/browser/pdf/pdf_extension_test_util.h
+++ b/chrome/browser/pdf/pdf_extension_test_util.h
@@ -11,8 +11,7 @@
 
 namespace pdf_extension_test_util {
 
-// Ensures through PDFScriptingAPI that a PDF has finished loading inside the
-// given |web_contents|.
+// Ensures that a PDF has finished loading inside the given |web_contents|.
 // Returns true if it loads successfully or false if it fails. If it doesn't
 // finish loading the test will hang.
 bool EnsurePDFHasLoaded(content::WebContents* web_contents);
diff --git a/chrome/browser/previews/previews_browsertest.cc b/chrome/browser/previews/previews_browsertest.cc
index 09ac162..dfa865d 100644
--- a/chrome/browser/previews/previews_browsertest.cc
+++ b/chrome/browser/previews/previews_browsertest.cc
@@ -360,9 +360,8 @@
   EXPECT_TRUE(noscript_js_requested());
 }
 
-IN_PROC_BROWSER_TEST_P(
-    PreviewsNoScriptBrowserTest,
-    DISABLE_ON_WIN_MAC_CHROMEOS(NoScriptPreviewsEnabledButHttpRequest)) {
+IN_PROC_BROWSER_TEST_P(PreviewsNoScriptBrowserTest,
+                       DISABLE_ON_WIN_MAC_CHROMEOS(NoScriptPreviewsForHttp)) {
   GURL url = http_url();
 
   // Whitelist NoScript for http_hint_setup_url() host.
@@ -370,9 +369,9 @@
 
   ui_test_utils::NavigateToURL(browser(), url);
 
-  // Verify loaded js resource but not css triggered by noscript tag.
-  EXPECT_TRUE(noscript_js_requested());
-  EXPECT_FALSE(noscript_css_requested());
+  // Verify loaded noscript tag triggered css resource but not js one.
+  EXPECT_TRUE(noscript_css_requested());
+  EXPECT_FALSE(noscript_js_requested());
 }
 
 IN_PROC_BROWSER_TEST_P(PreviewsNoScriptBrowserTest,
diff --git a/chrome/browser/previews/previews_content_util.cc b/chrome/browser/previews/previews_content_util.cc
index bcfef1b..ec9b83f8 100644
--- a/chrome/browser/previews/previews_content_util.cc
+++ b/chrome/browser/previews/previews_content_util.cc
@@ -401,7 +401,6 @@
     content::PreviewsState previews_state,
     const previews::PreviewsDecider* previews_decider,
     content::NavigationHandle* navigation_handle) {
-  bool is_https = url.SchemeIs(url::kHttpsScheme);
 
   // Record whether the hint cache has a matching entry for this committed URL.
   previews_decider->LogHintCacheMatch(url, true /* is_committed */);
@@ -512,10 +511,9 @@
   if (previews_state & content::DEFER_ALL_SCRIPT_ON) {
     // DeferAllScript was allowed for the original URL but only continue with it
     // if the committed URL has HTTPS scheme and is allowed by decider.
-    if (is_https && previews_decider &&
-        previews_decider->ShouldCommitPreview(
-            previews_data, navigation_handle,
-            previews::PreviewsType::DEFER_ALL_SCRIPT)) {
+    if (previews_decider && previews_decider->ShouldCommitPreview(
+                                previews_data, navigation_handle,
+                                previews::PreviewsType::DEFER_ALL_SCRIPT)) {
       LogCommittedPreview(previews_data, PreviewsType::DEFER_ALL_SCRIPT);
       return content::DEFER_ALL_SCRIPT_ON;
     }
@@ -527,7 +525,7 @@
   if (previews_state & content::RESOURCE_LOADING_HINTS_ON) {
     // Resource loading hints was chosen for the original URL but only continue
     // with it if the committed URL has HTTPS scheme and is allowed by decider.
-    if (is_https && previews_decider &&
+    if (previews_decider &&
         previews_decider->ShouldCommitPreview(
             previews_data, navigation_handle,
             previews::PreviewsType::RESOURCE_LOADING_HINTS)) {
@@ -542,10 +540,9 @@
   if (previews_state & content::NOSCRIPT_ON) {
     // NoScript was chosen for the original URL but only continue with it
     // if the committed URL has HTTPS scheme and is allowed by decider.
-    if (is_https && previews_decider &&
-        previews_decider->ShouldCommitPreview(
-            previews_data, navigation_handle,
-            previews::PreviewsType::NOSCRIPT)) {
+    if (previews_decider && previews_decider->ShouldCommitPreview(
+                                previews_data, navigation_handle,
+                                previews::PreviewsType::NOSCRIPT)) {
       LogCommittedPreview(previews_data, PreviewsType::NOSCRIPT);
       return content::NOSCRIPT_ON;
     }
diff --git a/chrome/browser/previews/previews_content_util_unittest.cc b/chrome/browser/previews/previews_content_util_unittest.cc
index 52367e4a..357ab98 100644
--- a/chrome/browser/previews/previews_content_util_unittest.cc
+++ b/chrome/browser/previews/previews_content_util_unittest.cc
@@ -447,25 +447,26 @@
   user_data.set_navigation_ect(net::EFFECTIVE_CONNECTION_TYPE_2G);
   base::HistogramTester histogram_tester;
 
-  // Verify that currently these previews do not commit on HTTP.
-  EXPECT_EQ(content::PREVIEWS_OFF,
+  // Verify that these previews do now commit on HTTP.
+  EXPECT_EQ(content::DEFER_ALL_SCRIPT_ON,
             previews::DetermineCommittedClientPreviewsState(
                 &user_data, GURL("http://www.google.com"),
                 content::NOSCRIPT_ON | content::RESOURCE_LOADING_HINTS_ON |
                     content::DEFER_ALL_SCRIPT_ON,
                 enabled_previews_decider(), nullptr));
   histogram_tester.ExpectTotalCount(
-      "Previews.Triggered.EffectiveConnectionType2", 0);
-
-  // Ensure one does commit on HTTPS (to confirm test setup).
-  EXPECT_NE(content::PREVIEWS_OFF,
-            previews::DetermineCommittedClientPreviewsState(
-                &user_data, GURL("https://www.google.com"),
-                content::NOSCRIPT_ON | content::RESOURCE_LOADING_HINTS_ON |
-                    content::DEFER_ALL_SCRIPT_ON,
-                enabled_previews_decider(), nullptr));
-  histogram_tester.ExpectTotalCount(
       "Previews.Triggered.EffectiveConnectionType2", 1);
+
+  EXPECT_EQ(content::RESOURCE_LOADING_HINTS_ON,
+            previews::DetermineCommittedClientPreviewsState(
+                &user_data, GURL("http://www.google.com"),
+                content::RESOURCE_LOADING_HINTS_ON, enabled_previews_decider(),
+                nullptr));
+
+  EXPECT_EQ(content::NOSCRIPT_ON,
+            previews::DetermineCommittedClientPreviewsState(
+                &user_data, GURL("http://www.google.com"), content::NOSCRIPT_ON,
+                enabled_previews_decider(), nullptr));
 }
 
 TEST_F(PreviewsContentUtilTest,
diff --git a/chrome/browser/previews/resource_loading_hints/resource_loading_hints_browsertest.cc b/chrome/browser/previews/resource_loading_hints/resource_loading_hints_browsertest.cc
index 406525d2..c8a18ea 100644
--- a/chrome/browser/previews/resource_loading_hints/resource_loading_hints_browsertest.cc
+++ b/chrome/browser/previews/resource_loading_hints/resource_loading_hints_browsertest.cc
@@ -936,14 +936,15 @@
       "ResourceLoadingHints.CountBlockedSubresourcePatterns", 0);
 }
 
-IN_PROC_BROWSER_TEST_P(ResourceLoadingHintsBrowserTest,
-                       DISABLE_ON_WIN_MAC_CHROMEOS(ResourceLoadingHintsHttp)) {
+IN_PROC_BROWSER_TEST_P(
+    ResourceLoadingHintsBrowserTest,
+    DISABLE_ON_WIN_MAC_CHROMEOS(ResourceLoadingHintsForHttp)) {
   GURL url = http_url();
 
   // Whitelist resource loading hints for http_hint_setup_url()'s' host.
   SetDefaultOnlyResourceLoadingHints(http_hint_setup_url());
 
-  SetExpectedFooJpgRequest(true);
+  SetExpectedFooJpgRequest(false);
   SetExpectedBarJpgRequest(true);
 
   base::HistogramTester histogram_tester;
@@ -953,11 +954,7 @@
 
   histogram_tester.ExpectBucketCount(
       "Previews.EligibilityReason.ResourceLoadingHints",
-      static_cast<int>(previews::PreviewsEligibilityReason::ALLOWED), 1);
-  histogram_tester.ExpectTotalCount(
-      "Previews.PreviewShown.ResourceLoadingHints", 0);
-  histogram_tester.ExpectTotalCount(
-      "ResourceLoadingHints.CountBlockedSubresourcePatterns", 0);
+      static_cast<int>(previews::PreviewsEligibilityReason::COMMITTED), 1);
 }
 
 IN_PROC_BROWSER_TEST_P(ResourceLoadingHintsBrowserTest,
diff --git a/chrome/browser/profiles/off_the_record_profile_impl.cc b/chrome/browser/profiles/off_the_record_profile_impl.cc
index f3ffc0e5..ea4ea9f 100644
--- a/chrome/browser/profiles/off_the_record_profile_impl.cc
+++ b/chrome/browser/profiles/off_the_record_profile_impl.cc
@@ -31,6 +31,8 @@
 #include "chrome/browser/download/chrome_download_manager_delegate.h"
 #include "chrome/browser/download/download_core_service.h"
 #include "chrome/browser/download/download_core_service_factory.h"
+#include "chrome/browser/heavy_ad_intervention/heavy_ad_service.h"
+#include "chrome/browser/heavy_ad_intervention/heavy_ad_service_factory.h"
 #include "chrome/browser/native_file_system/chrome_native_file_system_permission_context.h"
 #include "chrome/browser/native_file_system/native_file_system_permission_context_factory.h"
 #include "chrome/browser/permissions/permission_manager.h"
@@ -187,6 +189,8 @@
 
   // AccessibilityLabelsService has a default prefs behavior in incognito.
   AccessibilityLabelsService::InitOffTheRecordPrefs(this);
+
+  HeavyAdServiceFactory::GetForBrowserContext(this)->InitializeOffTheRecord();
 }
 
 OffTheRecordProfileImpl::~OffTheRecordProfileImpl() {
diff --git a/chrome/browser/resources/BUILD.gn b/chrome/browser/resources/BUILD.gn
index a842791..93d5f6e 100644
--- a/chrome/browser/resources/BUILD.gn
+++ b/chrome/browser/resources/BUILD.gn
@@ -108,6 +108,34 @@
 }
 
 if (!is_android) {
+  grit("bookmarks_resources") {
+    if (optimize_webui) {
+      source = "bookmarks/bookmarks_resources_vulcanized.grd"
+
+      # The .grd contains references to generated files.
+      source_is_generated = true
+
+      deps = [
+        "//chrome/browser/resources/bookmarks:build",
+      ]
+      grit_flags = [
+        "-E",
+        "root_gen_dir=" + rebase_path(root_gen_dir, root_build_dir),
+      ]
+    } else {
+      source = "bookmarks/bookmarks_resources.grd"
+    }
+
+    defines = chrome_grit_defines
+    outputs = [
+      "grit/bookmarks_resources.h",
+      "grit/bookmarks_resources_map.cc",
+      "grit/bookmarks_resources_map.h",
+      "bookmarks_resources.pak",
+    ]
+    output_dir = "$root_gen_dir/chrome"
+  }
+
   grit("component_extension_resources") {
     source = "component_extension_resources.grd"
 
@@ -381,27 +409,34 @@
   ]
 }
 
-js2gtest("resources_unitjs_tests") {
-  test_type = "webui"
-  sources = [
-    "gaia_auth_host/password_change_authenticator_test.unitjs",
-  ]
+# TODO(https://crbug.com/930109): Figure out why this test fails on MAC ASAN.
+if (!is_asan || !is_mac) {
+  js2gtest("resources_unitjs_tests") {
+    test_type = "webui"
+    sources = [
+      "gaia_auth_host/password_change_authenticator_test.unitjs",
+    ]
 
-  # This has to be a gen_include, so it doesn't collide with other js2gtests
-  gen_include_files = [ "//ui/webui/resources/js/cr.js" ]
+    # This has to be a gen_include, so it doesn't collide with other js2gtests
+    gen_include_files = [ "//ui/webui/resources/js/cr.js" ]
 
-  # But these have to be extra_js_files, since it uses a native object
-  # EventTarget, which doesn't work at compile time.
-  extra_js_files = [
-    "//ui/webui/resources/js/cr/event_target.js",
-    "gaia_auth_host/password_change_authenticator.js",
-  ]
-  defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
-}
+    # But these have to be extra_js_files, since it uses a native object
+    # EventTarget, which doesn't work at compile time.
+    extra_js_files = [
+      "//ui/webui/resources/js/cr/event_target.js",
+      "gaia_auth_host/password_change_authenticator.js",
+    ]
+    defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
+  }
 
-source_set("browser_tests") {
-  testonly = true
-  deps = [
-    ":resources_unitjs_tests",
-  ]
+  source_set("browser_tests") {
+    testonly = true
+    deps = [
+      ":resources_unitjs_tests",
+    ]
+  }
+} else {
+  source_set("browser_tests") {
+    testonly = true
+  }
 }
diff --git a/chrome/browser/resources/bookmarks/BUILD.gn b/chrome/browser/resources/bookmarks/BUILD.gn
index 3fc62cd..5029f2a5 100644
--- a/chrome/browser/resources/bookmarks/BUILD.gn
+++ b/chrome/browser/resources/bookmarks/BUILD.gn
@@ -2,18 +2,52 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//chrome/common/features.gni")
 import("//third_party/closure_compiler/compile_js.gni")
+import("//tools/grit/grit_rule.gni")
 import("../optimize_webui.gni")
 
-optimize_webui("build") {
-  host = "bookmarks"
-  html_in_files = [ "bookmarks.html" ]
-  html_out_files = [ "vulcanized.html" ]
+if (optimize_webui) {
+  bookmarks_pak_file = "bookmarks_resources.pak"
+  unpak_folder = "bookmarks_resources.unpak"
 
-  input = rebase_path(".", root_build_dir)
-  js_out_files = [ "crisper.js" ]
+  optimize_webui("build") {
+    host = "bookmarks"
+    html_in_files = [ "bookmarks.html" ]
+    html_out_files = [ "vulcanized.html" ]
 
-  deps = []
+    input = rebase_path(".", root_build_dir)
+    js_out_files = [ "crisper.js" ]
+
+    deps = [
+      ":unpak",
+    ]
+  }
+
+  unpak("unpak") {
+    pak_file = bookmarks_pak_file
+    out_folder = unpak_folder
+
+    deps = [
+      ":flattened_resources",
+    ]
+  }
+
+  grit("flattened_resources") {
+    source = "bookmarks_resources.grd"
+
+    # The .grd contains references to generated files.
+    source_is_generated = true
+
+    defines = chrome_grit_defines
+    outputs = [
+      "grit/bookmarks_resources.h",
+      "grit/bookmarks_resources_map.cc",
+      "grit/bookmarks_resources_map.h",
+      bookmarks_pak_file,
+    ]
+    output_dir = "$root_gen_dir/chrome/browser/resources/bookmarks"
+  }
 }
 
 js_type_check("closure_compile") {
diff --git a/chrome/browser/resources/bookmarks/bookmarks_resources.grd b/chrome/browser/resources/bookmarks/bookmarks_resources.grd
new file mode 100644
index 0000000..9a80d1a
--- /dev/null
+++ b/chrome/browser/resources/bookmarks/bookmarks_resources.grd
@@ -0,0 +1,145 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+  <outputs>
+    <output filename="grit/bookmarks_resources.h" type="rc_header">
+      <emit emit_type='prepend'></emit>
+    </output>
+    <output filename="grit/bookmarks_resources_map.cc"
+            type="resource_file_map_source" />
+    <output filename="grit/bookmarks_resources_map.h"
+            type="resource_map_header" />
+    <output filename="bookmarks_resources.pak" type="data_package" />
+  </outputs>
+  <release seq="1">
+    <structures>
+      <structure name="IDR_BOOKMARKS_ACTIONS_HTML"
+                 file="actions.html"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_ACTIONS_JS"
+                 file="actions.js"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_API_LISTENER_HTML"
+                 file="api_listener.html"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_API_LISTENER_JS"
+                 file="api_listener.js"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_APP_HTML"
+                 file="app.html"
+                 type="chrome_html"
+                 preprocess="true" />
+      <structure name="IDR_BOOKMARKS_APP_JS"
+                 file="app.js"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_BOOKMARKS_HTML"
+                 file="bookmarks.html"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_COMMAND_MANAGER_HTML"
+                 file="command_manager.html"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_COMMAND_MANAGER_JS"
+                 file="command_manager.js"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_CONSTANTS_HTML"
+                 file="constants.html"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_CONSTANTS_JS"
+                 file="constants.js"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_DEBOUNCER_HTML"
+                 file="debouncer.html"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_DEBOUNCER_JS"
+                 file="debouncer.js"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_DIALOG_FOCUS_MANAGER_HTML"
+                 file="dialog_focus_manager.html"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_DIALOG_FOCUS_MANAGER_JS"
+                 file="dialog_focus_manager.js"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_DND_MANAGER_HTML"
+                 file="dnd_manager.html"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_DND_MANAGER_JS"
+                 file="dnd_manager.js"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_EDIT_DIALOG_HTML"
+                 file="edit_dialog.html"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_EDIT_DIALOG_JS"
+                 file="edit_dialog.js"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_FOLDER_NODE_HTML"
+                 file="folder_node.html"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_FOLDER_NODE_JS"
+                 file="folder_node.js"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_ITEM_HTML"
+                 file="item.html"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_ITEM_JS"
+                 file="item.js"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_LIST_HTML"
+                 file="list.html"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_LIST_JS"
+                 file="list.js"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_MOUSE_FOCUS_BEHAVIOR_HTML"
+                 file="mouse_focus_behavior.html"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_MOUSE_FOCUS_BEHAVIOR_JS"
+                 file="mouse_focus_behavior.js"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_REDUCERS_HTML"
+                 file="reducers.html"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_REDUCERS_JS"
+                 file="reducers.js"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_ROUTER_HTML"
+                 file="router.html"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_ROUTER_JS"
+                 file="router.js"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_SHARED_STYLE_HTML"
+                 file="shared_style.html"
+                 preprocess="true"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_SHARED_VARS_HTML"
+                 file="shared_vars.html"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_STORE_CLIENT_HTML"
+                 file="store_client.html"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_STORE_CLIENT_JS"
+                 file="store_client.js"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_STORE_HTML"
+                 file="store.html"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_STORE_JS"
+                 file="store.js"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_STRINGS_HTML"
+                 file="strings.html"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_TOOLBAR_HTML"
+                 file="toolbar.html"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_TOOLBAR_JS"
+                 file="toolbar.js"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_UTIL_HTML"
+                 file="util.html"
+                 type="chrome_html" />
+      <structure name="IDR_BOOKMARKS_UTIL_JS"
+                 file="util.js"
+                 type="chrome_html" />
+    </structures>
+  </release>
+</grit>
diff --git a/chrome/browser/resources/bookmarks/bookmarks_resources_vulcanized.grd b/chrome/browser/resources/bookmarks/bookmarks_resources_vulcanized.grd
new file mode 100644
index 0000000..d43bc5f
--- /dev/null
+++ b/chrome/browser/resources/bookmarks/bookmarks_resources_vulcanized.grd
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+  <outputs>
+    <output filename="grit/bookmarks_resources.h" type="rc_header">
+      <emit emit_type='prepend'></emit>
+    </output>
+    <output filename="grit/bookmarks_resources_map.cc"
+            type="resource_map_source" />
+    <output filename="grit/bookmarks_resources_map.h"
+            type="resource_map_header" />
+    <output filename="bookmarks_resources.pak" type="data_package" />
+  </outputs>
+  <release seq="1">
+    <structures>
+      <structure name="IDR_BOOKMARKS_VULCANIZED_HTML" file="${root_gen_dir}\chrome\browser\resources\bookmarks\vulcanized.html" use_base_dir="false" preprocess="true" type="chrome_html" compress="gzip" />
+      <structure name="IDR_BOOKMARKS_CRISPER_JS" file="${root_gen_dir}\chrome\browser\resources\bookmarks\crisper.js" use_base_dir="false" preprocess="true" type="chrome_html" compress="gzip" />
+    </structures>
+  </release>
+</grit>
diff --git a/chrome/browser/resources/chromeos/wallpaper_manager/css/wallpaper_manager.css b/chrome/browser/resources/chromeos/wallpaper_manager/css/wallpaper_manager.css
index ca819b4a..bdcd2e0 100644
--- a/chrome/browser/resources/chromeos/wallpaper_manager/css/wallpaper_manager.css
+++ b/chrome/browser/resources/chromeos/wallpaper_manager/css/wallpaper_manager.css
@@ -856,22 +856,26 @@
 }
 
 #message-container {
-  background-color: rgba(0, 0, 0, 0.7);
-  border-radius: 16px;
   bottom: 12px;
-  color: #fff;
-  display: block;
-  left: 50%;
-  max-width: 90%;
-  min-width: 75%;
-  padding: 8px 28px;
+  display: none;
   position: absolute;
   text-align: center;
-  transform: translateX(-50%);
-  visibility: hidden;  /* Need this for correct positioning. */
+  width: 100%;
   z-index: 4;
 }
 
+#message-content {
+  background-color: rgba(0, 0, 0, 0.7);
+  border-radius: 16px;
+  color: #fff;
+  display: inline-block;
+  left: 50%;
+  margin: 0 auto;
+  max-width: 90%;
+  padding: 8px 28px;
+  text-align: center;
+}
+
 #preview-spinner,
 #current-wallpaper-spinner {
   background: url(chrome://resources/images/throbber_medium.svg) no-repeat;
diff --git a/chrome/browser/resources/chromeos/wallpaper_manager/js/event_page.js b/chrome/browser/resources/chromeos/wallpaper_manager/js/event_page.js
index 4cc49b3..7e6dd10 100644
--- a/chrome/browser/resources/chromeos/wallpaper_manager/js/event_page.js
+++ b/chrome/browser/resources/chromeos/wallpaper_manager/js/event_page.js
@@ -313,22 +313,18 @@
         if (!wallpaperPickerWindow)
           return;
         var wpDocument = wallpaperPickerWindow.contentWindow.document;
-        var messageContainer = wpDocument.querySelector('#message-container');
+        var messageContent = wpDocument.querySelector('#message-content');
 
         chrome.wallpaperPrivate.getStrings(strings => {
           if (appName) {
+            wpDocument.querySelector('#message-container').display = 'block';
             var message =
                 strings.currentWallpaperSetByMessage.replace(/\$1/g, appName);
-            messageContainer.textContent = message;
-            messageContainer.style.visibility = 'visible';
+            messageContent.textContent = message;
             wpDocument.querySelector('#checkbox').classList.remove('checked');
             wpDocument.querySelector('#categories-list').disabled = false;
             wpDocument.querySelector('#wallpaper-grid').disabled = false;
           } else {
-            if (messageContainer.textContent !=
-                strings.setSuccessfullyMessage) {
-              messageContainer.style.visibility = 'hidden';
-            }
             Constants.WallpaperSyncStorage.get(
                 Constants.AccessSyncSurpriseMeEnabledKey, function(item) {
                   // TODO(crbug.com/810169): Try to combine this part with
diff --git a/chrome/browser/resources/chromeos/wallpaper_manager/js/wallpaper_manager.js b/chrome/browser/resources/chromeos/wallpaper_manager/js/wallpaper_manager.js
index e14d494..bf52664 100644
--- a/chrome/browser/resources/chromeos/wallpaper_manager/js/wallpaper_manager.js
+++ b/chrome/browser/resources/chromeos/wallpaper_manager/js/wallpaper_manager.js
@@ -363,8 +363,8 @@
  * @param {string} errroMessage The string to show in the error dialog.
  */
 WallpaperManager.prototype.showError_ = function(errorMessage) {
-    $('message-container').textContent = errorMessage;
-    $('message-container').style.visibility = 'visible';
+    $('message-content').textContent = errorMessage;
+    $('message-container').style.display = 'block';
 };
 
 /**
@@ -429,11 +429,11 @@
 
   getThirdPartyAppName(function(appName) {
     if (appName) {
-      $('message-container').textContent =
+      $('message-content').textContent =
           loadTimeData.getStringF('currentWallpaperSetByMessage', appName);
-      $('message-container').style.visibility = 'visible';
+      $('message-container').style.display = 'block';
     } else {
-      $('message-container').style.visibility = 'hidden';
+      $('message-container').style.display = 'none';
     }
   });
 
@@ -451,7 +451,7 @@
     // Force refreshing the images.
     this.wallpaperGrid_.dataModel = null;
     this.onCategoriesChange_();
-    $('message-container').style.visibility = 'hidden';
+    $('message-container').style.display = 'none';
     this.downloadedListMap_ = null;
     $('wallpaper-grid').classList.remove('image-picker-offline');
   });
@@ -712,7 +712,7 @@
  */
 WallpaperManager.prototype.onWallpaperChanged_ = function(
     activeItem, currentWallpaperURL) {
-  $('message-container').style.visibility = 'hidden';
+  $('message-container').style.display = 'none';
   this.wallpaperGrid_.activeItem = activeItem;
   this.currentWallpaper_ = currentWallpaperURL;
   this.decorateCurrentWallpaperInfoBar_();
@@ -1059,7 +1059,7 @@
       var nextPreviewImage = dataModel.item(nextPreviewIndex);
       if (nextPreviewImage.source == Constants.WallpaperSourceEnum.Online)
         this.updateSpinnerVisibility_(true);
-      $('message-container').style.visibility = 'hidden';
+      $('message-container').style.display = 'none';
       this.setWallpaperAttribution(nextPreviewImage);
       this.setSelectedWallpaper_(nextPreviewImage);
       this.currentPreviewIndex_ = nextPreviewIndex;
@@ -1094,7 +1094,7 @@
   };
   this.addEventToButton_($('cancel-preview-wallpaper'), onCancelClicked);
 
-  $('message-container').style.visibility = 'hidden';
+  $('message-container').style.display = 'none';
 };
 
 /*
@@ -1103,10 +1103,10 @@
  */
 WallpaperManager.prototype.showSuccessMessageAndQuit_ = function() {
   this.document_.body.classList.add('wallpaper-set-successfully');
-  $('message-container').textContent = str('setSuccessfullyMessage');
+  $('message-content').textContent = str('setSuccessfullyMessage');
   // Success message must be shown in full screen mode.
   chrome.app.window.current().fullscreen();
-  $('message-container').style.visibility = 'visible';
+  $('message-container').style.display = 'block';
   // Close the window after showing the success message.
   window.setTimeout(() => {
     window.close();
diff --git a/chrome/browser/resources/chromeos/wallpaper_manager/main.html b/chrome/browser/resources/chromeos/wallpaper_manager/main.html
index beb055f..3816df2 100644
--- a/chrome/browser/resources/chromeos/wallpaper_manager/main.html
+++ b/chrome/browser/resources/chromeos/wallpaper_manager/main.html
@@ -182,7 +182,10 @@
       </div>
     </div>
   </div>
-  <div id="message-container" i18n-content="connectionFailed"></div>
+  <div id="message-container">
+    <div id="message-content" i18n-content="connectionFailed">
+    </div>
+  </div>
   <div id="preview-canvas"></div>
   <div id="preview-spinner" hidden></div>
   <div id="daily-refresh-banner-template" hidden>
diff --git a/chrome/browser/resources/settings/device_page/display.js b/chrome/browser/resources/settings/device_page/display.js
index 01f136c..cb87eeb 100644
--- a/chrome/browser/resources/settings/device_page/display.js
+++ b/chrome/browser/resources/settings/device_page/display.js
@@ -700,8 +700,7 @@
       this.logicalResolutionText_ = '';
       return;
     }
-    const mode = this.selectedDisplay.modes[
-        /** @type {number} */ (this.selectedModePref_.value)];
+    const mode = this.selectedDisplay.modes[this.currentSelectedModeIndex_];
     const deviceScaleFactor = mode.deviceScaleFactor;
     const inverseZoomFactor = 1.0 / zoomFactor;
     let logicalResolutionStrId = 'displayZoomLogicalResolutionText';
@@ -710,17 +709,38 @@
     } else if (Math.abs(inverseZoomFactor - 1.0) < 0.001) {
       logicalResolutionStrId = 'displayZoomLogicalResolutionDefaultText';
     }
-    const widthStr =
+    let widthStr =
         Math.round(mode.widthInNativePixels / (deviceScaleFactor * zoomFactor))
             .toString();
-    const heightStr =
+    let heightStr =
         Math.round(mode.heightInNativePixels / (deviceScaleFactor * zoomFactor))
             .toString();
+    if (this.shouldSwapLogicalResolutionText_()) {
+      const temp = widthStr;
+      widthStr = heightStr;
+      heightStr = temp;
+    }
     this.logicalResolutionText_ =
         this.i18n(logicalResolutionStrId, widthStr, heightStr);
   },
 
   /**
+   * Determines whether width and height should be swapped in the
+   * Logical Resolution Text. Returns true if the aspect ratio of the display's
+   * native pixels is not equal to the aspect ratio of the displays current
+   * bounds.
+   * @private
+   */
+  shouldSwapLogicalResolutionText_: function() {
+    const mode = this.selectedDisplay.modes[this.currentSelectedModeIndex_];
+    const bounds = this.selectedDisplay.bounds;
+
+    return (bounds.width / bounds.height).toPrecision(4) !=
+        (mode.widthInNativePixels / mode.heightInNativePixels).toPrecision(4);
+  },
+
+
+  /**
    * Handles the event where the display size slider is being dragged, i.e. the
    * mouse or tap has not been released.
    * @private
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_delegate.cc b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_delegate.cc
index 75c4972..48892e0 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_delegate.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_delegate.cc
@@ -34,6 +34,8 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/ui_base_types.h"
 
+using BrowserDMToken = policy::BrowserDMTokenStorage::BrowserDMToken;
+
 namespace safe_browsing {
 
 const base::Feature kDeepScanningOfUploads{"SafeBrowsingDeepScanningOfUploads",
@@ -45,9 +47,15 @@
 
 namespace {
 
-std::string* GetDMTokenForTestingStorage() {
-  static std::string dm_token;
-  return &dm_token;
+const char** GetDMTokenForTestingStorage() {
+  static const char* dm_token_storage = "";
+  return &dm_token_storage;
+}
+
+BrowserDMToken GetDMTokenForTesting() {
+  const char* dm_token = *GetDMTokenForTestingStorage();
+  return dm_token && dm_token[0] ? BrowserDMToken::CreateValidToken(dm_token)
+                                 : BrowserDMToken::CreateEmptyToken();
 }
 
 // Global pointer of factory function (RepeatingCallback) used to create
@@ -298,8 +306,8 @@
   if (profile->IsOffTheRecord())
     return false;
 
-  // If there's no DM token, the upload will fail.
-  if (GetDMToken().empty())
+  // If there's no valid DM token, the upload will fail.
+  if (!GetDMToken().is_valid())
     return false;
 
   // See if content compliance checks are needed.
@@ -473,8 +481,8 @@
 }
 
 // static
-std::string DeepScanningDialogDelegate::GetDMToken() {
-  std::string dm_token = *GetDMTokenForTestingStorage();
+BrowserDMToken DeepScanningDialogDelegate::GetDMToken() {
+  auto dm_token = GetDMTokenForTesting();
 
 #if !defined(OS_CHROMEOS)
   // This is not compiled on chromeos because
@@ -482,9 +490,9 @@
   // policy::BrowserDMTokenStorage::Get()->RetrieveDMToken() does not return a
   // valid token either.  Once these are fixed the #if !defined can be removed.
 
-  if (dm_token.empty() &&
+  if (dm_token.is_empty() &&
       policy::ChromeBrowserCloudManagementController::IsEnabled()) {
-    dm_token = policy::BrowserDMTokenStorage::Get()->RetrieveDMToken();
+    dm_token = policy::BrowserDMTokenStorage::Get()->RetrieveBrowserDMToken();
   }
 #endif
 
@@ -550,7 +558,7 @@
     request->set_request_malware_scan(std::move(malware_request));
   }
 
-  request->set_dm_token(GetDMToken());
+  request->set_dm_token(GetDMToken().value());
 }
 
 void DeepScanningDialogDelegate::FillAllResultsWith(bool status) {
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_delegate.h b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_delegate.h
index 3e2783c..c4d5694 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_delegate.h
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_delegate.h
@@ -15,6 +15,7 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/strings/string16.h"
+#include "chrome/browser/policy/browser_dm_token_storage.h"
 #include "chrome/browser/safe_browsing/download_protection/binary_upload_service.h"
 #include "chrome/browser/ui/tab_modal_confirm_dialog.h"
 #include "chrome/browser/ui/tab_modal_confirm_dialog_delegate.h"
@@ -190,7 +191,7 @@
   class FileSourceRequest;
 
   // Gets the device level DM token to use with deep scans.
-  static std::string GetDMToken();
+  static policy::BrowserDMTokenStorage::BrowserDMToken GetDMToken();
 
   // Uploads data for deep scanning.  Returns true if uploading is occurring in
   // the background and false if there is nothing to do.
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_delegate_unittest.cc b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_delegate_unittest.cc
index 6cf084f..de9413f 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_delegate_unittest.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_delegate_unittest.cc
@@ -28,16 +28,16 @@
 
 namespace {
 
-const char kDmToken[] = "dm_token";
-const char kTestUrl[] = "http://example.com";
+constexpr char kDmToken[] = "dm_token";
+constexpr char kTestUrl[] = "http://example.com";
 
-const char kTestHttpsSchemePatternUrl[] = "https://*";
-const char kTestChromeSchemePatternUrl[] = "chrome://*";
-const char kTestDevtoolsSchemePatternUrl[] = "devtools://*";
+constexpr char kTestHttpsSchemePatternUrl[] = "https://*";
+constexpr char kTestChromeSchemePatternUrl[] = "chrome://*";
+constexpr char kTestDevtoolsSchemePatternUrl[] = "devtools://*";
 
-const char kTestPathPatternUrl[] = "*/a/specific/path/";
-const char kTestPortPatternUrl[] = "*:1234";
-const char kTestQueryPatternUrl[] = "*?q=5678";
+constexpr char kTestPathPatternUrl[] = "*/a/specific/path/";
+constexpr char kTestPortPatternUrl[] = "*:1234";
+constexpr char kTestQueryPatternUrl[] = "*?q=5678";
 
 class BaseTest : public testing::Test {
  public:
diff --git a/chrome/browser/ui/app_list/app_service/app_service_app_item.cc b/chrome/browser/ui/app_list/app_service/app_service_app_item.cc
index cf37255..5ee0860 100644
--- a/chrome/browser/ui/app_list/app_service/app_service_app_item.cc
+++ b/chrome/browser/ui/app_list/app_service/app_service_app_item.cc
@@ -14,6 +14,7 @@
 #include "chrome/browser/ui/app_list/arc/arc_app_context_menu.h"
 #include "chrome/browser/ui/app_list/crostini/crostini_app_context_menu.h"
 #include "chrome/browser/ui/app_list/extension_app_context_menu.h"
+#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
 
 // static
 const char AppServiceAppItem::kItemType[] = "AppServiceAppItem";
@@ -103,6 +104,14 @@
 }
 
 void AppServiceAppItem::Activate(int event_flags) {
+  // TODO(crbug.com/1022541): Move the Chrome special case to ExtensionApps,
+  // when AppService Instance feature is done.
+  if (id() == extension_misc::kChromeAppId) {
+    ChromeLauncherController::instance()->ActivateApp(
+        id(), ash::LAUNCH_FROM_APP_LIST, event_flags,
+        GetController()->GetAppListDisplayId());
+    return;
+  }
   Launch(event_flags, apps::mojom::LaunchSource::kFromAppListGrid);
 }
 
diff --git a/chrome/browser/ui/libgtkui/gtk_event_loop_x11.cc b/chrome/browser/ui/libgtkui/gtk_event_loop_x11.cc
index 421e8a61..96106ef 100644
--- a/chrome/browser/ui/libgtkui/gtk_event_loop_x11.cc
+++ b/chrome/browser/ui/libgtkui/gtk_event_loop_x11.cc
@@ -11,30 +11,12 @@
 #include <gtk/gtk.h>
 
 #include "base/memory/singleton.h"
+#include "chrome/browser/ui/libgtkui/gtk_util.h"
 #include "ui/events/platform/x11/x11_event_source.h"
 #include "ui/gfx/x/x11_types.h"
 
 namespace libgtkui {
 
-namespace {
-
-// Xkb Events store group attribute into XKeyEvent::state bit field, along with
-// other state-related info, while GdkEventKey objects have separate fields for
-// that purpose, they are ::state and ::group. This function is responsible for
-// recomposing them into a single bit field value when translating GdkEventKey
-// into XKeyEvent. This is similar to XkbBuildCoreState(), but assumes state is
-// an uint rather than an uchar.
-//
-// More details:
-// https://gitlab.freedesktop.org/xorg/proto/xorgproto/blob/master/include/X11/extensions/XKB.h#L372
-int BuildXkbStateFromGdkEvent(const GdkEventKey& keyev) {
-  DCHECK_EQ(0u, XkbGroupForCoreState(keyev.state));
-  DCHECK(XkbIsLegalGroup(keyev.group));
-  return keyev.state | ((keyev.group & 0x3) << 13);
-}
-
-}  // namespace
-
 // static
 GtkEventLoopX11* GtkEventLoopX11::GetInstance() {
   return base::Singleton<GtkEventLoopX11>::get();
@@ -88,9 +70,10 @@
   x_event.xkey.window = GDK_WINDOW_XID(gdk_event_key.window);
   x_event.xkey.root = DefaultRootWindow(x_event.xkey.display);
   x_event.xkey.time = gdk_event_key.time;
-  x_event.xkey.state = BuildXkbStateFromGdkEvent(gdk_event_key);
   x_event.xkey.keycode = gdk_event_key.hardware_keycode;
   x_event.xkey.same_screen = true;
+  x_event.xkey.state =
+      BuildXkbStateFromGdkEvent(gdk_event_key.state, gdk_event_key.group);
 
   // We want to process the gtk event; mapped to an X11 event immediately
   // otherwise if we put it back on the queue we may get items out of order.
diff --git a/chrome/browser/ui/libgtkui/gtk_util.cc b/chrome/browser/ui/libgtkui/gtk_util.cc
index 1f2abb3..94174c8 100644
--- a/chrome/browser/ui/libgtkui/gtk_util.cc
+++ b/chrome/browser/ui/libgtkui/gtk_util.cc
@@ -607,6 +607,11 @@
   return display;
 }
 
+int BuildXkbStateFromGdkEvent(unsigned int state, unsigned char group) {
+  DCHECK_EQ(0u, ((state >> 13) & 0x3));
+  return state | ((group & 0x3) << 13);
+}
+
 GdkEvent* GdkEventFromKeyEvent(const ui::KeyEvent& key_event) {
   GdkEventType event_type =
       key_event.type() == ui::ET_KEY_PRESSED ? GDK_KEY_PRESS : GDK_KEY_RELEASE;
@@ -632,7 +637,7 @@
   gdk_event->key.time = event_time.InMilliseconds();
   gdk_event->key.hardware_keycode = hw_code;
   gdk_event->key.keyval = keyval;
-  gdk_event->key.state = state;
+  gdk_event->key.state = BuildXkbStateFromGdkEvent(state, group);
   gdk_event->key.group = group;
   gdk_event->key.send_event = key_event.flags() & ui::EF_FINAL;
   gdk_event->key.is_modifier = state & GDK_MODIFIER_MASK;
diff --git a/chrome/browser/ui/libgtkui/gtk_util.h b/chrome/browser/ui/libgtkui/gtk_util.h
index c9712cd..ab87074 100644
--- a/chrome/browser/ui/libgtkui/gtk_util.h
+++ b/chrome/browser/ui/libgtkui/gtk_util.h
@@ -177,6 +177,17 @@
 // Get current GdkDisplay instance
 GdkDisplay* GetGdkDisplay();
 
+// Xkb Events store group attribute into XKeyEvent::state bit field, along with
+// other state-related info, while GdkEventKey objects have separate fields for
+// that purpose, they are ::state and ::group. This function is responsible for
+// recomposing them into a single bit field value when translating GdkEventKey
+// into XKeyEvent. This is similar to XkbBuildCoreState(), but assumes state is
+// an uint rather than an uchar.
+//
+// More details:
+// https://gitlab.freedesktop.org/xorg/proto/xorgproto/blob/master/include/X11/extensions/XKB.h#L372
+int BuildXkbStateFromGdkEvent(unsigned int state, unsigned char group);
+
 // Translates |key_event| into a GdkEvent. GdkEvent::key::window is the only
 // field not set by this function, callers must set it, as the way for
 // retrieving it may vary depending on the event being processed. E.g: for IME
diff --git a/chrome/browser/ui/page_info/page_info.cc b/chrome/browser/ui/page_info/page_info.cc
index 08ff0064..aa5aed56 100644
--- a/chrome/browser/ui/page_info/page_info.cc
+++ b/chrome/browser/ui/page_info/page_info.cc
@@ -58,8 +58,6 @@
 #include "components/content_settings/core/common/content_settings_pattern.h"
 #include "components/content_settings/core/common/content_settings_utils.h"
 #include "components/password_manager/core/browser/password_manager_metrics_util.h"
-#include "components/rappor/public/rappor_utils.h"
-#include "components/rappor/rappor_service_impl.h"
 #include "components/safe_browsing/buildflags.h"
 #include "components/safe_browsing/password_protection/metrics_util.h"
 #include "components/safe_browsing/proto/csd.pb.h"
@@ -520,12 +518,6 @@
     UMA_HISTOGRAM_EXACT_LINEAR(
         "WebsiteSettings.OriginInfo.PermissionChanged.Allowed", histogram_value,
         num_values);
-
-    if (type == ContentSettingsType::PLUGINS) {
-      rappor::SampleDomainAndRegistryFromGURL(
-          g_browser_process->rappor_service(),
-          "ContentSettings.Plugins.AddedAllowException", site_url_);
-    }
   } else if (setting == ContentSetting::CONTENT_SETTING_BLOCK) {
     UMA_HISTOGRAM_EXACT_LINEAR(
         "WebsiteSettings.OriginInfo.PermissionChanged.Blocked", histogram_value,
diff --git a/chrome/browser/ui/webui/bookmarks/bookmarks_ui.cc b/chrome/browser/ui/webui/bookmarks/bookmarks_ui.cc
index 788ee09..517fd52 100644
--- a/chrome/browser/ui/webui/bookmarks/bookmarks_ui.cc
+++ b/chrome/browser/ui/webui/bookmarks/bookmarks_ui.cc
@@ -20,7 +20,8 @@
 #include "chrome/browser/ui/webui/plural_string_handler.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/url_constants.h"
-#include "chrome/grit/browser_resources.h"
+#include "chrome/grit/bookmarks_resources.h"
+#include "chrome/grit/bookmarks_resources_map.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/grit/theme_resources.h"
 #include "components/favicon_base/favicon_url_parser.h"
diff --git a/chrome/browser/upgrade_detector/upgrade_detector_impl.cc b/chrome/browser/upgrade_detector/upgrade_detector_impl.cc
index 414c7ce..92910dd 100644
--- a/chrome/browser/upgrade_detector/upgrade_detector_impl.cc
+++ b/chrome/browser/upgrade_detector/upgrade_detector_impl.cc
@@ -419,7 +419,9 @@
 #if defined(OS_WIN)
     // Don't show the update bubbles to enterprise users.
     if (base::IsMachineExternallyManaged() ||
-        !policy::BrowserDMTokenStorage::Get()->RetrieveDMToken().empty()) {
+        policy::BrowserDMTokenStorage::Get()
+            ->RetrieveBrowserDMToken()
+            .is_valid()) {
       return false;
     }
 #endif
diff --git a/chrome/browser/vr/service/browser_xr_runtime.cc b/chrome/browser/vr/service/browser_xr_runtime.cc
index cd06168..0e6db832 100644
--- a/chrome/browser/vr/service/browser_xr_runtime.cc
+++ b/chrome/browser/vr/service/browser_xr_runtime.cc
@@ -116,12 +116,6 @@
   // Rather than just cloning everything, we copy over each field and validate
   // individually.  This ensures new fields don't bypass validation.
   ret->id = id;
-  ret->display_name = info->display_name;
-  DCHECK(info->capabilities);  // Ensured by mojo.
-  ret->capabilities = device::mojom::VRDisplayCapabilities::New(
-      info->capabilities->has_position,
-      info->capabilities->has_external_display, info->capabilities->can_present,
-      info->capabilities->can_provide_environment_integration);
 
   // Maximum 1000km translation.
   if (info->stage_parameters &&
@@ -137,13 +131,6 @@
 
   float kMinFramebufferScale = 0.1f;
   float kMaxFramebufferScale = 1.0f;
-  if (info->webvr_default_framebuffer_scale <= kMaxFramebufferScale &&
-      info->webvr_default_framebuffer_scale >= kMinFramebufferScale) {
-    ret->webvr_default_framebuffer_scale =
-        info->webvr_default_framebuffer_scale;
-  } else {
-    ret->webvr_default_framebuffer_scale = 1;
-  }
 
   if (info->webxr_default_framebuffer_scale <= kMaxFramebufferScale &&
       info->webxr_default_framebuffer_scale >= kMinFramebufferScale) {
@@ -403,14 +390,6 @@
   }
 }
 
-void BrowserXRRuntime::OnInitialized() {
-  DVLOG(2) << __func__;
-  for (auto& callback : pending_initialization_callbacks_) {
-    std::move(callback).Run(display_info_.Clone());
-  }
-  pending_initialization_callbacks_.clear();
-}
-
 void BrowserXRRuntime::OnServiceAdded(VRServiceImpl* service) {
   DVLOG(2) << __func__ << ": id=" << id_;
   services_.insert(service);
@@ -501,19 +480,4 @@
   StopImmersiveSession(base::DoNothing());
 }
 
-void BrowserXRRuntime::InitializeAndGetDisplayInfo(
-    content::RenderFrameHost* render_frame_host,
-    device::mojom::VRService::GetImmersiveVRDisplayInfoCallback callback) {
-  DVLOG(2) << __func__ << ": id=" << id_;
-  device::mojom::VRDisplayInfoPtr device_info = GetVRDisplayInfo();
-  if (device_info) {
-    std::move(callback).Run(std::move(device_info));
-    return;
-  }
-
-  pending_initialization_callbacks_.push_back(std::move(callback));
-  runtime_->EnsureInitialized(
-      base::BindOnce(&BrowserXRRuntime::OnInitialized, base::Unretained(this)));
-}
-
 }  // namespace vr
diff --git a/chrome/browser/vr/service/browser_xr_runtime.h b/chrome/browser/vr/service/browser_xr_runtime.h
index d4d566a1..13b5b73 100644
--- a/chrome/browser/vr/service/browser_xr_runtime.h
+++ b/chrome/browser/vr/service/browser_xr_runtime.h
@@ -83,9 +83,6 @@
   device::mojom::VRDisplayInfoPtr GetVRDisplayInfo() {
     return display_info_.Clone();
   }
-  void InitializeAndGetDisplayInfo(
-      content::RenderFrameHost* render_frame_host,
-      device::mojom::VRService::GetImmersiveVRDisplayInfoCallback callback);
 
   // Methods called to support metrics/overlays on Windows.
   void AddObserver(BrowserXRRuntimeObserver* observer) {
@@ -114,7 +111,6 @@
       mojo::PendingRemote<device::mojom::XRSessionController>
           immersive_session_controller);
   void OnImmersiveSessionError();
-  void OnInitialized();
 
   device::mojom::XRDeviceId id_;
   mojo::Remote<device::mojom::XRRuntime> runtime_;
@@ -128,8 +124,6 @@
 
   mojo::AssociatedReceiver<device::mojom::XRRuntimeEventListener> receiver_{
       this};
-  std::vector<device::mojom::VRService::GetImmersiveVRDisplayInfoCallback>
-      pending_initialization_callbacks_;
 
   base::ObserverList<BrowserXRRuntimeObserver> observers_;
 
diff --git a/chrome/browser/vr/service/vr_service_impl.cc b/chrome/browser/vr/service/vr_service_impl.cc
index f903b13..55ce1cf7 100644
--- a/chrome/browser/vr/service/vr_service_impl.cc
+++ b/chrome/browser/vr/service/vr_service_impl.cc
@@ -605,26 +605,6 @@
   }
 }
 
-void VRServiceImpl::GetImmersiveVRDisplayInfo(
-    device::mojom::VRService::GetImmersiveVRDisplayInfoCallback callback) {
-  if (!initialization_complete_) {
-    pending_requests_.push_back(
-        base::BindOnce(&VRServiceImpl::GetImmersiveVRDisplayInfo,
-                       base::Unretained(this), std::move(callback)));
-    return;
-  }
-
-  BrowserXRRuntime* immersive_runtime =
-      runtime_manager_->GetImmersiveVrRuntime();
-  if (immersive_runtime) {
-    immersive_runtime->InitializeAndGetDisplayInfo(render_frame_host_,
-                                                   std::move(callback));
-    return;
-  }
-
-  std::move(callback).Run(nullptr);
-}
-
 void VRServiceImpl::OnExitPresent() {
   DVLOG(2) << __func__;
 
diff --git a/chrome/browser/vr/service/vr_service_impl.h b/chrome/browser/vr/service/vr_service_impl.h
index 44b98156..4389255f 100644
--- a/chrome/browser/vr/service/vr_service_impl.h
+++ b/chrome/browser/vr/service/vr_service_impl.h
@@ -65,10 +65,6 @@
       device::mojom::VRService::SupportsSessionCallback callback) override;
   void ExitPresent(ExitPresentCallback on_exited) override;
   void SetFramesThrottled(bool throttled) override;
-  // device::mojom::VRService WebVR compatibility functions
-  void GetImmersiveVRDisplayInfo(
-      device::mojom::VRService::GetImmersiveVRDisplayInfoCallback callback)
-      override;
 
   void InitializationComplete();
 
@@ -81,7 +77,6 @@
   void OnExitPresent();
   void OnVisibilityStateChanged(
       device::mojom::XRVisibilityState visibility_state);
-  bool InFocusedFrame() { return in_focused_frame_; }
   void OnDisplayInfoChanged();
   void RuntimesChanged();
 
diff --git a/chrome/chrome_paks.gni b/chrome/chrome_paks.gni
index 82927ac..ed39f54 100644
--- a/chrome/chrome_paks.gni
+++ b/chrome/chrome_paks.gni
@@ -133,6 +133,7 @@
     } else {
       # New paks should be added here by default.
       sources += [
+        "$root_gen_dir/chrome/bookmarks_resources.pak",
         "$root_gen_dir/chrome/component_extension_resources.pak",
         "$root_gen_dir/chrome/dev_ui_resources.pak",
         "$root_gen_dir/chrome/downloads_resources.pak",
@@ -142,6 +143,7 @@
         "$root_gen_dir/headless/headless_lib_resources.pak",
       ]
       deps += [
+        "//chrome/browser/resources:bookmarks_resources",
         "//chrome/browser/resources:component_extension_resources",
         "//chrome/browser/resources:dev_ui_paks",
         "//chrome/browser/resources:downloads_resources",
diff --git a/chrome/common/chrome_content_client.cc b/chrome/common/chrome_content_client.cc
index a5c5055f..8978e11 100644
--- a/chrome/common/chrome_content_client.cc
+++ b/chrome/common/chrome_content_client.cc
@@ -417,8 +417,8 @@
 #endif  // BUILDFLAG(USE_PROPRIETARY_CODECS)
 
   // Both encryption schemes are supported on ChromeOS.
-  capability.encryption_schemes.insert(media::EncryptionMode::kCenc);
-  capability.encryption_schemes.insert(media::EncryptionMode::kCbcs);
+  capability.encryption_schemes.insert(media::EncryptionScheme::kCenc);
+  capability.encryption_schemes.insert(media::EncryptionScheme::kCbcs);
 
   // Both temporary and persistent sessions are supported on ChromeOS.
   capability.session_types.insert(media::CdmSessionType::kTemporary);
@@ -656,7 +656,7 @@
 
       // Supported codecs are hard-coded in ExternalClearKeyProperties.
       content::CdmCapability capability(
-          {}, {media::EncryptionMode::kCenc, media::EncryptionMode::kCbcs},
+          {}, {media::EncryptionScheme::kCenc, media::EncryptionScheme::kCbcs},
           {media::CdmSessionType::kTemporary,
            media::CdmSessionType::kPersistentLicense,
            media::CdmSessionType::kPersistentUsageRecord},
diff --git a/chrome/common/media/cdm_manifest.cc b/chrome/common/media/cdm_manifest.cc
index e7147156..a35b5e9 100644
--- a/chrome/common/media/cdm_manifest.cc
+++ b/chrome/common/media/cdm_manifest.cc
@@ -218,7 +218,7 @@
 // fail. Unrecognized values will be reported but otherwise ignored.
 bool GetEncryptionSchemes(
     const base::Value& manifest,
-    base::flat_set<media::EncryptionMode>* encryption_schemes) {
+    base::flat_set<media::EncryptionScheme>* encryption_schemes) {
   DCHECK(manifest.is_dict());
   DCHECK(encryption_schemes);
 
@@ -227,7 +227,7 @@
   if (!value) {
     // No manifest entry found, so assume only 'cenc' supported for backwards
     // compatibility.
-    encryption_schemes->insert(media::EncryptionMode::kCenc);
+    encryption_schemes->insert(media::EncryptionScheme::kCenc);
     return true;
   }
 
@@ -238,7 +238,7 @@
   }
 
   base::span<const base::Value> list = value->GetList();
-  base::flat_set<media::EncryptionMode> result;
+  base::flat_set<media::EncryptionScheme> result;
   for (const auto& item : list) {
     if (!item.is_string()) {
       DLOG(ERROR) << "Unrecognized item type in CDM manifest entry "
@@ -248,9 +248,9 @@
 
     const std::string& scheme = item.GetString();
     if (scheme == kCdmSupportedEncryptionSchemeCenc) {
-      result.insert(media::EncryptionMode::kCenc);
+      result.insert(media::EncryptionScheme::kCenc);
     } else if (scheme == kCdmSupportedEncryptionSchemeCbcs) {
-      result.insert(media::EncryptionMode::kCbcs);
+      result.insert(media::EncryptionScheme::kCbcs);
     } else {
       DLOG(WARNING) << "Unrecognized encryption scheme '" << scheme
                     << "' in CDM manifest entry "
diff --git a/chrome/common/media/cdm_manifest_unittest.cc b/chrome/common/media/cdm_manifest_unittest.cc
index b12ee87..df99ccdd 100644
--- a/chrome/common/media/cdm_manifest_unittest.cc
+++ b/chrome/common/media/cdm_manifest_unittest.cc
@@ -107,8 +107,8 @@
 }
 
 void CheckEncryptionSchemes(
-    const base::flat_set<media::EncryptionMode>& actual,
-    const std::vector<media::EncryptionMode>& expected) {
+    const base::flat_set<media::EncryptionScheme>& actual,
+    const std::vector<media::EncryptionScheme>& expected) {
   EXPECT_EQ(expected.size(), actual.size());
   for (const auto& encryption_scheme : expected) {
     EXPECT_TRUE(base::Contains(actual, encryption_scheme));
@@ -198,7 +198,7 @@
                media::VideoCodec::kCodecAV1});
   CheckEncryptionSchemes(
       capability.encryption_schemes,
-      {media::EncryptionMode::kCenc, media::EncryptionMode::kCbcs});
+      {media::EncryptionScheme::kCenc, media::EncryptionScheme::kCbcs});
   CheckSessionTypes(capability.session_types,
                     {media::CdmSessionType::kTemporary,
                      media::CdmSessionType::kPersistentLicense});
@@ -212,7 +212,7 @@
   EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
   CheckCodecs(capability.video_codecs, {});
   CheckEncryptionSchemes(capability.encryption_schemes,
-                         {media::EncryptionMode::kCenc});
+                         {media::EncryptionScheme::kCenc});
   CheckSessionTypes(capability.session_types,
                     {media::CdmSessionType::kTemporary});
   CheckProxyProtocols(capability.cdm_proxy_protocols, {});
@@ -298,14 +298,14 @@
     manifest.SetKey(kCdmSupportedEncryptionSchemesName, MakeListValue("cenc"));
     EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
     CheckEncryptionSchemes(capability.encryption_schemes,
-                           {media::EncryptionMode::kCenc});
+                           {media::EncryptionScheme::kCenc});
   }
   {
     CdmCapability capability;
     manifest.SetKey(kCdmSupportedEncryptionSchemesName, MakeListValue("cbcs"));
     EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
     CheckEncryptionSchemes(capability.encryption_schemes,
-                           {media::EncryptionMode::kCbcs});
+                           {media::EncryptionScheme::kCbcs});
   }
   {
     // Try multiple valid entries.
@@ -315,7 +315,7 @@
     EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
     CheckEncryptionSchemes(
         capability.encryption_schemes,
-        {media::EncryptionMode::kCenc, media::EncryptionMode::kCbcs});
+        {media::EncryptionScheme::kCenc, media::EncryptionScheme::kCbcs});
   }
   {
     // Invalid encryption schemes are ignored. However, if value specified then
@@ -331,7 +331,7 @@
                     MakeListValue("invalid", "cenc"));
     EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
     CheckEncryptionSchemes(capability.encryption_schemes,
-                           {media::EncryptionMode::kCenc});
+                           {media::EncryptionScheme::kCenc});
   }
   {
     // Wrong types are an error.
@@ -345,7 +345,7 @@
     EXPECT_TRUE(manifest.RemoveKey(kCdmSupportedEncryptionSchemesName));
     EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
     CheckEncryptionSchemes(capability.encryption_schemes,
-                           {media::EncryptionMode::kCenc});
+                           {media::EncryptionScheme::kCenc});
   }
 }
 
@@ -442,7 +442,7 @@
                media::VideoCodec::kCodecAV1});
   CheckEncryptionSchemes(
       capability.encryption_schemes,
-      {media::EncryptionMode::kCenc, media::EncryptionMode::kCbcs});
+      {media::EncryptionScheme::kCenc, media::EncryptionScheme::kCbcs});
   CheckSessionTypes(capability.session_types,
                     {media::CdmSessionType::kTemporary,
                      media::CdmSessionType::kPersistentLicense});
@@ -522,7 +522,7 @@
   EXPECT_TRUE(ParseCdmManifestFromPath(manifest_path, &version, &capability));
   CheckCodecs(capability.video_codecs, {});
   CheckEncryptionSchemes(capability.encryption_schemes,
-                         {media::EncryptionMode::kCenc});
+                         {media::EncryptionScheme::kCenc});
   CheckSessionTypes(capability.session_types,
                     {media::CdmSessionType::kTemporary});
   CheckProxyProtocols(capability.cdm_proxy_protocols, {});
diff --git a/chrome/installer/mac/signing/pipeline.py b/chrome/installer/mac/signing/pipeline.py
index c1fb432..e3d520c8 100644
--- a/chrome/installer/mac/signing/pipeline.py
+++ b/chrome/installer/mac/signing/pipeline.py
@@ -163,10 +163,15 @@
     #   2. The outer product archive (which is the installable thing that has
     #      pre-install requirements). This is built with `productbuild`.
 
-    # The component package.
+    ## The component package.
 
-    component_pkg_path = os.path.join(paths.work,
-                                      '{}.pkg'.format(dist_config.app_product))
+    # The spaces are removed from |dist_config.app_product| for the component
+    # package path due to a bug in Installer.app that causes the "Show Files"
+    # window to be blank if there is a space in a component package name.
+    # https://stackoverflow.com/questions/43031272/
+    component_pkg_name = '{}.pkg'.format(dist_config.app_product).replace(
+        ' ', '')
+    component_pkg_path = os.path.join(paths.work, component_pkg_name)
     app_path = os.path.join(paths.work, dist_config.app_dir)
 
     commands.run_command([
@@ -175,7 +180,7 @@
         '/Applications', component_pkg_path
     ])
 
-    # The product archive.
+    ## The product archive.
 
     # There are two steps here. The first is to create the "distribution file"
     # which describes the product archive. `productbuild` has a mode to generate
diff --git a/chrome/installer/mac/signing/pipeline_test.py b/chrome/installer/mac/signing/pipeline_test.py
index 68ca9796..fc6917a 100644
--- a/chrome/installer/mac/signing/pipeline_test.py
+++ b/chrome/installer/mac/signing/pipeline_test.py
@@ -314,7 +314,7 @@
             '$W/App Product.req',
             _get_adjacent_item(productbuild_synthesize_args, '--product'))
         self.assertEqual(
-            '$W/App Product.pkg',
+            '$W/AppProduct.pkg',
             _get_adjacent_item(productbuild_synthesize_args, '--package'))
 
         self.assertEqual(
@@ -401,7 +401,7 @@
             '$W/App Product.req',
             _get_adjacent_item(productbuild_synthesize_args, '--product'))
         self.assertEqual(
-            '$W/App Product.pkg',
+            '$W/AppProduct.pkg',
             _get_adjacent_item(productbuild_synthesize_args, '--package'))
 
         self.assertEqual(
diff --git a/chrome/renderer/media/chrome_key_systems_provider_unittest.cc b/chrome/renderer/media/chrome_key_systems_provider_unittest.cc
index a57a4ab..31c21b1 100644
--- a/chrome/renderer/media/chrome_key_systems_provider_unittest.cc
+++ b/chrome/renderer/media/chrome_key_systems_provider_unittest.cc
@@ -29,7 +29,7 @@
   }
 
   media::EmeConfigRule GetEncryptionSchemeConfigRule(
-      media::EncryptionMode encryption_scheme) const override {
+      media::EncryptionScheme encryption_scheme) const override {
     return media::EmeConfigRule::NOT_SUPPORTED;
   }
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 048cdfb..f85cbbe67 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -525,6 +525,7 @@
 
     sources = [
       "../browser/engagement/important_sites_util_browsertest.cc",
+      "../browser/payments/empty_parameters_browsertest.cc",
       "../browser/payments/has_enrolled_instrument_browsertest.cc",
       "../browser/payments/has_enrolled_instrument_query_quota_browsertest.cc",
       "../browser/payments/hybrid_request_skip_ui_browsertest.cc",
@@ -1426,6 +1427,7 @@
         "//chrome/browser/media/router:test_support",
         "//chrome/browser/resources/chromeos/autoclick:browser_tests",
         "//chrome/browser/resources/chromeos/chromevox:browser_tests",
+        "//chrome/browser/resources/chromeos/login:browser_tests",
         "//chrome/browser/resources/chromeos/select_to_speak:browser_tests",
         "//chrome/browser/resources/chromeos/switch_access:browser_tests",
         "//chrome/services/file_util/public/cpp:browser_tests",
@@ -1457,7 +1459,6 @@
       ]
       if (is_chromeos) {
         deps += [
-          "//chrome/browser/resources/chromeos/login:browser_tests",
           "//chromeos/components/help_app_ui:browser_tests_js",
           "//chromeos/components/media_app_ui:browser_tests_js",
         ]
@@ -1841,6 +1842,7 @@
     }
     if (toolkit_views) {
       sources += [
+        "../browser/payments/empty_parameters_browsertest.cc",
         "../browser/payments/has_enrolled_instrument_browsertest.cc",
         "../browser/payments/has_enrolled_instrument_query_quota_browsertest.cc",
         "../browser/payments/journey_logger_browsertest.cc",
diff --git a/chrome/test/data/android/render_tests/StartSurfaceLayoutTest.3_incognito_ntps.Nexus_5-19.png.sha1 b/chrome/test/data/android/render_tests/StartSurfaceLayoutTest.3_incognito_ntps.Nexus_5-19.png.sha1
new file mode 100644
index 0000000..59c7543
--- /dev/null
+++ b/chrome/test/data/android/render_tests/StartSurfaceLayoutTest.3_incognito_ntps.Nexus_5-19.png.sha1
@@ -0,0 +1 @@
+aa72b682f2a1056919067f870c576e0f9f531302
\ No newline at end of file
diff --git a/chrome/test/data/extensions/api_test/autotest_private/test.js b/chrome/test/data/extensions/api_test/autotest_private/test.js
index ae5846c..ac15733d 100644
--- a/chrome/test/data/extensions/api_test/autotest_private/test.js
+++ b/chrome/test/data/extensions/api_test/autotest_private/test.js
@@ -613,15 +613,22 @@
   // This test verifies that test can get the window list and set
   // window state.
   function getWindowInfoAndSetState() {
+    // Button Masks
+    var kMinimizeMask = 1 << 0;
+    var kMaximizeRestoreMask = 1 << 1;
+    var kCloseMask = 1 << 2;
+    var kLeftSnappedMask = 1 << 3;
+    var kRightSnappedMask = 1 << 4;
+
     chrome.autotestPrivate.getAppWindowList(function(list) {
-      var found = false;
+      var browserFrameIndex = -1;
       chrome.test.assertEq(1, list.length);
-      for (i = 0; i < list.length; i++) {
+      for (var i = 0; i < list.length; i++) {
         var window = list[i];
-        if (window.name != 'BrowserFrame') {
+        if (window.windowType != 'Browser') {
           continue;
         }
-        found = true;
+        browserFrameIndex = i;
         // Sanity check
         chrome.test.assertEq('BrowserFrame', window.name);
         chrome.test.assertTrue(window.title.includes('New Tab') > 0);
@@ -634,10 +641,14 @@
         chrome.test.assertTrue(window.hasFocus);
         chrome.test.assertTrue(window.isActive);
         chrome.test.assertFalse(window.hasCapture);
+        chrome.test.assertEq(42, window.captionHeight);
+        chrome.test.assertEq(
+            window.captionButtonVisibleStatus,
+            kMinimizeMask | kMaximizeRestoreMask | kCloseMask |
+            kLeftSnappedMask | kRightSnappedMask);
 
         var change = new Object();
         change.eventType = 'WMEventMaximize';
-        console.log('maximizing');
         chrome.autotestPrivate.setAppWindowState(
             window.id,
             change,
@@ -647,7 +658,7 @@
               chrome.test.succeed();
             });
       }
-      chrome.test.assertTrue(found);
+      chrome.test.assertTrue(-1 != browserFrameIndex);
     });
   },
 
diff --git a/chrome/test/data/extensions/platform_apps/web_view/newwindow/embedder.js b/chrome/test/data/extensions/platform_apps/web_view/newwindow/embedder.js
index b3c0877..ca50809e 100644
--- a/chrome/test/data/extensions/platform_apps/web_view/newwindow/embedder.js
+++ b/chrome/test/data/extensions/platform_apps/web_view/newwindow/embedder.js
@@ -570,6 +570,33 @@
   embedder.setUpNewWindowRequest_(webview, 'guest.html', '', testName);
 }
 
+function testNewWindowAndUpdateOpener() {
+  var testName = 'testNewWindowAndUpdateOpener';
+  var webview = embedder.setUpGuest_('foobar');
+
+  // Convert window.open requests into new <webview> tags.
+  var onNewWindow = function(e) {
+    chrome.test.log('Embedder notified on newwindow');
+    embedder.assertCorrectEvent_(e, '');
+
+    var newwebview = document.createElement('webview');
+    document.querySelector('#webview-tag-container').appendChild(newwebview);
+    try {
+      e.window.attach(newwebview);
+    } catch (e) {
+      embedder.test.fail();
+    }
+
+    // Exit after the first opened window is attached.  The rest of the test is
+    // implemented on the C++ side.
+    embedder.test.succeed();
+  };
+  webview.addEventListener('newwindow', onNewWindow);
+
+  // Load a new window with the given name.
+  embedder.setUpNewWindowRequest_(webview, 'guest.html', '', testName);
+}
+
 embedder.test.testList = {
   'testNewWindowAttachAfterOpenerDestroyed':
       testNewWindowAttachAfterOpenerDestroyed,
@@ -589,7 +616,8 @@
   'testNewWindowWebRequestCloseWindow': testNewWindowWebRequestCloseWindow,
   'testNewWindowWebRequestRemoveElement': testNewWindowWebRequestRemoveElement,
   'testNewWindowWebViewNameTakesPrecedence':
-      testNewWindowWebViewNameTakesPrecedence
+      testNewWindowWebViewNameTakesPrecedence,
+  'testNewWindowAndUpdateOpener': testNewWindowAndUpdateOpener
 };
 
 onload = function() {
diff --git a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastAudioManager.java b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastAudioManager.java
index 43bbf7e7..394b2ed 100644
--- a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastAudioManager.java
+++ b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastAudioManager.java
@@ -10,8 +10,9 @@
 import android.os.Build;
 import android.support.annotation.Nullable;
 
+import androidx.annotation.VisibleForTesting;
+
 import org.chromium.base.Log;
-import org.chromium.base.VisibleForTesting;
 import org.chromium.chromecast.base.Controller;
 import org.chromium.chromecast.base.Observable;
 
diff --git a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastCommandLineHelper.java b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastCommandLineHelper.java
index 1211d132..ea3a3b8 100644
--- a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastCommandLineHelper.java
+++ b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastCommandLineHelper.java
@@ -7,13 +7,14 @@
 import android.content.Intent;
 import android.content.SharedPreferences;
 
+import androidx.annotation.VisibleForTesting;
+
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import org.chromium.base.CommandLine;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
-import org.chromium.base.VisibleForTesting;
 import org.chromium.chromecast.base.Itertools;
 
 import java.util.concurrent.atomic.AtomicBoolean;
diff --git a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsComponent.java b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsComponent.java
index b114443..4b82070 100644
--- a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsComponent.java
+++ b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsComponent.java
@@ -13,8 +13,9 @@
 import android.os.IBinder;
 import android.os.PatternMatcher;
 
+import androidx.annotation.VisibleForTesting;
+
 import org.chromium.base.Log;
-import org.chromium.base.VisibleForTesting;
 import org.chromium.chromecast.base.Controller;
 import org.chromium.content_public.browser.WebContents;
 
diff --git a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/ElidedLogcatProvider.java b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/ElidedLogcatProvider.java
index 05e6ddb..9305d31b 100644
--- a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/ElidedLogcatProvider.java
+++ b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/ElidedLogcatProvider.java
@@ -6,8 +6,9 @@
 
 import android.os.SystemClock;
 
+import androidx.annotation.VisibleForTesting;
+
 import org.chromium.base.Log;
-import org.chromium.base.VisibleForTesting;
 import org.chromium.base.task.AsyncTask;
 
 import java.io.BufferedReader;
diff --git a/chromecast/media/cma/base/decoder_config_adapter.cc b/chromecast/media/cma/base/decoder_config_adapter.cc
index fad7a9f..2b60efc 100644
--- a/chromecast/media/cma/base/decoder_config_adapter.cc
+++ b/chromecast/media/cma/base/decoder_config_adapter.cc
@@ -130,28 +130,13 @@
   }
 }
 
-::media::EncryptionScheme::CipherMode ToMediaCipherMode(
-    EncryptionScheme scheme) {
+EncryptionScheme ToEncryptionScheme(::media::EncryptionScheme scheme) {
   switch (scheme) {
-    case EncryptionScheme::kUnencrypted:
-      return ::media::EncryptionScheme::CIPHER_MODE_UNENCRYPTED;
-    case EncryptionScheme::kAesCtr:
-      return ::media::EncryptionScheme::CIPHER_MODE_AES_CTR;
-    case EncryptionScheme::kAesCbc:
-      return ::media::EncryptionScheme::CIPHER_MODE_AES_CBC;
-    default:
-      NOTREACHED();
-      return ::media::EncryptionScheme::CIPHER_MODE_UNENCRYPTED;
-  }
-}
-
-EncryptionScheme ToEncryptionScheme(const ::media::EncryptionScheme& scheme) {
-  switch (scheme.mode()) {
-    case ::media::EncryptionScheme::CIPHER_MODE_UNENCRYPTED:
+    case ::media::EncryptionScheme::kUnencrypted:
       return EncryptionScheme::kUnencrypted;
-    case ::media::EncryptionScheme::CIPHER_MODE_AES_CTR:
+    case ::media::EncryptionScheme::kCenc:
       return EncryptionScheme::kAesCtr;
-    case ::media::EncryptionScheme::CIPHER_MODE_AES_CBC:
+    case ::media::EncryptionScheme::kCbcs:
       return EncryptionScheme::kAesCbc;
     default:
       NOTREACHED();
@@ -159,10 +144,18 @@
   }
 }
 
-// TODO(yucliu): Remove pattern after update ::media::Audio/VideoDecoderConfig.
 ::media::EncryptionScheme ToMediaEncryptionScheme(EncryptionScheme scheme) {
-  return ::media::EncryptionScheme(ToMediaCipherMode(scheme),
-                                   ::media::EncryptionPattern());
+  switch (scheme) {
+    case EncryptionScheme::kUnencrypted:
+      return ::media::EncryptionScheme::kUnencrypted;
+    case EncryptionScheme::kAesCtr:
+      return ::media::EncryptionScheme::kCenc;
+    case EncryptionScheme::kAesCbc:
+      return ::media::EncryptionScheme::kCbcs;
+    default:
+      NOTREACHED();
+      return ::media::EncryptionScheme::kUnencrypted;
+  }
 }
 
 }  // namespace
diff --git a/chromecast/media/cma/base/demuxer_stream_for_test.cc b/chromecast/media/cma/base/demuxer_stream_for_test.cc
index 99d79204..f1c11a720 100644
--- a/chromecast/media/cma/base/demuxer_stream_for_test.cc
+++ b/chromecast/media/cma/base/demuxer_stream_for_test.cc
@@ -61,7 +61,7 @@
       ::media::VideoDecoderConfig::AlphaMode::kIsOpaque,
       ::media::VideoColorSpace(), ::media::kNoTransformation, coded_size,
       visible_rect, natural_size, ::media::EmptyExtraData(),
-      ::media::Unencrypted());
+      ::media::EncryptionScheme::kUnencrypted);
 }
 
 ::media::DemuxerStream::Type DemuxerStreamForTest::type() const {
diff --git a/chromecast/media/cma/pipeline/audio_video_pipeline_impl_unittest.cc b/chromecast/media/cma/pipeline/audio_video_pipeline_impl_unittest.cc
index 3eb5c38..e20fdf1a 100644
--- a/chromecast/media/cma/pipeline/audio_video_pipeline_impl_unittest.cc
+++ b/chromecast/media/cma/pipeline/audio_video_pipeline_impl_unittest.cc
@@ -137,7 +137,7 @@
       ::media::AudioDecoderConfig audio_config(
           ::media::kCodecMP3, ::media::kSampleFormatS16,
           ::media::CHANNEL_LAYOUT_STEREO, 44100, ::media::EmptyExtraData(),
-          ::media::Unencrypted());
+          ::media::EncryptionScheme::kUnencrypted);
       AvPipelineClient client;
       client.eos_cb = base::Bind(&PipelineHelper::OnEos, base::Unretained(this),
                                  STREAM_AUDIO);
diff --git a/chromecast/media/cma/test/mock_frame_provider.cc b/chromecast/media/cma/test/mock_frame_provider.cc
index 6b4e19b9..b7c5568 100644
--- a/chromecast/media/cma/test/mock_frame_provider.cc
+++ b/chromecast/media/cma/test/mock_frame_provider.cc
@@ -84,15 +84,12 @@
         ::media::VideoDecoderConfig::AlphaMode::kIsOpaque,
         ::media::VideoColorSpace(), ::media::kNoTransformation, coded_size,
         visible_rect, natural_size, ::media::EmptyExtraData(),
-        ::media::Unencrypted());
+        ::media::EncryptionScheme::kUnencrypted);
 
     audio_config = ::media::AudioDecoderConfig(
-      ::media::kCodecAAC,
-      ::media::kSampleFormatS16,
-      ::media::CHANNEL_LAYOUT_STEREO,
-      44100,
-      ::media::EmptyExtraData(),
-      ::media::Unencrypted());
+        ::media::kCodecAAC, ::media::kSampleFormatS16,
+        ::media::CHANNEL_LAYOUT_STEREO, 44100, ::media::EmptyExtraData(),
+        ::media::EncryptionScheme::kUnencrypted);
   }
 
   read_cb.Run(buffer, audio_config, video_config);
diff --git a/chromecast/renderer/media/key_systems_cast.cc b/chromecast/renderer/media/key_systems_cast.cc
index 21166af..b06c086 100644
--- a/chromecast/renderer/media/key_systems_cast.cc
+++ b/chromecast/renderer/media/key_systems_cast.cc
@@ -97,8 +97,8 @@
   }
 
   EmeConfigRule GetEncryptionSchemeConfigRule(
-      ::media::EncryptionMode encryption_mode) const override {
-    if (encryption_mode == ::media::EncryptionMode::kCenc)
+      ::media::EncryptionScheme encryption_scheme) const override {
+    if (encryption_scheme == ::media::EncryptionScheme::kCenc)
       return EmeConfigRule::SUPPORTED;
     return EmeConfigRule::NOT_SUPPORTED;
   }
@@ -162,8 +162,8 @@
 #if BUILDFLAG(ENABLE_WIDEVINE)
   using Robustness = cdm::WidevineKeySystemProperties::Robustness;
 
-  base::flat_set<::media::EncryptionMode> encryption_schemes = {
-      ::media::EncryptionMode::kCenc, ::media::EncryptionMode::kCbcs};
+  base::flat_set<::media::EncryptionScheme> encryption_schemes = {
+      ::media::EncryptionScheme::kCenc, ::media::EncryptionScheme::kCbcs};
 
   key_systems_properties->emplace_back(new cdm::WidevineKeySystemProperties(
       codecs,                            // Regular codecs.
diff --git a/chromeos/services/multidevice_setup/multidevice_setup_impl.cc b/chromeos/services/multidevice_setup/multidevice_setup_impl.cc
index 256e47a..b8a5d755 100644
--- a/chromeos/services/multidevice_setup/multidevice_setup_impl.cc
+++ b/chromeos/services/multidevice_setup/multidevice_setup_impl.cc
@@ -2,6 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <utility>
+#include <vector>
+
 #include "chromeos/services/multidevice_setup/multidevice_setup_impl.h"
 
 #include "base/memory/ptr_util.h"
@@ -182,6 +185,19 @@
   std::move(callback).Run(eligible_remote_devices);
 }
 
+void MultiDeviceSetupImpl::GetEligibleActiveHostDevices(
+    GetEligibleActiveHostDevicesCallback callback) {
+  std::vector<mojom::HostDevicePtr> eligible_active_hosts;
+  for (const auto& host_device :
+       eligible_host_devices_provider_->GetEligibleActiveHostDevices()) {
+    eligible_active_hosts.push_back(
+        mojom::HostDevice::New(host_device.remote_device.GetRemoteDevice(),
+                               host_device.connectivity_status));
+  }
+
+  std::move(callback).Run(std::move(eligible_active_hosts));
+}
+
 void MultiDeviceSetupImpl::SetHostDevice(const std::string& host_device_id,
                                          const std::string& auth_token,
                                          SetHostDeviceCallback callback) {
diff --git a/chromeos/services/multidevice_setup/multidevice_setup_impl.h b/chromeos/services/multidevice_setup/multidevice_setup_impl.h
index 0f01a47..89309f62 100644
--- a/chromeos/services/multidevice_setup/multidevice_setup_impl.h
+++ b/chromeos/services/multidevice_setup/multidevice_setup_impl.h
@@ -87,6 +87,8 @@
   void AddFeatureStateObserver(
       mojo::PendingRemote<mojom::FeatureStateObserver> observer) override;
   void GetEligibleHostDevices(GetEligibleHostDevicesCallback callback) override;
+  void GetEligibleActiveHostDevices(
+      GetEligibleActiveHostDevicesCallback callback) override;
   void SetHostDevice(const std::string& host_device_id,
                      const std::string& auth_token,
                      SetHostDeviceCallback callback) override;
diff --git a/chromeos/services/multidevice_setup/multidevice_setup_impl_unittest.cc b/chromeos/services/multidevice_setup/multidevice_setup_impl_unittest.cc
index 2d40700..10d3fd2 100644
--- a/chromeos/services/multidevice_setup/multidevice_setup_impl_unittest.cc
+++ b/chromeos/services/multidevice_setup/multidevice_setup_impl_unittest.cc
@@ -633,6 +633,20 @@
     return eligible_devices_list;
   }
 
+  std::vector<mojom::HostDevicePtr> CallGetEligibleActiveHostDevices() {
+    base::RunLoop run_loop;
+    multidevice_setup_->GetEligibleActiveHostDevices(base::BindOnce(
+        &MultiDeviceSetupImplTest::OnEligibleActiveHostDevicesFetched,
+        base::Unretained(this), run_loop.QuitClosure()));
+    run_loop.Run();
+
+    std::vector<mojom::HostDevicePtr> eligible_devices_list =
+        std::move(*last_eligible_active_devices_list_);
+    last_eligible_active_devices_list_.reset();
+
+    return eligible_devices_list;
+  }
+
   bool CallSetHostDevice(const std::string& host_device_id,
                          const std::string& auth_token) {
     base::RunLoop run_loop;
@@ -810,6 +824,15 @@
     std::move(quit_closure).Run();
   }
 
+  void OnEligibleActiveHostDevicesFetched(
+      base::OnceClosure quit_closure,
+      std::vector<mojom::HostDevicePtr> eligible_active_devices_list) {
+    EXPECT_FALSE(last_eligible_active_devices_list_);
+    last_eligible_active_devices_list_ =
+        std::move(eligible_active_devices_list);
+    std::move(quit_closure).Run();
+  }
+
   void OnSetHostDeviceResult(base::OnceClosure quit_closure, bool success) {
     EXPECT_FALSE(last_set_host_success_);
     last_set_host_success_ = success;
@@ -899,6 +922,8 @@
 
   base::Optional<bool> last_debug_event_success_;
   base::Optional<multidevice::RemoteDeviceList> last_eligible_devices_list_;
+  base::Optional<std::vector<mojom::HostDevicePtr>>
+      last_eligible_active_devices_list_;
   base::Optional<bool> last_set_host_success_;
   base::Optional<bool> last_set_host_without_auth_success_;
   base::Optional<
@@ -1181,6 +1206,29 @@
   fake_host_backend_delegate()->NotifyHostChangedOnBackend(base::nullopt);
 }
 
+TEST_F(MultiDeviceSetupImplTest, TestGetEligibleActiveHosts) {
+  // Start with no eligible devices.
+  EXPECT_TRUE(CallGetEligibleActiveHostDevices().empty());
+
+  multidevice::DeviceWithConnectivityStatusList host_device_list;
+  for (auto remote_device_ref : test_devices()) {
+    host_device_list.emplace_back(multidevice::DeviceWithConnectivityStatus(
+        remote_device_ref, cryptauthv2::ConnectivityStatus::ONLINE));
+  }
+  // Simulate a sync occurring; now, all of the test devices are eligible hosts.
+  fake_eligible_host_devices_provider()->set_eligible_active_host_devices(
+      host_device_list);
+
+  std::vector<mojom::HostDevicePtr> result_hosts =
+      CallGetEligibleActiveHostDevices();
+  for (size_t i = 0; i < kNumTestDevices; i++) {
+    EXPECT_EQ(*GetMutableRemoteDevice(test_devices()[i]),
+              result_hosts[i]->remote_device);
+    EXPECT_EQ(cryptauthv2::ConnectivityStatus::ONLINE,
+              result_hosts[i]->connectivity_status);
+  }
+}
+
 TEST_F(MultiDeviceSetupImplTest, TestSetHostDevice_InvalidAuthToken) {
   // Start valid eligible host devices.
   fake_eligible_host_devices_provider()->set_eligible_host_devices(
diff --git a/chromeos/services/multidevice_setup/multidevice_setup_initializer.cc b/chromeos/services/multidevice_setup/multidevice_setup_initializer.cc
index d7f2506..cb03408b 100644
--- a/chromeos/services/multidevice_setup/multidevice_setup_initializer.cc
+++ b/chromeos/services/multidevice_setup/multidevice_setup_initializer.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <utility>
+
 #include "chromeos/services/multidevice_setup/multidevice_setup_initializer.h"
 
 #include "base/logging.h"
@@ -138,6 +140,16 @@
   pending_get_eligible_hosts_args_.push_back(std::move(callback));
 }
 
+void MultiDeviceSetupInitializer::GetEligibleActiveHostDevices(
+    GetEligibleActiveHostDevicesCallback callback) {
+  if (multidevice_setup_impl_) {
+    multidevice_setup_impl_->GetEligibleActiveHostDevices(std::move(callback));
+    return;
+  }
+
+  pending_get_eligible_active_hosts_args_.push_back(std::move(callback));
+}
+
 void MultiDeviceSetupInitializer::SetHostDevice(
     const std::string& host_device_id,
     const std::string& auth_token,
@@ -326,6 +338,12 @@
   }
   pending_get_eligible_hosts_args_.clear();
 
+  for (auto& get_eligible_callback : pending_get_eligible_active_hosts_args_) {
+    multidevice_setup_impl_->GetEligibleActiveHostDevices(
+        std::move(get_eligible_callback));
+  }
+  pending_get_eligible_active_hosts_args_.clear();
+
   for (auto& get_host_callback : pending_get_host_args_)
     multidevice_setup_impl_->GetHostStatus(std::move(get_host_callback));
   pending_get_host_args_.clear();
diff --git a/chromeos/services/multidevice_setup/multidevice_setup_initializer.h b/chromeos/services/multidevice_setup/multidevice_setup_initializer.h
index 642bf8b..d213a12 100644
--- a/chromeos/services/multidevice_setup/multidevice_setup_initializer.h
+++ b/chromeos/services/multidevice_setup/multidevice_setup_initializer.h
@@ -95,6 +95,8 @@
   void AddFeatureStateObserver(
       mojo::PendingRemote<mojom::FeatureStateObserver> observer) override;
   void GetEligibleHostDevices(GetEligibleHostDevicesCallback callback) override;
+  void GetEligibleActiveHostDevices(
+      GetEligibleActiveHostDevicesCallback callback) override;
   void SetHostDevice(const std::string& host_device_id,
                      const std::string& auth_token,
                      SetHostDeviceCallback callback) override;
@@ -140,6 +142,8 @@
   std::vector<mojo::PendingRemote<mojom::FeatureStateObserver>>
       pending_feature_state_observers_;
   std::vector<GetEligibleHostDevicesCallback> pending_get_eligible_hosts_args_;
+  std::vector<GetEligibleActiveHostDevicesCallback>
+      pending_get_eligible_active_hosts_args_;
   std::vector<GetHostStatusCallback> pending_get_host_args_;
   std::vector<std::tuple<mojom::Feature,
                          bool,
diff --git a/chromeos/services/multidevice_setup/public/cpp/fake_multidevice_setup.cc b/chromeos/services/multidevice_setup/public/cpp/fake_multidevice_setup.cc
index 3363734..d1c451ba 100644
--- a/chromeos/services/multidevice_setup/public/cpp/fake_multidevice_setup.cc
+++ b/chromeos/services/multidevice_setup/public/cpp/fake_multidevice_setup.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <utility>
+
 #include "chromeos/services/multidevice_setup/public/cpp/fake_multidevice_setup.h"
 
 #include "base/containers/flat_map.h"
@@ -22,6 +24,13 @@
       std::move(get_eligible_hosts_arg).Run(multidevice::RemoteDeviceList());
   }
 
+  for (auto& get_eligible_active_hosts_arg : get_eligible_active_hosts_args_) {
+    if (get_eligible_active_hosts_arg) {
+      std::move(get_eligible_active_hosts_arg)
+          .Run(std::vector<mojom::HostDevicePtr>());
+    }
+  }
+
   for (auto& set_host_arg : set_host_args_) {
     if (std::get<2>(set_host_arg))
       std::move(std::get<2>(set_host_arg)).Run(false /* success */);
@@ -115,6 +124,11 @@
   get_eligible_hosts_args_.push_back(std::move(callback));
 }
 
+void FakeMultiDeviceSetup::GetEligibleActiveHostDevices(
+    GetEligibleActiveHostDevicesCallback callback) {
+  get_eligible_active_hosts_args_.push_back(std::move(callback));
+}
+
 void FakeMultiDeviceSetup::SetHostDevice(const std::string& host_device_id,
                                          const std::string& auth_token,
                                          SetHostDeviceCallback callback) {
diff --git a/chromeos/services/multidevice_setup/public/cpp/fake_multidevice_setup.h b/chromeos/services/multidevice_setup/public/cpp/fake_multidevice_setup.h
index 7ff757b..0a418dc6 100644
--- a/chromeos/services/multidevice_setup/public/cpp/fake_multidevice_setup.h
+++ b/chromeos/services/multidevice_setup/public/cpp/fake_multidevice_setup.h
@@ -94,6 +94,8 @@
   void AddFeatureStateObserver(
       mojo::PendingRemote<mojom::FeatureStateObserver> observer) override;
   void GetEligibleHostDevices(GetEligibleHostDevicesCallback callback) override;
+  void GetEligibleActiveHostDevices(
+      GetEligibleActiveHostDevicesCallback callback) override;
   void SetHostDevice(const std::string& host_device_id,
                      const std::string& auth_token,
                      SetHostDeviceCallback callback) override;
@@ -120,6 +122,8 @@
   mojo::RemoteSet<mojom::FeatureStateObserver> feature_state_observers_;
 
   std::vector<GetEligibleHostDevicesCallback> get_eligible_hosts_args_;
+  std::vector<GetEligibleActiveHostDevicesCallback>
+      get_eligible_active_hosts_args_;
   std::vector<std::tuple<std::string, std::string, SetHostDeviceCallback>>
       set_host_args_;
   size_t num_remove_host_calls_ = 0u;
diff --git a/chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom b/chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom
index 2421ba5..e7566188 100644
--- a/chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom
+++ b/chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom
@@ -5,6 +5,7 @@
 module chromeos.multidevice_setup.mojom;
 
 import "chromeos/components/multidevice/mojom/multidevice_types.mojom";
+import "chromeos/services/device_sync/public/mojom/device_sync.mojom";
 
 // Enumeration of event types which can be dispatched. Only used for debugging
 // purposes.
@@ -91,6 +92,13 @@
   kFurtherSetupRequired = 8,
 };
 
+// Metadata describing a device that can used as the host for multidevice
+// features.
+struct HostDevice {
+  chromeos.multidevice.mojom.RemoteDevice remote_device;
+  chromeos.device_sync.mojom.ConnectivityStatus connectivity_status;
+};
+
 interface AccountStatusChangeDelegate {
   // Callback which indicates that one or more MultiDevice host phones are
   // available for setup with the MultiDevice setup flow. This function is only
@@ -154,6 +162,12 @@
   GetEligibleHostDevices() =>
       (array<chromeos.multidevice.mojom.RemoteDevice> eligible_host_devices);
 
+  // Provides a list of all active eligible host devices (i.e., those which
+  // can be passed to SetHostDevice()) sorted by last usage as determined by
+  // the server.
+  GetEligibleActiveHostDevices() =>
+      (array<HostDevice> eligible_host_devices);
+
   // Sets the host associated with the provided device ID as the host device
   // for this account. The provided auth token must be valid in order to prove
   // that the user is authenticated. If called when there is no current host or
diff --git a/components/arc/bluetooth/bluetooth_type_converters.cc b/components/arc/bluetooth/bluetooth_type_converters.cc
index cba30eb..1a149ce 100644
--- a/components/arc/bluetooth/bluetooth_type_converters.cc
+++ b/components/arc/bluetooth/bluetooth_type_converters.cc
@@ -12,10 +12,9 @@
 
 #include "base/json/json_reader.h"
 #include "base/json/json_writer.h"
-#include "base/stl_util.h"
-#include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "components/arc/bluetooth/bluetooth_type_converters.h"
+#include "device/bluetooth/bluetooth_device.h"
 #include "device/bluetooth/bluetooth_gatt_service.h"
 #include "device/bluetooth/public/cpp/bluetooth_uuid.h"
 
@@ -31,16 +30,6 @@
 constexpr uint16_t kBluetoothProfileDescriptorList = 0x0009;
 constexpr uint16_t kServiceName = 0x0100;
 
-bool IsNonHex(char c) {
-  return !isxdigit(c);
-}
-
-std::string StripNonHex(const std::string& str) {
-  std::string result = str;
-  base::EraseIf(result, IsNonHex);
-  return result;
-}
-
 }  // namespace
 
 namespace mojo {
@@ -52,7 +41,10 @@
 
   arc::mojom::BluetoothAddressPtr mojo_addr =
       arc::mojom::BluetoothAddress::New();
-  base::HexStringToBytes(StripNonHex(address), &mojo_addr->address);
+
+  mojo_addr->address.resize(kAddressSize);
+  if (!device::BluetoothDevice::ParseAddress(address, mojo_addr->address))
+    mojo_addr->address.clear();
 
   return mojo_addr;
 }
diff --git a/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/TaskIds.java b/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/TaskIds.java
index 8229aae0..2d619f6a 100644
--- a/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/TaskIds.java
+++ b/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/TaskIds.java
@@ -13,6 +13,7 @@
     // BackgroundTaskSchedulerUma#toUmaEnumValueFromTaskId(int) method. Also, if the new task id
     // is related to a BackgroundTask class in //chrome, remember to update
     // ChromeBackgroundTaskFactory#getBackgroundTaskFromTaskId(int).
+    // Id from 111000000 to 111999999 are reserved for internal usage.
     public static final int TEST = 0x00008378;
     public static final int OMAHA_JOB_ID = 0x00011684;
 
diff --git a/components/bookmarks/browser/bookmark_model.cc b/components/bookmarks/browser/bookmark_model.cc
index 3e5f4ff3..1e1c3fce 100644
--- a/components/bookmarks/browser/bookmark_model.cc
+++ b/components/bookmarks/browser/bookmark_model.cc
@@ -205,6 +205,7 @@
   DCHECK(parent);
   size_t index = size_t{parent->GetIndexOf(node)};
   DCHECK_NE(size_t{-1}, index);
+  DCHECK(!is_permanent_node(node));
 
   for (BookmarkModelObserver& observer : observers_)
     observer.OnWillRemoveBookmarks(this, parent, index, node);
diff --git a/components/bookmarks/browser/bookmark_model.h b/components/bookmarks/browser/bookmark_model.h
index 568e9b41..073f137d 100644
--- a/components/bookmarks/browser/bookmark_model.h
+++ b/components/bookmarks/browser/bookmark_model.h
@@ -126,7 +126,8 @@
   bool IsDoingExtensiveChanges() const { return extensive_changes_ > 0; }
 
   // Removes |node| from the model and deletes it. Removing a folder node
-  // recursively removes all nodes. Observers are notified immediately.
+  // recursively removes all nodes. Observers are notified immediately. |node|
+  // must not be a permanent node.
   void Remove(const BookmarkNode* node);
 
   // Removes all the non-permanent bookmark nodes that are editable by the user.
diff --git a/components/cdm/renderer/android_key_systems.cc b/components/cdm/renderer/android_key_systems.cc
index ccfcca0..4065088b 100644
--- a/components/cdm/renderer/android_key_systems.cc
+++ b/components/cdm/renderer/android_key_systems.cc
@@ -61,8 +61,8 @@
   }
 
   EmeConfigRule GetEncryptionSchemeConfigRule(
-      media::EncryptionMode encryption_scheme) const override {
-    return encryption_scheme == media::EncryptionMode::kCenc
+      media::EncryptionScheme encryption_scheme) const override {
+    return encryption_scheme == media::EncryptionScheme::kCenc
                ? EmeConfigRule::SUPPORTED
                : EmeConfigRule::NOT_SUPPORTED;
   }
@@ -140,10 +140,10 @@
   if (codecs != media::EME_CODEC_NONE) {
     DVLOG(3) << __func__ << " Widevine supported.";
 
-    base::flat_set<media::EncryptionMode> encryption_schemes = {
-        media::EncryptionMode::kCenc};
+    base::flat_set<media::EncryptionScheme> encryption_schemes = {
+        media::EncryptionScheme::kCenc};
     if (response.is_cbcs_encryption_supported) {
-      encryption_schemes.insert(media::EncryptionMode::kCbcs);
+      encryption_schemes.insert(media::EncryptionScheme::kCbcs);
     }
 
     concrete_key_systems->emplace_back(new WidevineKeySystemProperties(
diff --git a/components/cdm/renderer/external_clear_key_key_system_properties.cc b/components/cdm/renderer/external_clear_key_key_system_properties.cc
index 3a38719..0998dd5 100644
--- a/components/cdm/renderer/external_clear_key_key_system_properties.cc
+++ b/components/cdm/renderer/external_clear_key_key_system_properties.cc
@@ -35,12 +35,12 @@
 }
 
 media::EmeConfigRule ExternalClearKeyProperties::GetEncryptionSchemeConfigRule(
-    media::EncryptionMode encryption_scheme) const {
+    media::EncryptionScheme encryption_scheme) const {
   switch (encryption_scheme) {
-    case media::EncryptionMode::kCenc:
-    case media::EncryptionMode::kCbcs:
+    case media::EncryptionScheme::kCenc:
+    case media::EncryptionScheme::kCbcs:
       return media::EmeConfigRule::SUPPORTED;
-    case media::EncryptionMode::kUnencrypted:
+    case media::EncryptionScheme::kUnencrypted:
       break;
   }
   NOTREACHED();
diff --git a/components/cdm/renderer/external_clear_key_key_system_properties.h b/components/cdm/renderer/external_clear_key_key_system_properties.h
index 6fe015f..4e6389c 100644
--- a/components/cdm/renderer/external_clear_key_key_system_properties.h
+++ b/components/cdm/renderer/external_clear_key_key_system_properties.h
@@ -23,7 +23,7 @@
   bool IsSupportedInitDataType(
       media::EmeInitDataType init_data_type) const override;
   media::EmeConfigRule GetEncryptionSchemeConfigRule(
-      media::EncryptionMode encryption_scheme) const override;
+      media::EncryptionScheme encryption_scheme) const override;
   media::SupportedCodecs GetSupportedCodecs() const override;
   media::EmeConfigRule GetRobustnessConfigRule(
       media::EmeMediaType media_type,
diff --git a/components/cdm/renderer/widevine_key_system_properties.cc b/components/cdm/renderer/widevine_key_system_properties.cc
index f316475..553ac49 100644
--- a/components/cdm/renderer/widevine_key_system_properties.cc
+++ b/components/cdm/renderer/widevine_key_system_properties.cc
@@ -44,9 +44,9 @@
 
 WidevineKeySystemProperties::WidevineKeySystemProperties(
     media::SupportedCodecs codecs,
-    base::flat_set<media::EncryptionMode> encryption_schemes,
+    base::flat_set<media::EncryptionScheme> encryption_schemes,
     media::SupportedCodecs hw_secure_codecs,
-    base::flat_set<media::EncryptionMode> hw_secure_encryption_schemes,
+    base::flat_set<media::EncryptionScheme> hw_secure_encryption_schemes,
     Robustness max_audio_robustness,
     Robustness max_video_robustness,
     media::EmeSessionTypeSupport persistent_license_support,
@@ -84,7 +84,7 @@
 }
 
 EmeConfigRule WidevineKeySystemProperties::GetEncryptionSchemeConfigRule(
-    media::EncryptionMode encryption_scheme) const {
+    media::EncryptionScheme encryption_scheme) const {
   bool is_supported = encryption_schemes_.count(encryption_scheme);
   bool is_hw_secure_supported =
       hw_secure_encryption_schemes_.count(encryption_scheme);
diff --git a/components/cdm/renderer/widevine_key_system_properties.h b/components/cdm/renderer/widevine_key_system_properties.h
index fa51638..cc29ee9 100644
--- a/components/cdm/renderer/widevine_key_system_properties.h
+++ b/components/cdm/renderer/widevine_key_system_properties.h
@@ -30,9 +30,9 @@
 
   WidevineKeySystemProperties(
       media::SupportedCodecs codecs,
-      base::flat_set<media::EncryptionMode> encryption_schemes,
+      base::flat_set<media::EncryptionScheme> encryption_schemes,
       media::SupportedCodecs hw_secure_codecs,
-      base::flat_set<media::EncryptionMode> hw_secure_encryption_schemes,
+      base::flat_set<media::EncryptionScheme> hw_secure_encryption_schemes,
       Robustness max_audio_robustness,
       Robustness max_video_robustness,
       media::EmeSessionTypeSupport persistent_license_support,
@@ -45,7 +45,7 @@
   bool IsSupportedInitDataType(
       media::EmeInitDataType init_data_type) const override;
   media::EmeConfigRule GetEncryptionSchemeConfigRule(
-      media::EncryptionMode encryption_scheme) const override;
+      media::EncryptionScheme encryption_scheme) const override;
   media::SupportedCodecs GetSupportedCodecs() const override;
   media::SupportedCodecs GetSupportedHwSecureCodecs() const override;
   media::EmeConfigRule GetRobustnessConfigRule(
@@ -60,9 +60,9 @@
 
  private:
   const media::SupportedCodecs codecs_;
-  const base::flat_set<media::EncryptionMode> encryption_schemes_;
+  const base::flat_set<media::EncryptionScheme> encryption_schemes_;
   const media::SupportedCodecs hw_secure_codecs_;
-  const base::flat_set<media::EncryptionMode> hw_secure_encryption_schemes_;
+  const base::flat_set<media::EncryptionScheme> hw_secure_encryption_schemes_;
   const Robustness max_audio_robustness_;
   const Robustness max_video_robustness_;
   const media::EmeSessionTypeSupport persistent_license_support_;
diff --git a/components/crash/OWNERS b/components/crash/OWNERS
index dc5fda6..ea972c3 100644
--- a/components/crash/OWNERS
+++ b/components/crash/OWNERS
@@ -1,8 +1,6 @@
-cpu@chromium.org
 jochen@chromium.org
 mark@chromium.org
 rsesek@chromium.org
-scottmg@chromium.org
 thestig@chromium.org
 
 # For Android-specific changes:
diff --git a/components/network_session_configurator/browser/network_session_configurator.cc b/components/network_session_configurator/browser/network_session_configurator.cc
index 3358aa8c..af66c13 100644
--- a/components/network_session_configurator/browser/network_session_configurator.cc
+++ b/components/network_session_configurator/browser/network_session_configurator.cc
@@ -151,12 +151,15 @@
       GetVariationParam(http2_trial_params, "http2_grease_frame_type") ==
           "true") {
     const uint8_t type = 0x0b + 0x1f * base::RandGenerator(8);
-    const uint8_t flags =
-        base::RandGenerator(std::numeric_limits<uint8_t>::max() + 1);
+
+    uint8_t flags;
+    base::RandBytes(&flags, /* output_length = */ sizeof(flags));
+
     const size_t length = base::RandGenerator(7);
     // RandBytesAsString() does not support zero length.
     const std::string payload =
         (length > 0) ? base::RandBytesAsString(length) : std::string();
+
     params->greased_http2_frame =
         base::Optional<net::SpdySessionPool::GreasedHttp2Frame>(
             {type, flags, payload});
diff --git a/components/omnibox/browser/omnibox_field_trial.cc b/components/omnibox/browser/omnibox_field_trial.cc
index 3ff4762..7450f24 100644
--- a/components/omnibox/browser/omnibox_field_trial.cc
+++ b/components/omnibox/browser/omnibox_field_trial.cc
@@ -281,9 +281,19 @@
     OmniboxEventProto::PageClassification current_page_classification,
     DemotionMultipliers* demotions_by_type) {
   demotions_by_type->clear();
+
+  // Explicitly check whether the feature is enabled before calling
+  // |GetValueForRuleInContextByFeature| because it is possible for
+  // |GetValueForRuleInContextByFeature| to return an empty string even if the
+  // feature is enabled, and we don't want to fallback to
+  // |GetValueForRuleInContext| in this case.
   std::string demotion_rule =
-      OmniboxFieldTrial::internal::GetValueForRuleInContext(
-          kDemoteByTypeRule, current_page_classification);
+      base::FeatureList::IsEnabled(omnibox::kOmniboxDemoteByType)
+          ? OmniboxFieldTrial::internal::GetValueForRuleInContextByFeature(
+                omnibox::kOmniboxDemoteByType, kDemoteByTypeRule,
+                current_page_classification)
+          : OmniboxFieldTrial::internal::GetValueForRuleInContext(
+                kDemoteByTypeRule, current_page_classification);
   // If there is no demotion rule for this context, then use the default
   // value for that context.
   if (demotion_rule.empty()) {
diff --git a/components/omnibox/browser/on_device_head_provider.cc b/components/omnibox/browser/on_device_head_provider.cc
index 628b6356..7079414 100644
--- a/components/omnibox/browser/on_device_head_provider.cc
+++ b/components/omnibox/browser/on_device_head_provider.cc
@@ -148,9 +148,6 @@
   if (minimal_changes)
     return;
 
-  // Load the new model first before fulfilling the request if it's available.
-  MaybeResetServingInstanceFromNewModel();
-
   matches_.clear();
   if (!input.text().empty() && serving_) {
     done_ = false;
@@ -199,18 +196,21 @@
 void OnDeviceHeadProvider::OnModelUpdate(
     const std::string& new_model_filename) {
   if (new_model_filename != current_model_filename_ &&
-      new_model_filename_.empty())
-    new_model_filename_ = new_model_filename;
+      !new_model_filename.empty()) {
+    task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(&OnDeviceHeadProvider::ResetServingInstanceFromNewModel,
+                       weak_ptr_factory_.GetWeakPtr(), new_model_filename));
+  }
 }
 
-void OnDeviceHeadProvider::MaybeResetServingInstanceFromNewModel() {
-  if (new_model_filename_.empty())
+void OnDeviceHeadProvider::ResetServingInstanceFromNewModel(
+    const std::string& new_model_filename) {
+  if (new_model_filename.empty())
     return;
-
-  serving_ =
-      OnDeviceHeadServing::Create(new_model_filename_, provider_max_matches_);
-  current_model_filename_ = new_model_filename_;
-  new_model_filename_.clear();
+  current_model_filename_ = new_model_filename;
+  serving_ = OnDeviceHeadServing::Create(current_model_filename_,
+                                         provider_max_matches_);
 }
 
 void OnDeviceHeadProvider::AddProviderInfo(ProvidersInfo* provider_info) const {
diff --git a/components/omnibox/browser/on_device_head_provider.h b/components/omnibox/browser/on_device_head_provider.h
index d0af01a..6202db53 100644
--- a/components/omnibox/browser/on_device_head_provider.h
+++ b/components/omnibox/browser/on_device_head_provider.h
@@ -64,7 +64,7 @@
 
   // Resets |serving_| if new model is available and cleans up the old model if
   // it exists.
-  void MaybeResetServingInstanceFromNewModel();
+  void ResetServingInstanceFromNewModel(const std::string& new_model_filename);
 
   AutocompleteProviderClient* client_;
   AutocompleteProviderListener* listener_;
@@ -73,8 +73,9 @@
   // suggestions matching the Autocomplete input.
   std::unique_ptr<OnDeviceHeadServing> serving_;
 
-  // The task runner instance where asynchronous searches to the head model will
-  // be run.
+  // The task runner instance where asynchronous operations using |serving_|
+  // will be run. Note that SequencedTaskRunner guarantees that operations will
+  // be executed in sequence so we don't need to apply a lock to |serving_|.
   scoped_refptr<base::SequencedTaskRunner> task_runner_;
 
   // The request id used to trace current request to the on device head model.
@@ -85,9 +86,6 @@
   // The filename for the on device model currently being used.
   std::string current_model_filename_;
 
-  // The filename for the new model updated by Component Updater.
-  std::string new_model_filename_;
-
   // Owns the subscription after adding the model update callback to the
   // listener such that the callback can be removed automatically from the
   // listener on provider's deconstruction.
diff --git a/components/omnibox/browser/on_device_head_provider_unittest.cc b/components/omnibox/browser/on_device_head_provider_unittest.cc
index 41b8215..fce8888 100644
--- a/components/omnibox/browser/on_device_head_provider_unittest.cc
+++ b/components/omnibox/browser/on_device_head_provider_unittest.cc
@@ -59,7 +59,6 @@
     if (provider_) {
       provider_->serving_.reset();
       provider_->current_model_filename_.clear();
-      provider_->new_model_filename_.clear();
     }
   }
 
diff --git a/components/omnibox/common/omnibox_features.cc b/components/omnibox/common/omnibox_features.cc
index 1cab7451..993eeb6 100644
--- a/components/omnibox/common/omnibox_features.cc
+++ b/components/omnibox/common/omnibox_features.cc
@@ -288,6 +288,12 @@
     "OmniboxPreserveDefaultMatchAgainstAsyncUpdate",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Demotes the relevance scores when comparing suggestions based on the
+// suggestion's |AutocompleteMatchType| and the user's |PageClassification|.
+// This feature's main job is to contain the DemoteByType parameter.
+const base::Feature kOmniboxDemoteByType{"OmniboxDemoteByType",
+                                         base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Feature to configure on-focus suggestions provided by ZeroSuggestProvider.
 // This feature's main job is to contain some field trial parameters such as:
 //  - "ZeroSuggestVariant" configures the per-page-classification mode of
diff --git a/components/omnibox/common/omnibox_features.h b/components/omnibox/common/omnibox_features.h
index 659de92..831fb41 100644
--- a/components/omnibox/common/omnibox_features.h
+++ b/components/omnibox/common/omnibox_features.h
@@ -53,6 +53,7 @@
 // TODO(tommycli): There are more flags above that belong in this category.
 extern const base::Feature kOmniboxPreserveDefaultMatchScore;
 extern const base::Feature kOmniboxPreserveDefaultMatchAgainstAsyncUpdate;
+extern const base::Feature kOmniboxDemoteByType;
 
 // On-Focus Suggestions a.k.a. ZeroSuggest.
 extern const base::Feature kOnFocusSuggestions;
diff --git a/components/performance_manager/BUILD.gn b/components/performance_manager/BUILD.gn
index 0d8e13c1..279844b 100644
--- a/components/performance_manager/BUILD.gn
+++ b/components/performance_manager/BUILD.gn
@@ -4,6 +4,7 @@
 
 static_library("performance_manager") {
   sources = [
+    "frame_priority/boosting_vote_aggregator.cc",
     "frame_priority/frame_priority.cc",
     "frame_priority/max_vote_aggregator.cc",
     "frame_priority/override_vote_aggregator.cc",
@@ -47,6 +48,7 @@
     "performance_manager_tab_helper.h",
     "process_node_source.cc",
     "process_node_source.h",
+    "public/frame_priority/boosting_vote_aggregator.h",
     "public/frame_priority/frame_priority.h",
     "public/frame_priority/max_vote_aggregator.h",
     "public/frame_priority/override_vote_aggregator.h",
@@ -87,6 +89,7 @@
   testonly = true
 
   sources = [
+    "frame_priority/boosting_vote_aggregator_unittest.cc",
     "frame_priority/frame_priority_unittest.cc",
     "frame_priority/max_vote_aggregator_unittest.cc",
     "frame_priority/override_vote_aggregator_unittest.cc",
diff --git a/components/performance_manager/frame_priority/boosting_vote_aggregator.cc b/components/performance_manager/frame_priority/boosting_vote_aggregator.cc
new file mode 100644
index 0000000..bbe5caf
--- /dev/null
+++ b/components/performance_manager/frame_priority/boosting_vote_aggregator.cc
@@ -0,0 +1,758 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_PUBLIC_FRAME_PRIORITY_BOOSTING_VOTE_AGGREGATOR_H_
+#define CHROME_BROWSER_PERFORMANCE_MANAGER_PUBLIC_FRAME_PRIORITY_BOOSTING_VOTE_AGGREGATOR_H_
+
+#include "components/performance_manager/public/frame_priority/boosting_vote_aggregator.h"
+
+#include <algorithm>
+#include <tuple>
+#include <utility>
+
+#include "base/stl_util.h"
+#include "components/performance_manager/public/graph/frame_node.h"
+
+// This voter allows expressing "priority boosts" which are used to resolve
+// priority inversions. These priority inversions are a result of resource
+// contention. For example, consider frames f0 and f1, where f0 holds a WebLock
+// and f1 is waiting to acquire that WebLock. If the priority of f0 is below
+// that of frame f1 then its priority needs to be boosted in order to prevent
+// a priority inversion.
+//
+// We represent this resource contention relationship with a directed edge from
+// f1 -> f0, which expresses the fact that f1 is waiting on a resource held by
+// f0. We instrument APIs that provide shared access to resources (WebLocks,
+// IndexedDB, etc) and they serve as factories of edges, expressing their "wait
+// lists" to the BoostingVoteAggregator. This graph will in general be quite
+// sparse and consist of directed acyclic components (one per shared resource),
+// but in theory it may be fully connected and cyclic. (A cycle in the graph
+// effectively indicates web content that has expressed a dead lock, so while
+// possible it is unlikely.)
+//
+// Nodes in the graph represent frames, which themselves have baseline
+// priorities that are calculated based on a variety of factors (visibility,
+// type of content, ...). These priorities flow along edges if the next node has
+// a lower priority, "boosting" the priority of the destination node in order to
+// resolve priority inversions. Since the graph is finite and the flow is in one
+// direction only this process is guaranteed to converge for any given graph and
+// set of baseline priorities. We call this graph the "priority flow graph".
+//
+// We break this down into a separate graph problem per priority level. We
+// define a virtual "source" node, and create directed edges from that source
+// node to each node that has received an explicit vote. Finding the set of all
+// nodes that are supported at that priority level then becomes one of
+// reachability from a single source node, which is trivially O(|edges|) using
+// a depth-first search.
+//
+// We wish to maintain this graph in the face of edge additions and removals
+// (both direct priority votes and priority boosts are expressed as edges in
+// this representation). To make this simpler we actually maintain a
+// reachability spanning tree. That is, every reachable node has exactly one
+// "active" (part of the spanning tree) incoming edge. The tree can be
+// maintained as follows:
+//
+// Adding an edge:
+//
+// - Add the edge to the graph and mark it as inactive.
+// - If the destination node is already active, we're done.
+// - If the source node is inactive (and so is the destination node at this
+//   point), then there is no possible path to the node, thus we're done.
+// - At this point the source node is active, and the destination is inactive.
+//   We mark the edge and the destination node as active, and recursively
+//   investigate outbound edges to see if other nodes are now reachable. Since
+//   edges are only ever marked active if they are from an active node to an
+//   inactive node, this can occur once per node, thus each active node has
+//   exactly one incoming edge, and the entire set of active edges and nodes
+//   forms a directed tree.
+//
+// Removing an edge:
+//
+// - If the edge is inactive, simply remove it and return.
+// - If the edge is active, then recurse on the subtree hanging off the
+//   destination node, marking all of the associated edges and nodes as
+//   inactive, while simultaneously maintaining a set of the nodes that have
+//   been marked as inactive.
+//   - For each node that was just marked inactive, traverse inactive back-edges
+//     and find an active ancestor node if possible. Collect these active
+//     ancestors as part of a search front.
+//   - Launch a search from the search front, extending the tree of active nodes
+//     by traversing inactive edges and marking them and the destination node as
+//     active.
+// - The 2-pass approach of first marking the subtree as inactive and *then*
+//   extending the tree is necessary. If these 2 steps were mingled, then when
+//   traversing the subtree and inspecting a node it would be possible to
+//   identify an inactive edge from an ancestor that is actually a descendant
+//   of the current node. The first pass serves to effectively split the tree
+//   into ancestors and descendants so that future edge activations can maintain
+//   the tree relationship rather than creating simple cycles.
+
+namespace performance_manager {
+namespace frame_priority {
+
+namespace {
+
+// Converts a non-default priority level to a zero-based index.
+constexpr size_t PriorityToIndex(base::TaskPriority priority) {
+  DCHECK_NE(base::TaskPriority::LOWEST, priority);
+  return static_cast<size_t>(static_cast<int>(priority) -
+                             static_cast<int>(base::TaskPriority::LOWEST)) -
+         1;
+}
+
+// Converts a priority level to a bit in an |active_layers| variable.
+constexpr uint32_t PriorityToBit(base::TaskPriority priority) {
+  return 1 << PriorityToIndex(priority);
+}
+
+static constexpr uint32_t kFirstLayerBit = 1;
+static constexpr uint32_t kLastLayerBit =
+    PriorityToBit(base::TaskPriority::HIGHEST);
+static_assert(kFirstLayerBit < kLastLayerBit, "expect more than 1 layer");
+
+static const FrameNode* kMaxFrameNode =
+    reinterpret_cast<const FrameNode*>(static_cast<uintptr_t>(0) - 1);
+
+}  // namespace
+
+BoostingVote::BoostingVote(BoostingVoteAggregator* aggregator,
+                           const FrameNode* input_frame,
+                           const FrameNode* output_frame,
+                           const char* reason)
+    : aggregator_(aggregator),
+      input_frame_(input_frame),
+      output_frame_(output_frame),
+      reason_(reason) {
+  aggregator_->SubmitBoostingVote(this);
+}
+
+BoostingVote::BoostingVote(BoostingVote&& rhs) {
+  *this = std::move(rhs);
+}
+
+BoostingVote& BoostingVote::operator=(BoostingVote&& rhs) {
+  // Reset the current BoostingVote first, as it is about to be overwritten.
+  Reset();
+
+  // Take the aggregator.
+  aggregator_ = std::exchange(rhs.aggregator_, nullptr);
+  input_frame_ = std::exchange(rhs.input_frame_, nullptr);
+  output_frame_ = std::exchange(rhs.output_frame_, nullptr);
+  reason_ = std::exchange(rhs.reason_, nullptr);
+
+  return *this;
+}
+
+BoostingVote::~BoostingVote() {
+  Reset();
+}
+
+void BoostingVote::Reset() {
+  if (aggregator_) {
+    aggregator_->CancelBoostingVote(this);
+    aggregator_ = nullptr;
+  }
+}
+
+bool BoostingVoteAggregator::ActiveLayers::IsActive(uint32_t layer_bit) const {
+  return active_layers_ & layer_bit;
+}
+
+void BoostingVoteAggregator::ActiveLayers::SetActive(uint32_t layer_bit) {
+  active_layers_ |= layer_bit;
+}
+
+void BoostingVoteAggregator::ActiveLayers::SetInactive(uint32_t layer_bit) {
+  active_layers_ &= ~layer_bit;
+}
+
+base::TaskPriority BoostingVoteAggregator::NodeData::GetEffectivePriorityLevel()
+    const {
+  if (IsActive(PriorityToBit(base::TaskPriority::HIGHEST)))
+    return base::TaskPriority::HIGHEST;
+  if (IsActive(PriorityToBit(base::TaskPriority::USER_VISIBLE)))
+    return base::TaskPriority::USER_VISIBLE;
+  return base::TaskPriority::LOWEST;
+}
+
+void BoostingVoteAggregator::NodeData::IncrementEdgeCount() {
+  ++edge_count_;
+}
+
+void BoostingVoteAggregator::NodeData::DecrementEdgeCount() {
+  DCHECK_LT(0u, edge_count_);
+  --edge_count_;
+}
+
+VoteReceipt BoostingVoteAggregator::NodeData::SetIncomingVote(
+    VoteConsumer* consumer,
+    VoterId voter_id,
+    const Vote& vote) {
+  incoming_ = AcceptedVote(consumer, voter_id, vote);
+  return incoming_.IssueReceipt();
+}
+
+BoostingVoteAggregator::EdgeData::EdgeData() = default;
+
+BoostingVoteAggregator::EdgeData::EdgeData(EdgeData&&) = default;
+
+BoostingVoteAggregator::EdgeData& BoostingVoteAggregator::EdgeData::operator=(
+    EdgeData&&) = default;
+
+BoostingVoteAggregator::EdgeData::~EdgeData() = default;
+
+void BoostingVoteAggregator::EdgeData::AddReason(const char* reason) {
+  reasons_.push_back(reason);
+}
+
+bool BoostingVoteAggregator::EdgeData::RemoveReason(const char* reason) {
+  // Special case: there's only one reason.
+  if (reasons_.size() == 1) {
+    DCHECK_EQ(reason, reasons_[0]);
+    reasons_.clear();
+    return true;
+  }
+
+  bool active_reason_changed = false;
+
+  // Look for the reason to be erased anywhere but in the first position. This
+  // is so that we can avoid causing an active reason change when possible.
+  auto it = std::find(reasons_.begin() + 1, reasons_.end(), reason);
+
+  // If the reason being deleted is the sole instance of the active reason (we
+  // didn't find another instance in our search above), then there's no way to
+  // avoid propagating a reason change.
+  if (it == reasons_.end()) {
+    DCHECK_EQ(reason, reasons_[0]);
+    it = reasons_.begin();
+    active_reason_changed = true;
+  }
+
+  // Replace the element being erased with the last one in the array. If it was
+  // already the last one in the array this is a nop.
+  *it = reasons_.back();
+  reasons_.pop_back();
+
+  return active_reason_changed;
+}
+
+const char* BoostingVoteAggregator::EdgeData::GetActiveReason() const {
+  DCHECK(!reasons_.empty());
+  return reasons_[0];
+}
+
+BoostingVoteAggregator::BoostingVoteAggregator() : factory_(this) {}
+
+BoostingVoteAggregator::~BoostingVoteAggregator() {
+  DCHECK(forward_edges_.empty());
+  DCHECK(reverse_edges_.empty());
+}
+
+VotingChannel BoostingVoteAggregator::GetVotingChannel() {
+  DCHECK(nodes_.empty());
+  DCHECK_EQ(kInvalidVoterId, input_voter_id_);
+  DCHECK_GT(1u, factory_.voting_channels_issued());
+  auto channel = factory_.BuildVotingChannel();
+  input_voter_id_ = channel.voter_id();
+  return channel;
+}
+
+void BoostingVoteAggregator::SetUpstreamVotingChannel(VotingChannel&& channel) {
+  DCHECK(channel.IsValid());
+  DCHECK(nodes_.empty());
+  DCHECK(!channel_.IsValid());
+  channel_ = std::move(channel);
+}
+
+bool BoostingVoteAggregator::IsSetup() const {
+  return input_voter_id_ != kInvalidVoterId && channel_.IsValid();
+}
+
+void BoostingVoteAggregator::SubmitBoostingVote(
+    const BoostingVote* boosting_vote) {
+  bool is_new_edge = false;
+
+  // Ensure an entry exists in the edge map.
+  EdgeData* edge_data = nullptr;
+  ForwardEdge fwd_edge(boosting_vote);
+  auto it = forward_edges_.lower_bound(fwd_edge);
+  if (it == forward_edges_.end() || it->first != fwd_edge) {
+    is_new_edge = true;
+    edge_data =
+        &forward_edges_.insert(it, std::make_pair(fwd_edge, EdgeData()))
+             ->second;
+    reverse_edges_.insert(
+        std::make_pair(ReverseEdge(boosting_vote), edge_data));
+  } else {
+    edge_data = &it->second;
+  }
+
+  // Update the list of reasons.
+  edge_data->AddReason(boosting_vote->reason());
+
+  // If this is not a new edge, then the priority flow graph result doesn't
+  // change.
+  if (!is_new_edge)
+    return;
+
+  auto src_node_data_it = FindOrCreateNodeData(boosting_vote->input_frame());
+  auto dst_node_data_it = FindOrCreateNodeData(boosting_vote->output_frame());
+  src_node_data_it->second.IncrementEdgeCount();
+  dst_node_data_it->second.IncrementEdgeCount();
+
+  // Update the reachability spanning tree for each priority layer.
+  NodeDataPtrSet changes;
+  for (uint32_t layer_bit = kFirstLayerBit; layer_bit <= kLastLayerBit;
+       layer_bit <<= 1) {
+    // If the source node is inactive, then there's no way this edge can be part
+    // of the spanning tree.
+    if (!src_node_data_it->second.IsActive(layer_bit))
+      continue;
+
+    // If the destination node is active, then there's no way this edge can be
+    // part of the spanning tree.
+    if (dst_node_data_it->second.IsActive(layer_bit))
+      continue;
+
+    // At this point we've added an edge from an active source node to an
+    // inactive destination node. The spanning tree needs to be extended.
+    NodeDataPtrSet active_search_front;
+    active_search_front.insert(&(*dst_node_data_it));
+    changes.insert(&(*dst_node_data_it));
+    edge_data->SetActive(layer_bit);
+    dst_node_data_it->second.SetActive(layer_bit);
+    MarkNodesActiveFromSearchFront(layer_bit, &active_search_front, &changes);
+  }
+
+  UpstreamChanges(changes);
+}
+
+void BoostingVoteAggregator::CancelBoostingVote(
+    const BoostingVote* boosting_vote) {
+  // Find the edge and remove the reason from it; it's possible that this reason
+  // was the 'active' reason associated with the edge, in which case upstream
+  // votes may need to be changed.
+  auto fwd_edge_it = forward_edges_.find(ForwardEdge(boosting_vote));
+  bool active_reason_changed =
+      fwd_edge_it->second.RemoveReason(boosting_vote->reason());
+
+  auto src_node_data_it = FindNodeData(boosting_vote->input_frame());
+  auto dst_node_data_it = FindNodeData(boosting_vote->output_frame());
+
+  // If the edge's active reason changed, and the edge is active in any layer,
+  // then the destination node might need to have its vote updated.
+  NodeDataPtrSet changes;
+  if (active_reason_changed && fwd_edge_it->second.IsActiveInAnyLayer())
+    changes.insert(&(*dst_node_data_it));
+
+  // If the reasons are now empty, then this is the last occurrence of the
+  // edge. Remove it entirely.
+  bool edge_removed = (fwd_edge_it->second.GetReasonCount() == 0);
+  if (edge_removed) {
+    // Remember the active layer information associated with this edge, as its
+    // needed to decide if the reachability spanning tree needs to be updated.
+    ActiveLayers old_edge_layers = fwd_edge_it->second;
+
+    // Delete the edge from both the forward and reverse map, updating edge
+    // counts.
+    forward_edges_.erase(fwd_edge_it);
+    auto rev_edge_it = reverse_edges_.find(ReverseEdge(boosting_vote));
+    reverse_edges_.erase(rev_edge_it);
+    src_node_data_it->second.DecrementEdgeCount();
+    dst_node_data_it->second.DecrementEdgeCount();
+
+    // Repair the reachability spanning tree for each layer.
+    for (uint32_t layer_bit = kFirstLayerBit; layer_bit <= kLastLayerBit;
+         layer_bit <<= 1) {
+      // If the edge was inactive in this layer, then the layer won't change.
+      if (!old_edge_layers.IsActive(layer_bit))
+        continue;
+
+      // The edge was active in this layer, so the reachability spanning tree
+      // needs repairing.
+      ReprocessSubtree(layer_bit, &(*dst_node_data_it), &changes);
+    }
+  }
+
+  UpstreamChanges(changes);
+
+  // Since an edge was removed the nodes might also be eligible for removal.
+  // This is done after changes are upstreamed so that votes have a chance to
+  // be canceled first.
+  if (edge_removed) {
+    MaybeRemoveNode(src_node_data_it);
+    MaybeRemoveNode(dst_node_data_it);
+  }
+}
+
+VoteReceipt BoostingVoteAggregator::SubmitVote(VoterId voter_id,
+                                               const Vote& vote) {
+  DCHECK(IsSetup());
+  DCHECK_EQ(input_voter_id_, voter_id);
+  DCHECK(vote.IsValid());
+
+  // Store the vote.
+  auto node_data_it = FindOrCreateNodeData(vote.frame_node());
+  VoteReceipt receipt =
+      node_data_it->second.SetIncomingVote(this, voter_id, vote);
+
+  NodeDataPtrSet changes;
+
+  // Update the reachability tree for the new vote if necessary.
+  if (vote.priority() != base::TaskPriority::LOWEST) {
+    uint32_t layer_bit = PriorityToBit(vote.priority());
+    OnVoteAdded(layer_bit, &(*node_data_it), &changes);
+  }
+
+  UpstreamChanges(changes);
+
+  return receipt;
+}
+
+VoteReceipt BoostingVoteAggregator::ChangeVote(VoteReceipt receipt,
+                                               AcceptedVote* old_vote,
+                                               const Vote& new_vote) {
+  // Remember the old and new priorities before committing any changes.
+  base::TaskPriority old_priority = old_vote->vote().priority();
+  base::TaskPriority new_priority = new_vote.priority();
+
+  // Update the vote in place.
+  auto node_data_it = GetNodeDataByVote(old_vote);
+  auto* node_data = &(*node_data_it);
+  old_vote->UpdateVote(new_vote);
+
+  NodeDataPtrSet changes;
+  changes.insert(node_data);  // This node is changing regardless.
+
+  // Update the reachability tree for the old vote if necessary.
+  if (old_priority != base::TaskPriority::LOWEST) {
+    uint32_t layer_bit = PriorityToBit(old_priority);
+    OnVoteRemoved(layer_bit, node_data, &changes);
+  }
+
+  // Update the reachability tree for the new vote if necessary.
+  if (new_priority != base::TaskPriority::LOWEST) {
+    uint32_t layer_bit = PriorityToBit(new_priority);
+    OnVoteAdded(layer_bit, node_data, &changes);
+  }
+
+  UpstreamChanges(changes);
+
+  // Return the original receipt, as its still valid.
+  return receipt;
+}
+
+void BoostingVoteAggregator::VoteInvalidated(AcceptedVote* vote) {
+  auto node_data_it = GetNodeDataByVote(vote);
+  base::TaskPriority old_priority = vote->vote().priority();
+
+  NodeDataPtrSet changes;
+
+  // Update the reachability tree for the old vote if necessary.
+  if (old_priority != base::TaskPriority::LOWEST) {
+    uint32_t layer_bit = PriorityToBit(old_priority);
+    OnVoteRemoved(layer_bit, &(*node_data_it), &changes);
+  }
+
+  UpstreamChanges(changes);
+
+  // The node may be eligible for removal after its incoming vote has been
+  // canceled.
+  MaybeRemoveNode(node_data_it);
+
+  return;
+}
+
+template <typename Function>
+void BoostingVoteAggregator::ForEachIncomingEdge(const FrameNode* node,
+                                                 Function&& function) {
+  auto edges_begin = reverse_edges_.lower_bound(ReverseEdge(nullptr, node));
+  auto edges_end = reverse_edges_.upper_bound(ReverseEdge(kMaxFrameNode, node));
+  for (auto edge_it = edges_begin; edge_it != edges_end; ++edge_it) {
+    if (!function(edge_it))
+      return;
+  }
+}
+
+template <typename Function>
+void BoostingVoteAggregator::ForEachOutgoingEdge(const FrameNode* node,
+                                                 Function&& function) {
+  auto edges_begin = forward_edges_.lower_bound(ForwardEdge(node, nullptr));
+  auto edges_end = forward_edges_.upper_bound(ForwardEdge(node, kMaxFrameNode));
+  for (auto edge_it = edges_begin; edge_it != edges_end; ++edge_it) {
+    if (!function(edge_it))
+      return;
+  }
+}
+
+BoostingVoteAggregator::NodeDataMap::iterator
+BoostingVoteAggregator::GetNodeDataByVote(AcceptedVote* vote) {
+  // The vote being retrieved should have us as its consumer, and have been
+  // emitted by our known voter.
+  DCHECK(vote);
+  DCHECK_EQ(this, vote->consumer());
+  DCHECK(vote->voter_id() == input_voter_id_);
+  DCHECK(IsSetup());
+
+  auto it = nodes_.find(vote->vote().frame_node());
+  DCHECK(it != nodes_.end());
+  DCHECK_EQ(vote, &it->second.incoming());
+  return it;
+}
+
+BoostingVoteAggregator::NodeDataMap::iterator
+BoostingVoteAggregator::FindOrCreateNodeData(const FrameNode* frame_node) {
+  auto it = nodes_.lower_bound(frame_node);
+  if (it->first == frame_node)
+    return it;
+  it = nodes_.insert(it, std::make_pair(frame_node, NodeData()));
+  return it;
+}
+
+BoostingVoteAggregator::NodeDataMap::iterator
+BoostingVoteAggregator::FindNodeData(const FrameNode* frame_node) {
+  auto it = nodes_.lower_bound(frame_node);
+  DCHECK(it != nodes_.end());
+  return it;
+}
+
+const char* BoostingVoteAggregator::GetVoteReason(
+    const NodeDataMap::value_type* node) {
+  const NodeData& node_data = node->second;
+  auto priority = node_data.GetEffectivePriorityLevel();
+  uint32_t layer_bit = PriorityToBit(priority);
+
+  // No reason is needed for the lowest priority.
+  if (priority == base::TaskPriority::LOWEST)
+    return nullptr;
+
+  // If a vote has been expressed for this node at the given priority level then
+  // preferentially use that reason.
+  if (node_data.HasIncomingVote()) {
+    const Vote& incoming = node_data.incoming().vote();
+    if (priority == incoming.priority()) {
+      // This node will not have an active incoming edge in this case.
+      DCHECK(GetActiveInboundEdge(layer_bit, node) == reverse_edges_.end());
+      return incoming.reason();
+    }
+  }
+
+  // Otherwise, this node has inherited its priority. Find the active incoming
+  // edge and use the active reason for that edge.
+  auto edge_it = GetActiveInboundEdge(layer_bit, node);
+  DCHECK(edge_it != reverse_edges_.end());
+  DCHECK(edge_it->second->GetReasonCount());
+  return edge_it->second->GetActiveReason();
+}
+
+void BoostingVoteAggregator::UpstreamVoteIfNeeded(
+    NodeDataMap::value_type* node) {
+  const FrameNode* frame_node = node->first;
+  NodeData* node_data = &node->second;
+  auto priority = node_data->GetEffectivePriorityLevel();
+
+  // We specifically don't upstream lowest priority votes, as that is the
+  // default priority level of every frame in the absence of any specific higher
+  // votes.
+  if (priority == base::TaskPriority::LOWEST) {
+    if (node_data->HasOutgoingVote())
+      node_data->CancelOutgoingVote();
+    return;
+  }
+
+  // Get the reason for this vote.
+  const char* reason = GetVoteReason(node);
+
+  // If the node already has a vote, then change it. This is a nop if the vote
+  // details are identical.
+  if (node_data->HasOutgoingVote()) {
+    node_data->ChangeOutgoingVote(priority, reason);
+    return;
+  }
+
+  // Create an outgoing vote.
+  node_data->SetOutgoingVoteReceipt(
+      channel_.SubmitVote(Vote(frame_node, priority, reason)));
+}
+
+void BoostingVoteAggregator::UpstreamChanges(const NodeDataPtrSet& changes) {
+  for (auto* node : changes)
+    UpstreamVoteIfNeeded(node);
+}
+
+void BoostingVoteAggregator::MaybeRemoveNode(
+    NodeDataMap::iterator node_data_it) {
+  const NodeData& node = node_data_it->second;
+  if (!node.HasEdges() && !node.HasIncomingVote())
+    nodes_.erase(node_data_it);
+}
+
+void BoostingVoteAggregator::MarkSubtreeInactive(uint32_t layer_bit,
+                                                 NodeDataMap::value_type* node,
+                                                 NodeDataPtrSet* deactivated) {
+  std::deque<NodeDataMap::value_type*> to_visit;
+  to_visit.push_back(node);
+  while (!to_visit.empty()) {
+    auto* current_node = to_visit.front();
+    to_visit.pop_front();
+    DCHECK(current_node->second.IsActive(layer_bit));
+    current_node->second.SetInactive(layer_bit);
+    deactivated->insert(current_node);
+
+    ForEachOutgoingEdge(
+        current_node->first, [&](ForwardEdges::iterator edge_it) {
+          // Skip over inactive edges.
+          if (!edge_it->second.IsActive(layer_bit))
+            return true;
+
+          // Mark the edge inactive.
+          edge_it->second.SetInactive(layer_bit);
+
+          // Schedule the node to be visited.
+          auto dst_node_data_it = FindNodeData(edge_it->first.dst());
+          to_visit.push_back(&(*dst_node_data_it));
+
+          return true;
+        });
+  }
+}
+
+BoostingVoteAggregator::ReverseEdges::iterator
+BoostingVoteAggregator::GetActiveInboundEdge(
+    uint32_t layer_bit,
+    const NodeDataMap::value_type* node) {
+  ReverseEdges::iterator active_edge = reverse_edges_.end();
+  ForEachIncomingEdge(node->first, [&](ReverseEdges::iterator edge_it) -> bool {
+    if (!edge_it->second->IsActive(layer_bit))
+      return true;
+
+    active_edge = edge_it;
+    return false;
+  });
+
+  return active_edge;
+}
+
+BoostingVoteAggregator::NodeDataMap::value_type*
+BoostingVoteAggregator::GetNearestActiveAncestor(
+    uint32_t layer_bit,
+    const NodeDataMap::value_type* deactivated_node) {
+  DCHECK(!deactivated_node->second.IsActive(layer_bit));
+
+  NodeDataMap::value_type* active_ancestor = nullptr;
+  ForEachIncomingEdge(deactivated_node->first,
+                      [&](ReverseEdges::iterator edge_it) -> bool {
+                        // None of the edges should be active, by definition.
+                        DCHECK(!edge_it->second->IsActive(layer_bit));
+                        const FrameNode* src_node = edge_it->first.src();
+                        auto src_node_it = FindNodeData(src_node);
+
+                        if (!src_node_it->second.IsActive(layer_bit))
+                          return true;
+
+                        // Return the first active immediate ancestor we
+                        // encounter.
+                        active_ancestor = &(*src_node_it);
+                        return false;
+                      });
+
+  return active_ancestor;
+}
+
+void BoostingVoteAggregator::GetNearestActiveAncestors(
+    uint32_t layer_bit,
+    const NodeDataPtrSet& deactivated,
+    NodeDataPtrSet* active_ancestors) {
+  for (const auto* node : deactivated) {
+    auto* ancestor = GetNearestActiveAncestor(layer_bit, node);
+    if (ancestor)
+      active_ancestors->insert(ancestor);
+  }
+}
+
+void BoostingVoteAggregator::MarkNodesActiveFromSearchFront(
+    uint32_t layer_bit,
+    NodeDataPtrSet* active_search_front,
+    NodeDataPtrSet* activated) {
+  while (!active_search_front->empty()) {
+    auto* current_node = *active_search_front->begin();
+    active_search_front->erase(active_search_front->begin());
+    DCHECK(current_node->second.IsActive(layer_bit));
+
+    ForEachOutgoingEdge(current_node->first,
+                        [&](ForwardEdges::iterator edge_it) -> bool {
+                          // Ignore active edges.
+                          if (edge_it->second.IsActive(layer_bit))
+                            return true;
+
+                          // Ignore active destination nodes.
+                          const FrameNode* dst_node = edge_it->first.dst();
+                          auto dst_node_it = FindNodeData(dst_node);
+                          if (dst_node_it->second.IsActive(layer_bit))
+                            return true;
+
+                          // Mark the edge and the node as active, and add the
+                          // node both to the search front and to the set of
+                          // nodes that became active as a part of this search.
+                          edge_it->second.SetActive(layer_bit);
+                          dst_node_it->second.SetActive(layer_bit);
+                          active_search_front->insert(&(*dst_node_it));
+                          activated->insert(&(*dst_node_it));
+                          return true;
+                        });
+  }
+}
+
+void BoostingVoteAggregator::ReprocessSubtree(uint32_t layer_bit,
+                                              NodeDataMap::value_type* node,
+                                              NodeDataPtrSet* changes) {
+  MarkSubtreeInactive(layer_bit, node, changes);
+
+  NodeDataPtrSet active_ancestors;
+  GetNearestActiveAncestors(layer_bit, *changes, &active_ancestors);
+
+  MarkNodesActiveFromSearchFront(layer_bit, &active_ancestors, changes);
+}
+
+void BoostingVoteAggregator::OnVoteAdded(uint32_t layer_bit,
+                                         NodeDataMap::value_type* node,
+                                         NodeDataPtrSet* changes) {
+  changes->insert(node);
+
+  // If the node is already active then it must be active due to another
+  // inbound edge. In this case the reachability spanning tree doesn't need
+  // updating, but we do deactivate the incoming edge so as to always favor the
+  // explicit vote edge (and its reason). This is guaranteed not to introduce
+  // cycles as there is no inbound edge to the imaginary "root" node from which
+  // all votes flow (ie, we're simply rehoming a subtree to hang directly off
+  // the root).
+  if (node->second.IsActive(layer_bit)) {
+    auto edge_it = GetActiveInboundEdge(layer_bit, node);
+    edge_it->second->SetInactive(layer_bit);
+    return;
+  }
+
+  // Otherwise this node is now supported by a direct vote when it wasn't
+  // previously supported by anything. Extend the reachability spanning tree.
+  NodeDataPtrSet active_search_front;
+  active_search_front.insert(node);
+  node->second.SetActive(layer_bit);
+  MarkNodesActiveFromSearchFront(layer_bit, &active_search_front, changes);
+}
+
+void BoostingVoteAggregator::OnVoteRemoved(uint32_t layer_bit,
+                                           NodeDataMap::value_type* node,
+                                           NodeDataPtrSet* changes) {
+  // The node should *not* have an active inbound edge, as we always prefer a
+  // direct vote over an inferred priority boost.
+  DCHECK(GetActiveInboundEdge(layer_bit, node) == reverse_edges_.end());
+
+  // Reprocess the subtree hanging off this node.
+  ReprocessSubtree(layer_bit, node, changes);
+}
+
+}  // namespace frame_priority
+}  // namespace performance_manager
+
+#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PUBLIC_FRAME_PRIORITY_OVERRIDE_VOTE_AGGREGATOR_H_
diff --git a/components/performance_manager/frame_priority/boosting_vote_aggregator_unittest.cc b/components/performance_manager/frame_priority/boosting_vote_aggregator_unittest.cc
new file mode 100644
index 0000000..248af97
--- /dev/null
+++ b/components/performance_manager/frame_priority/boosting_vote_aggregator_unittest.cc
@@ -0,0 +1,603 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/performance_manager/public/frame_priority/boosting_vote_aggregator.h"
+
+#include "components/performance_manager/test_support/frame_priority.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace performance_manager {
+namespace frame_priority {
+
+namespace {
+
+// Some dummy frames.
+const FrameNode* kFrame0 = reinterpret_cast<const FrameNode*>(0xF5A33000);
+const FrameNode* kFrame1 = reinterpret_cast<const FrameNode*>(0xF5A33001);
+const FrameNode* kFrame2 = reinterpret_cast<const FrameNode*>(0xF5A33002);
+const FrameNode* kFrame3 = reinterpret_cast<const FrameNode*>(0xF5A33003);
+const FrameNode* kFrame4 = reinterpret_cast<const FrameNode*>(0xF5A33004);
+
+static constexpr base::TaskPriority kPriority0 = base::TaskPriority::LOWEST;
+static constexpr base::TaskPriority kPriority1 =
+    base::TaskPriority::USER_VISIBLE;
+static constexpr base::TaskPriority kPriority2 = base::TaskPriority::HIGHEST;
+
+static_assert(kPriority0 < kPriority1 && kPriority1 < kPriority2,
+              "priorities must be well ordered");
+
+static const char kReason0[] = "a reason";
+static const char kReason1[] = "another reason";
+static const char kReason2[] = "yet another reason";
+static const char kReasonBoost[] = "boosted!";
+
+class TestBoostingVoteAggregator : public BoostingVoteAggregator {
+ public:
+  using BoostingVoteAggregator::forward_edges_;
+  using BoostingVoteAggregator::NodeData;
+  using BoostingVoteAggregator::nodes_;
+  using BoostingVoteAggregator::reverse_edges_;
+};
+
+using NodeData = TestBoostingVoteAggregator::NodeData;
+
+class BoostingVoteAggregatorTest : public testing::Test {
+ public:
+  void SetUp() override {
+    // Set up the chain such that |voter_| provides votes to |agg_|, which
+    // upstreams them to |consumer_|.
+    auto channel = consumer_.voting_channel_factory_.BuildVotingChannel();
+    voter_id_ = channel.voter_id();
+    agg_.SetUpstreamVotingChannel(std::move(channel));
+    voter_.SetVotingChannel(agg_.GetVotingChannel());
+    EXPECT_TRUE(agg_.nodes_.empty());
+    EXPECT_TRUE(agg_.forward_edges_.empty());
+    EXPECT_TRUE(agg_.reverse_edges_.empty());
+  }
+
+  void ExpectEdges(size_t count) {
+    EXPECT_EQ(count, agg_.forward_edges_.size());
+    EXPECT_EQ(count, agg_.reverse_edges_.size());
+  }
+
+  void ExpectIsActive(const NodeData& node_data,
+                      bool mid_priority,
+                      bool high_priority) {
+    EXPECT_EQ(mid_priority, node_data.IsActive(1));
+    EXPECT_EQ(high_priority, node_data.IsActive(2));
+  }
+
+  // The id of |agg_| as seen by its upstream |consumer_|.
+  VoterId voter_id_ = 0;
+  test::DummyVoteConsumer consumer_;
+  TestBoostingVoteAggregator agg_;
+  test::DummyVoter voter_;
+};
+
+}  // namespace
+
+TEST_F(BoostingVoteAggregatorTest, VotesUpstreamingWorks) {
+  // Submit a default vote to the boosting aggregator, and expect it not to be
+  // upstreamed.
+  voter_.EmitVote(kFrame0, kPriority0, kReason0);
+  EXPECT_EQ(1u, agg_.nodes_.size());
+  EXPECT_TRUE(voter_.receipts_[0].HasVote());
+  ExpectEdges(0);
+  EXPECT_TRUE(consumer_.votes_.empty());
+
+  // Submit a non-default vote to the boosting aggregator, and expect it to be
+  // upstreamed.
+  voter_.EmitVote(kFrame1, kPriority1, kReason1);
+  EXPECT_EQ(2u, agg_.nodes_.size());
+  EXPECT_TRUE(voter_.receipts_[0].HasVote());
+  EXPECT_TRUE(voter_.receipts_[1].HasVote());
+  ExpectEdges(0);
+  EXPECT_EQ(1u, consumer_.votes_.size());
+  consumer_.ExpectValidVote(0, voter_id_, kFrame1, kPriority1, kReason1);
+
+  // Make vote 0 non-default and expect it to be upstreamed.
+  voter_.receipts_[0].ChangeVote(kPriority2, kReason2);
+  EXPECT_EQ(2u, agg_.nodes_.size());
+  EXPECT_TRUE(voter_.receipts_[0].HasVote());
+  EXPECT_TRUE(voter_.receipts_[1].HasVote());
+  ExpectEdges(0);
+  EXPECT_EQ(2u, consumer_.votes_.size());
+  consumer_.ExpectValidVote(0, voter_id_, kFrame1, kPriority1, kReason1);
+  consumer_.ExpectValidVote(1, voter_id_, kFrame0, kPriority2, kReason2);
+
+  // Make vote 1 default and expect the upstream vote to be canceled.
+  voter_.receipts_[1].ChangeVote(kPriority0, kReason0);
+  EXPECT_EQ(2u, agg_.nodes_.size());
+  EXPECT_TRUE(voter_.receipts_[0].HasVote());
+  EXPECT_TRUE(voter_.receipts_[1].HasVote());
+  ExpectEdges(0);
+  EXPECT_EQ(2u, consumer_.votes_.size());
+  consumer_.ExpectInvalidVote(0);
+  consumer_.ExpectValidVote(1, voter_id_, kFrame0, kPriority2, kReason2);
+
+  // Change the reason but not the priority of vote 0 and expect the upstream
+  // vote to change as well.
+  voter_.receipts_[0].ChangeVote(kPriority2, kReason0);
+  EXPECT_EQ(2u, agg_.nodes_.size());
+  EXPECT_TRUE(voter_.receipts_[0].HasVote());
+  EXPECT_TRUE(voter_.receipts_[1].HasVote());
+  ExpectEdges(0);
+  EXPECT_EQ(2u, consumer_.votes_.size());
+  consumer_.ExpectInvalidVote(0);
+  consumer_.ExpectValidVote(1, voter_id_, kFrame0, kPriority2, kReason0);
+
+  // Cancel vote 0 and expect it to be canceled upstream.
+  voter_.receipts_[0].Reset();
+  EXPECT_EQ(1u, agg_.nodes_.size());
+  EXPECT_FALSE(voter_.receipts_[0].HasVote());
+  EXPECT_TRUE(voter_.receipts_[1].HasVote());
+  ExpectEdges(0);
+  EXPECT_EQ(2u, consumer_.votes_.size());
+  consumer_.ExpectInvalidVote(0);
+  consumer_.ExpectInvalidVote(1);
+
+  // Cancel vote 1 and expect no change to the upstream votes.
+  voter_.receipts_[1].Reset();
+  EXPECT_EQ(0u, agg_.nodes_.size());
+  EXPECT_FALSE(voter_.receipts_[0].HasVote());
+  EXPECT_FALSE(voter_.receipts_[1].HasVote());
+  ExpectEdges(0);
+  EXPECT_EQ(2u, consumer_.votes_.size());
+  consumer_.ExpectInvalidVote(0);
+  consumer_.ExpectInvalidVote(1);
+}
+
+TEST_F(BoostingVoteAggregatorTest, BoostingWorks) {
+  // Add a boosting vote, with no actual incoming votes. This should produce
+  // the two nodes associated with the edge but not upstream any votes.
+  BoostingVote boost01a(&agg_, kFrame0, kFrame1, kReasonBoost);
+  const auto& data0 = agg_.nodes_.find(kFrame0)->second;
+  const auto& data1 = agg_.nodes_.find(kFrame1)->second;
+  EXPECT_TRUE(voter_.receipts_.empty());
+  EXPECT_EQ(2u, agg_.nodes_.size());
+  ExpectEdges(1);
+  EXPECT_TRUE(consumer_.votes_.empty());
+  EXPECT_EQ(1u, data0.edge_count_for_testing());
+  EXPECT_EQ(1u, data1.edge_count_for_testing());
+  ExpectIsActive(data0, false, false);
+  ExpectIsActive(data1, false, false);
+
+  // Create a second boosting vote. This duplicates the edge.
+  BoostingVote boost01b(&agg_, kFrame0, kFrame1, kReasonBoost);
+  EXPECT_TRUE(voter_.receipts_.empty());
+  EXPECT_EQ(2u, agg_.nodes_.size());
+  ExpectEdges(1);
+  EXPECT_TRUE(consumer_.votes_.empty());
+  EXPECT_EQ(1u, data0.edge_count_for_testing());
+  EXPECT_EQ(1u, data1.edge_count_for_testing());
+  ExpectIsActive(data0, false, false);
+  ExpectIsActive(data1, false, false);
+
+  // Create a mid priority vote for frame 1. This should cause a single vote
+  // to be emitted for that frame.
+  voter_.EmitVote(kFrame1, kPriority1, kReason1);
+  EXPECT_EQ(1u, voter_.receipts_.size());
+  EXPECT_TRUE(voter_.receipts_[0].HasVote());  // kFrame1.
+  EXPECT_EQ(2u, agg_.nodes_.size());
+  ExpectEdges(1);
+  EXPECT_EQ(1u, consumer_.votes_.size());
+  consumer_.ExpectValidVote(0, voter_id_, kFrame1, kPriority1, kReason1);
+  EXPECT_EQ(1u, data0.edge_count_for_testing());
+  EXPECT_EQ(1u, data1.edge_count_for_testing());
+  ExpectIsActive(data0, false, false);
+  ExpectIsActive(data1, true, false);
+
+  // Create a mid priority vote for frame 0. This should cause another vote to
+  // be emitted.
+  voter_.EmitVote(kFrame0, kPriority1, kReason1);
+  EXPECT_EQ(2u, voter_.receipts_.size());
+  EXPECT_TRUE(voter_.receipts_[1].HasVote());  // kFrame0.
+  EXPECT_TRUE(voter_.receipts_[0].HasVote());  // kFrame1.
+  EXPECT_EQ(2u, agg_.nodes_.size());
+  ExpectEdges(1);
+  EXPECT_EQ(2u, consumer_.votes_.size());
+  consumer_.ExpectValidVote(1, voter_id_, kFrame0, kPriority1, kReason1);
+  consumer_.ExpectValidVote(0, voter_id_, kFrame1, kPriority1, kReason1);
+  EXPECT_EQ(1u, data0.edge_count_for_testing());
+  EXPECT_EQ(1u, data1.edge_count_for_testing());
+  ExpectIsActive(data0, true, false);
+  ExpectIsActive(data1, true, false);
+
+  // Cancel the priority 1 vote for frame 1. The boosting should maintain the
+  // output priority for that node.
+  voter_.receipts_[0].Reset();  // kFrame1.
+  EXPECT_EQ(2u, voter_.receipts_.size());
+  EXPECT_TRUE(voter_.receipts_[1].HasVote());   // kFrame0.
+  EXPECT_FALSE(voter_.receipts_[0].HasVote());  // Old kFrame1.
+  EXPECT_EQ(2u, agg_.nodes_.size());
+  ExpectEdges(1);
+  EXPECT_EQ(2u, consumer_.votes_.size());
+  consumer_.ExpectValidVote(1, voter_id_, kFrame0, kPriority1, kReason1);
+  consumer_.ExpectValidVote(0, voter_id_, kFrame1, kPriority1, kReasonBoost);
+  EXPECT_EQ(1u, data0.edge_count_for_testing());
+  EXPECT_EQ(1u, data1.edge_count_for_testing());
+  ExpectIsActive(data0, true, false);
+  ExpectIsActive(data1, true, false);
+
+  // Create a default vote for a third frame. Other than creating the node data
+  // and the vote this shouldn't do anything.
+  voter_.EmitVote(kFrame2, kPriority0, kReason0);
+  const auto& data2 = agg_.nodes_.find(kFrame2)->second;
+  EXPECT_EQ(3u, voter_.receipts_.size());
+  EXPECT_TRUE(voter_.receipts_[1].HasVote());   // kFrame0.
+  EXPECT_FALSE(voter_.receipts_[0].HasVote());  // Old kFrame1.
+  EXPECT_TRUE(voter_.receipts_[2].HasVote());   // kFrame2.
+  EXPECT_EQ(3u, agg_.nodes_.size());
+  ExpectEdges(1);
+  EXPECT_EQ(2u, consumer_.votes_.size());
+  consumer_.ExpectValidVote(1, voter_id_, kFrame0, kPriority1, kReason1);
+  consumer_.ExpectValidVote(0, voter_id_, kFrame1, kPriority1, kReasonBoost);
+  EXPECT_EQ(1u, data0.edge_count_for_testing());
+  EXPECT_EQ(1u, data1.edge_count_for_testing());
+  EXPECT_EQ(0u, data2.edge_count_for_testing());
+  ExpectIsActive(data0, true, false);
+  ExpectIsActive(data1, true, false);
+  ExpectIsActive(data2, false, false);
+
+  // Create a boosting vote from frame 2 to frame 0. This should create an edge.
+  BoostingVote boost20(&agg_, kFrame2, kFrame0, kReasonBoost);
+  EXPECT_EQ(3u, voter_.receipts_.size());
+  EXPECT_TRUE(voter_.receipts_[1].HasVote());   // kFrame0.
+  EXPECT_FALSE(voter_.receipts_[0].HasVote());  // Old kFrame1.
+  EXPECT_TRUE(voter_.receipts_[2].HasVote());   // kFrame2.
+  EXPECT_EQ(3u, agg_.nodes_.size());
+  ExpectEdges(2);
+  EXPECT_EQ(2u, consumer_.votes_.size());
+  consumer_.ExpectValidVote(1, voter_id_, kFrame0, kPriority1, kReason1);
+  consumer_.ExpectValidVote(0, voter_id_, kFrame1, kPriority1, kReasonBoost);
+  EXPECT_EQ(2u, data0.edge_count_for_testing());
+  EXPECT_EQ(1u, data1.edge_count_for_testing());
+  EXPECT_EQ(1u, data2.edge_count_for_testing());
+  ExpectIsActive(data0, true, false);
+  ExpectIsActive(data1, true, false);
+  ExpectIsActive(data2, false, false);
+
+  // Emit a highest priority vote for frame 2. This should boost frames 0 and
+  // 1 as well.
+  voter_.receipts_[2].ChangeVote(kPriority2, kReason2);  // kFrame2.
+  EXPECT_EQ(3u, voter_.receipts_.size());
+  EXPECT_TRUE(voter_.receipts_[1].HasVote());   // kFrame0.
+  EXPECT_FALSE(voter_.receipts_[0].HasVote());  // Old kFrame1.
+  EXPECT_TRUE(voter_.receipts_[2].HasVote());   // kFrame2.
+  EXPECT_EQ(3u, agg_.nodes_.size());
+  ExpectEdges(2);
+  EXPECT_EQ(3u, consumer_.votes_.size());
+  consumer_.ExpectValidVote(1, voter_id_, kFrame0, kPriority2, kReasonBoost);
+  consumer_.ExpectValidVote(0, voter_id_, kFrame1, kPriority2, kReasonBoost);
+  consumer_.ExpectValidVote(2, voter_id_, kFrame2, kPriority2, kReason2);
+  EXPECT_EQ(2u, data0.edge_count_for_testing());
+  EXPECT_EQ(1u, data1.edge_count_for_testing());
+  EXPECT_EQ(1u, data2.edge_count_for_testing());
+  ExpectIsActive(data0, true, true);
+  ExpectIsActive(data1, true, true);
+  ExpectIsActive(data2, false, true);
+
+  // Emit a highest priority vote for frame 1. This should change the vote
+  // reason.
+  voter_.EmitVote(kFrame1, kPriority2, kReason2);
+  EXPECT_EQ(4u, voter_.receipts_.size());
+  EXPECT_TRUE(voter_.receipts_[1].HasVote());   // kFrame0.
+  EXPECT_FALSE(voter_.receipts_[0].HasVote());  // Old kFrame1.
+  EXPECT_TRUE(voter_.receipts_[3].HasVote());   // kFrame1.
+  EXPECT_TRUE(voter_.receipts_[2].HasVote());   // kFrame2.
+  EXPECT_EQ(3u, agg_.nodes_.size());
+  ExpectEdges(2);
+  EXPECT_EQ(3u, consumer_.votes_.size());
+  consumer_.ExpectValidVote(1, voter_id_, kFrame0, kPriority2,
+                            kReasonBoost);  // kFrame0.
+  consumer_.ExpectValidVote(0, voter_id_, kFrame1, kPriority2,
+                            kReason2);  // kFrame1.
+  consumer_.ExpectValidVote(2, voter_id_, kFrame2, kPriority2,
+                            kReason2);  // kFrame2.
+  EXPECT_EQ(2u, data0.edge_count_for_testing());
+  EXPECT_EQ(1u, data1.edge_count_for_testing());
+  EXPECT_EQ(1u, data2.edge_count_for_testing());
+  ExpectIsActive(data0, true, true);
+  ExpectIsActive(data1, true, true);
+  ExpectIsActive(data2, false, true);
+
+  // Kill the vote for frame 2. This should kill the upstream vote for frame 2
+  // entirely, reduce the priority of frame 0, and keep frame 1 the same.
+  voter_.receipts_[2].Reset();  // kFrame2.
+  EXPECT_EQ(4u, voter_.receipts_.size());
+  EXPECT_TRUE(voter_.receipts_[1].HasVote());   // kFrame0.
+  EXPECT_FALSE(voter_.receipts_[0].HasVote());  // Old kFrame1.
+  EXPECT_TRUE(voter_.receipts_[3].HasVote());   // kFrame1.
+  EXPECT_FALSE(voter_.receipts_[2].HasVote());  // Old kFrame2.
+  EXPECT_EQ(3u, agg_.nodes_.size());
+  ExpectEdges(2);
+  EXPECT_EQ(3u, consumer_.votes_.size());
+  consumer_.ExpectValidVote(1, voter_id_, kFrame0, kPriority1, kReason1);
+  consumer_.ExpectValidVote(0, voter_id_, kFrame1, kPriority2, kReason2);
+  consumer_.ExpectInvalidVote(2);  // Old kFrame2.
+  EXPECT_EQ(2u, data0.edge_count_for_testing());
+  EXPECT_EQ(1u, data1.edge_count_for_testing());
+  EXPECT_EQ(1u, data2.edge_count_for_testing());
+  ExpectIsActive(data0, true, false);
+  ExpectIsActive(data1, true, true);
+  ExpectIsActive(data2, false, false);
+
+  // Kill the direct vote for frame 1 so it goes back to being boosted by
+  // frame 0.
+  voter_.receipts_[3].Reset();
+  EXPECT_EQ(4u, voter_.receipts_.size());
+  EXPECT_TRUE(voter_.receipts_[1].HasVote());   // kFrame0.
+  EXPECT_FALSE(voter_.receipts_[0].HasVote());  // Old kFrame1.
+  EXPECT_FALSE(voter_.receipts_[3].HasVote());  // Old kFrame1.
+  EXPECT_FALSE(voter_.receipts_[2].HasVote());  // Old kFrame2.
+  EXPECT_EQ(3u, agg_.nodes_.size());
+  ExpectEdges(2);
+  EXPECT_EQ(3u, consumer_.votes_.size());
+  consumer_.ExpectValidVote(1, voter_id_, kFrame0, kPriority1, kReason1);
+  consumer_.ExpectValidVote(0, voter_id_, kFrame1, kPriority1, kReasonBoost);
+  consumer_.ExpectInvalidVote(2);  // Old kFrame2.
+  EXPECT_EQ(2u, data0.edge_count_for_testing());
+  EXPECT_EQ(1u, data1.edge_count_for_testing());
+  EXPECT_EQ(1u, data2.edge_count_for_testing());
+  ExpectIsActive(data0, true, false);
+  ExpectIsActive(data1, true, false);
+  ExpectIsActive(data2, false, false);
+
+  // Kill the first boosting vote from 0 to 1. This should do nothing but change
+  // edge the multiplicity of the edge.
+  boost01a.Reset();
+  EXPECT_EQ(4u, voter_.receipts_.size());
+  EXPECT_TRUE(voter_.receipts_[1].HasVote());   // kFrame0.
+  EXPECT_FALSE(voter_.receipts_[0].HasVote());  // Old kFrame1.
+  EXPECT_FALSE(voter_.receipts_[3].HasVote());  // Old kFrame1.
+  EXPECT_FALSE(voter_.receipts_[2].HasVote());  // Old kFrame2.
+  EXPECT_EQ(3u, agg_.nodes_.size());
+  ExpectEdges(2);
+  EXPECT_EQ(3u, consumer_.votes_.size());
+  consumer_.ExpectValidVote(1, voter_id_, kFrame0, kPriority1, kReason1);
+  consumer_.ExpectValidVote(0, voter_id_, kFrame1, kPriority1, kReasonBoost);
+  consumer_.ExpectInvalidVote(2);  // Old kFrame2.
+  EXPECT_EQ(2u, data0.edge_count_for_testing());
+  EXPECT_EQ(1u, data1.edge_count_for_testing());
+  EXPECT_EQ(1u, data2.edge_count_for_testing());
+  ExpectIsActive(data0, true, false);
+  ExpectIsActive(data1, true, false);
+  ExpectIsActive(data2, false, false);
+
+  // Kill the second boosting vote from 0 to 1. This should change edge counts,
+  // and remove both the vote and the node data. The variable |data1| is now
+  // invalid.
+  boost01b.Reset();
+  EXPECT_EQ(4u, voter_.receipts_.size());
+  EXPECT_TRUE(voter_.receipts_[1].HasVote());   // kFrame0.
+  EXPECT_FALSE(voter_.receipts_[0].HasVote());  // Old kFrame1.
+  EXPECT_FALSE(voter_.receipts_[3].HasVote());  // Old kFrame1.
+  EXPECT_FALSE(voter_.receipts_[2].HasVote());  // Old kFrame2.
+  EXPECT_EQ(2u, agg_.nodes_.size());
+  ExpectEdges(1);
+  EXPECT_EQ(3u, consumer_.votes_.size());
+  consumer_.ExpectValidVote(1, voter_id_, kFrame0, kPriority1, kReason1);
+  consumer_.ExpectInvalidVote(0);  // Old kFrame1.
+  consumer_.ExpectInvalidVote(2);  // Old kFrame2.
+  EXPECT_EQ(1u, data0.edge_count_for_testing());
+  EXPECT_EQ(1u, data2.edge_count_for_testing());
+  ExpectIsActive(data0, true, false);
+  ExpectIsActive(data2, false, false);
+
+  // Move the boosting vote. The move should not cause any outwardly visible
+  // changes.
+  BoostingVote boost20b(std::move(boost20));
+  EXPECT_EQ(&agg_, boost20b.aggregator());
+  EXPECT_EQ(kFrame2, boost20b.input_frame());
+  EXPECT_EQ(kFrame0, boost20b.output_frame());
+  EXPECT_EQ(kReasonBoost, boost20b.reason());
+  EXPECT_FALSE(boost20.aggregator());
+  EXPECT_FALSE(boost20.input_frame());
+  EXPECT_FALSE(boost20.output_frame());
+  EXPECT_FALSE(boost20.reason());
+  EXPECT_EQ(4u, voter_.receipts_.size());
+  EXPECT_TRUE(voter_.receipts_[1].HasVote());   // kFrame0.
+  EXPECT_FALSE(voter_.receipts_[0].HasVote());  // Old kFrame1.
+  EXPECT_FALSE(voter_.receipts_[3].HasVote());  // Old kFrame1.
+  EXPECT_FALSE(voter_.receipts_[2].HasVote());  // Old kFrame2.
+  EXPECT_EQ(2u, agg_.nodes_.size());
+  ExpectEdges(1);
+  EXPECT_EQ(3u, consumer_.votes_.size());
+  consumer_.ExpectValidVote(1, voter_id_, kFrame0, kPriority1, kReason1);
+  consumer_.ExpectInvalidVote(0);  // Old kFrame1.
+  consumer_.ExpectInvalidVote(2);  // Old kFrame2.
+  EXPECT_EQ(1u, data0.edge_count_for_testing());
+  EXPECT_EQ(1u, data2.edge_count_for_testing());
+  ExpectIsActive(data0, true, false);
+  ExpectIsActive(data2, false, false);
+
+  // Remove the boosting vote from 2 to 0. This should change edge counts, and
+  // also remove the node data associated with node 2. |data2| is now invalid.
+  boost20b.Reset();
+  EXPECT_EQ(4u, voter_.receipts_.size());
+  EXPECT_TRUE(voter_.receipts_[1].HasVote());   // kFrame0.
+  EXPECT_FALSE(voter_.receipts_[0].HasVote());  // Old kFrame1.
+  EXPECT_FALSE(voter_.receipts_[3].HasVote());  // Old kFrame1.
+  EXPECT_FALSE(voter_.receipts_[2].HasVote());  // Old kFrame2.
+  EXPECT_EQ(1u, agg_.nodes_.size());
+  ExpectEdges(0);
+  EXPECT_EQ(3u, consumer_.votes_.size());
+  consumer_.ExpectValidVote(1, voter_id_, kFrame0, kPriority1, kReason1);
+  consumer_.ExpectInvalidVote(0);  // Old kFrame1.
+  consumer_.ExpectInvalidVote(2);  // Old kFrame2.
+  EXPECT_EQ(0u, data0.edge_count_for_testing());
+  ExpectIsActive(data0, true, false);
+
+  // Finally remove the last vote. The aggregator should effectively be empty at
+  // this point. |data0| also becomes invalid after this.
+  voter_.receipts_[1].Reset();
+  EXPECT_EQ(4u, voter_.receipts_.size());
+  EXPECT_FALSE(voter_.receipts_[1].HasVote());  // Old kFrame0.
+  EXPECT_FALSE(voter_.receipts_[0].HasVote());  // Old kFrame1.
+  EXPECT_FALSE(voter_.receipts_[3].HasVote());  // Old kFrame1.
+  EXPECT_FALSE(voter_.receipts_[2].HasVote());  // Old kFrame2.
+  EXPECT_EQ(0u, agg_.nodes_.size());
+  ExpectEdges(0);
+  EXPECT_EQ(3u, consumer_.votes_.size());
+  consumer_.ExpectInvalidVote(1);  // Old kFrame0.
+  consumer_.ExpectInvalidVote(0);  // Old kFrame1.
+  consumer_.ExpectInvalidVote(2);  // Old kFrame2.
+}
+
+TEST_F(BoostingVoteAggregatorTest, DiamondPattern) {
+  // Create a diamond boosting vote pattern:
+  //
+  //   1
+  //  / \
+  // 0   3
+  //  \ /
+  //   2
+  BoostingVote boost01(&agg_, kFrame0, kFrame1, kReasonBoost);
+  BoostingVote boost02(&agg_, kFrame0, kFrame2, kReasonBoost);
+  BoostingVote boost13(&agg_, kFrame1, kFrame3, kReasonBoost);
+  BoostingVote boost23(&agg_, kFrame2, kFrame3, kReasonBoost);
+
+  const auto& data0 = agg_.nodes_.find(kFrame0)->second;
+  const auto& data1 = agg_.nodes_.find(kFrame1)->second;
+  const auto& data2 = agg_.nodes_.find(kFrame2)->second;
+  const auto& data3 = agg_.nodes_.find(kFrame3)->second;
+  ExpectIsActive(data0, false, false);
+  ExpectIsActive(data1, false, false);
+  ExpectIsActive(data2, false, false);
+  ExpectIsActive(data3, false, false);
+
+  // Add a vote to node 0. This should cause all nodes to be boosted.
+  voter_.EmitVote(kFrame0, kPriority2, kReason2);
+  ExpectIsActive(data0, false, true);
+  ExpectIsActive(data1, false, true);
+  ExpectIsActive(data2, false, true);
+  ExpectIsActive(data3, false, true);
+
+  // Cancel the vote. All boosting should disappear.
+  voter_.receipts_.clear();
+  ExpectIsActive(data0, false, false);
+  ExpectIsActive(data1, false, false);
+  ExpectIsActive(data2, false, false);
+  ExpectIsActive(data3, false, false);
+}
+
+TEST_F(BoostingVoteAggregatorTest, DiamondPatternMultipleVotes) {
+  // Create another diamond boosting vote pattern:
+  //
+  //       1
+  //      / \
+  // 4 - 0   3
+  //      \ /
+  //       2
+  BoostingVote boost01(&agg_, kFrame0, kFrame1, kReasonBoost);
+  BoostingVote boost02(&agg_, kFrame0, kFrame2, kReasonBoost);
+  BoostingVote boost13(&agg_, kFrame1, kFrame3, kReasonBoost);
+  BoostingVote boost23(&agg_, kFrame2, kFrame3, kReasonBoost);
+
+  const auto& data0 = agg_.nodes_.find(kFrame0)->second;
+  const auto& data1 = agg_.nodes_.find(kFrame1)->second;
+  const auto& data2 = agg_.nodes_.find(kFrame2)->second;
+  const auto& data3 = agg_.nodes_.find(kFrame3)->second;
+  ExpectIsActive(data0, false, false);
+  ExpectIsActive(data1, false, false);
+  ExpectIsActive(data2, false, false);
+  ExpectIsActive(data3, false, false);
+
+  // Add a vote to node 0. This should cause all downstream nodes to be boosted.
+  voter_.EmitVote(kFrame0, kPriority2, kReason2);
+  ExpectIsActive(data0, false, true);
+  ExpectIsActive(data1, false, true);
+  ExpectIsActive(data2, false, true);
+  ExpectIsActive(data3, false, true);
+
+  // Add a lower vote to frame0 via frame4. This should also propagate through
+  // the network in a similar way.
+  BoostingVote boost40(&agg_, kFrame4, kFrame0, kReasonBoost);
+  const auto& data4 = agg_.nodes_.find(kFrame4)->second;
+  voter_.EmitVote(kFrame4, kPriority1, kReason1);
+  ExpectIsActive(data0, true, true);
+  ExpectIsActive(data1, true, true);
+  ExpectIsActive(data2, true, true);
+  ExpectIsActive(data3, true, true);
+  ExpectIsActive(data4, true, false);
+}
+
+TEST_F(BoostingVoteAggregatorTest, RemoveEdgeFromCycle) {
+  BoostingVote boost01(&agg_, kFrame0, kFrame1, kReasonBoost);
+  BoostingVote boost12(&agg_, kFrame1, kFrame2, kReasonBoost);
+  BoostingVote boost23(&agg_, kFrame2, kFrame3, kReasonBoost);
+  BoostingVote boost30(&agg_, kFrame3, kFrame0, kReasonBoost);
+
+  const auto& data0 = agg_.nodes_.find(kFrame0)->second;
+  const auto& data1 = agg_.nodes_.find(kFrame1)->second;
+  const auto& data2 = agg_.nodes_.find(kFrame2)->second;
+  const auto& data3 = agg_.nodes_.find(kFrame3)->second;
+  ExpectIsActive(data0, false, false);
+  ExpectIsActive(data1, false, false);
+  ExpectIsActive(data2, false, false);
+  ExpectIsActive(data3, false, false);
+
+  // Add a vote to node 0.
+  voter_.EmitVote(kFrame0, kPriority2, kReason2);
+  ExpectIsActive(data0, false, true);
+  ExpectIsActive(data1, false, true);
+  ExpectIsActive(data2, false, true);
+  ExpectIsActive(data3, false, true);
+
+  // Remove an edge from the cycle. The first half of the cycle should still
+  // be boosted, the second half should not.
+  boost12.Reset();
+  ExpectIsActive(data0, false, true);
+  ExpectIsActive(data1, false, true);
+  ExpectIsActive(data2, false, false);
+  ExpectIsActive(data3, false, false);
+}
+
+TEST_F(BoostingVoteAggregatorTest, MoveCancelsPreviousBoostingVote) {
+  BoostingVote boost01(&agg_, kFrame0, kFrame1, kReasonBoost);
+  BoostingVote boost12(&agg_, kFrame1, kFrame2, kReasonBoost);
+
+  // Expect nodes to have been created for all nodes involved in boosting votes.
+  EXPECT_TRUE(agg_.nodes_.count(kFrame0));
+  EXPECT_TRUE(agg_.nodes_.count(kFrame1));
+  EXPECT_TRUE(agg_.nodes_.count(kFrame2));
+
+  // Move one boosting vote into the other. This should cause the latter to be
+  // canceled. In this case that means node0 should be removed.
+  boost01 = std::move(boost12);
+  EXPECT_FALSE(agg_.nodes_.count(kFrame0));
+  EXPECT_TRUE(agg_.nodes_.count(kFrame1));
+  EXPECT_TRUE(agg_.nodes_.count(kFrame2));
+}
+
+TEST_F(BoostingVoteAggregatorTest, BoostingVoteAfterNormalVotes) {
+  voter_.EmitVote(kFrame0, kPriority2, kReason2);
+  EXPECT_TRUE(agg_.nodes_.count(kFrame0));
+  EXPECT_EQ(1u, agg_.nodes_.size());
+  const auto& data0 = agg_.nodes_.find(kFrame0)->second;
+  ExpectIsActive(data0, false, true);
+
+  BoostingVote boost12(&agg_, kFrame1, kFrame2, kReasonBoost);
+  EXPECT_TRUE(agg_.nodes_.count(kFrame0));
+  EXPECT_TRUE(agg_.nodes_.count(kFrame1));
+  EXPECT_TRUE(agg_.nodes_.count(kFrame2));
+  EXPECT_EQ(3u, agg_.nodes_.size());
+  const auto& data1 = agg_.nodes_.find(kFrame1)->second;
+  const auto& data2 = agg_.nodes_.find(kFrame2)->second;
+  ExpectIsActive(data0, false, true);
+  ExpectIsActive(data1, false, false);
+  ExpectIsActive(data2, false, false);
+
+  BoostingVote boost01(&agg_, kFrame0, kFrame1, kReasonBoost);
+  EXPECT_TRUE(agg_.nodes_.count(kFrame0));
+  EXPECT_TRUE(agg_.nodes_.count(kFrame1));
+  EXPECT_TRUE(agg_.nodes_.count(kFrame2));
+  EXPECT_EQ(3u, agg_.nodes_.size());
+  ExpectIsActive(data0, false, true);
+  ExpectIsActive(data1, false, true);
+  ExpectIsActive(data2, false, true);
+}
+
+}  // namespace frame_priority
+}  // namespace performance_manager
diff --git a/components/performance_manager/public/frame_priority/boosting_vote_aggregator.h b/components/performance_manager/public/frame_priority/boosting_vote_aggregator.h
new file mode 100644
index 0000000..e844023b
--- /dev/null
+++ b/components/performance_manager/public/frame_priority/boosting_vote_aggregator.h
@@ -0,0 +1,384 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_FRAME_PRIORITY_BOOSTING_VOTE_AGGREGATOR_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_FRAME_PRIORITY_BOOSTING_VOTE_AGGREGATOR_H_
+
+#include <map>
+#include <set>
+
+#include "base/containers/flat_map.h"
+#include "base/task/task_traits.h"
+#include "components/performance_manager/public/frame_priority/frame_priority.h"
+
+namespace performance_manager {
+namespace frame_priority {
+
+class BoostingVoteAggregator;
+
+// A BoostingVote is a special kind of relative vote that allows a voter to
+// express that "frame X should have the same or greater priority than frame Y".
+// It allows implementing priority boost semantics to avoid priority inversions
+// for access to shared resources. BoostingVotes must be registered with a
+// BoostingVoteAggregator. Similar to a VoteReceipt, they are a move-only type
+// and their vote will be removed with their destruction.
+//
+// A BoostingVote is considered "active" if it is associated with an aggregator
+// (the result of calling "aggregator()" is non-null).
+//
+// See comments in the implementation for details on how the algorithm works.
+class BoostingVote {
+ public:
+  // Registers a relative vote with the provided |aggregator|, that ensures that
+  // the priority of |output_frame| will be at least as high as that of
+  // |input_frame|.
+  BoostingVote(BoostingVoteAggregator* aggregator,
+               const FrameNode* input_frame,
+               const FrameNode* output_frame,
+               const char* reason);
+  BoostingVote(const BoostingVote& rhs) = delete;
+  BoostingVote(BoostingVote&& rhs);
+  BoostingVote& operator=(const BoostingVote& rhs) = delete;
+  BoostingVote& operator=(BoostingVote&& rhs);
+  ~BoostingVote();
+
+  BoostingVoteAggregator* aggregator() const { return aggregator_; }
+  const FrameNode* input_frame() const { return input_frame_; }
+  const FrameNode* output_frame() const { return output_frame_; }
+  const char* reason() const { return reason_; }
+
+  // Detaches this BoostingVote from its aggregator. After calling this
+  // |aggregator_| will be nullptr and the vote will no longer be active.
+  void Reset();
+
+ private:
+  BoostingVoteAggregator* aggregator_ = nullptr;
+  const FrameNode* input_frame_ = nullptr;
+  const FrameNode* output_frame_ = nullptr;
+  const char* reason_ = nullptr;
+};
+
+// The BoostingVoteAggregator allows for incoming votes to be modified via a
+// collection of registered "relative boosting votes" that express relationships
+// such as "frame X should have the same or greater priority than frame Y".
+// It is intended to serve as the root of a tree of voters and aggregators,
+// allowing priority boost semantics to be implemented. This class must outlive
+// all boosting votes registered with it.
+class BoostingVoteAggregator : public VoteConsumer {
+ public:
+  BoostingVoteAggregator();
+  ~BoostingVoteAggregator() override;
+
+  // Both of these must be called in order for the aggregator to be setup
+  // ("IsSetup" will return true). Both of these should be called exactly once.
+  VotingChannel GetVotingChannel();
+  void SetUpstreamVotingChannel(VotingChannel&& channel);
+
+  bool IsSetup() const;
+
+ protected:
+  friend class BoostingVote;
+
+  // We currently require that base::TaskPriority be zero-based, and
+  // consecutive. These static asserts ensure that we revisit this code if the
+  // base::TaskPriority enum ever changes.
+  static_assert(static_cast<int>(base::TaskPriority::LOWEST) == 0,
+                "expect 0-based priorities");
+  static_assert(static_cast<int>(base::TaskPriority::HIGHEST) == 2,
+                "expect 3 priority levels");
+
+  using NodePriorityMap = std::map<const FrameNode*, base::TaskPriority>;
+
+  // Small helper class used to endow both edges and nodes with "active" bits
+  // for each priority layer.
+  class ActiveLayers {
+   public:
+    // Returns true if any layer is active.
+    bool IsActiveInAnyLayer() const { return active_layers_ != 0; }
+
+    // Returns the "active" state of this node for the given |layer_bit|.
+    bool IsActive(uint32_t layer_bit) const;
+
+    // Sets the active state for this node in the given |layer_bit|.
+    void SetActive(uint32_t layer_bit);
+    void SetInactive(uint32_t layer_bit);
+
+   private:
+    // A bit-set corresponding to the priority layers in which this object is
+    // active.
+    uint32_t active_layers_ = 0;
+  };
+
+  // This is move-only because all of its members are move-only.
+  // An instance of this will exist for any node that is referenced, either by a
+  // direct Vote for that node, or as an input or output of a BoostedVote.
+  class NodeData : public ActiveLayers {
+   public:
+    NodeData() = default;
+    NodeData(const NodeData& rhs) = delete;
+    NodeData(NodeData&& rhs) = default;
+    NodeData& operator=(const NodeData& rhs) = delete;
+    NodeData& operator=(NodeData&& rhs) = default;
+    ~NodeData() = default;
+
+    const AcceptedVote& incoming() const { return incoming_; }
+    const VoteReceipt& receipt() const { return receipt_; }
+
+    // For modifying |incoming_|.
+    VoteReceipt SetIncomingVote(VoteConsumer* consumer,
+                                VoterId voter_id,
+                                const Vote& vote);
+    void UpdateIncomingVote(const Vote& vote) { incoming_.UpdateVote(vote); }
+
+    // For modifying |receipt_|.
+    void ChangeOutgoingVote(base::TaskPriority priority, const char* reason) {
+      receipt_.ChangeVote(priority, reason);
+    }
+    void CancelOutgoingVote() { receipt_.Reset(); }
+    void SetOutgoingVoteReceipt(VoteReceipt&& receipt) {
+      receipt_ = std::move(receipt);
+    }
+
+    // Returns true if this node has an active |incoming| vote. If false that
+    // means this node exists only because it is referenced by a BoostedVote.
+    // Same as |incoming_.IsValid()|, but more readable.
+    bool HasIncomingVote() const { return incoming_.IsValid(); }
+
+    // Returns true if this node has an active outgoing vote. Sam as
+    // |receipt_.HasVote()|, but more readable.
+    bool HasOutgoingVote() const { return receipt_.HasVote(); }
+
+    // Returns true if this node is involved in any edges.
+    bool HasEdges() const { return edge_count_ > 0; }
+
+    // Returns the effective priority of this node based on the highest of the
+    // values in |supporting_node_count_|.
+    base::TaskPriority GetEffectivePriorityLevel() const;
+
+    // For keeping track of the number of edges in which this node is involved.
+    void IncrementEdgeCount();
+    void DecrementEdgeCount();
+
+    size_t edge_count_for_testing() const { return edge_count_; }
+
+   private:
+    // Counts the number of edges involving this node, both input and output.
+    // When this goes to zero the node no longer needs an explicit
+    // representation.
+    size_t edge_count_ = 0;
+
+    // The input vote we've received, if any.
+    AcceptedVote incoming_;
+
+    // The receipt for the vote we've upstreamed, if any.
+    VoteReceipt receipt_;
+  };
+
+  // NOTE: It is important that NodeDataMap preserve pointers to NodeData
+  // through insertions and deletions in the map, as we take raw pointers to
+  // objects in the map.
+  using NodeDataMap = std::map<const FrameNode*, NodeData>;
+  using NodeDataPtrSet = std::set<NodeDataMap::value_type*>;
+
+  // For any given edge, this maintains the metadata associated with that
+  // particular edge.
+  class EdgeData : public ActiveLayers {
+   public:
+    EdgeData();
+    EdgeData(const EdgeData&) = delete;
+    EdgeData(EdgeData&&);
+    EdgeData& operator=(const EdgeData&) = delete;
+    EdgeData& operator=(EdgeData&&);
+    ~EdgeData();
+
+    // Adds a reason to the set of reasons associated with this edge.
+    void AddReason(const char* reason);
+
+    // Removes a reason from this edge. Returns true if this was the active
+    // selected reason that had been forwarded, indicating that a new reason
+    // needs to be chosen.
+    bool RemoveReason(const char* reason);
+
+    // Returns the active reason for this edge.
+    const char* GetActiveReason() const;
+
+    // Returns the total number of reasons associated with this edge. This is
+    // effectively the multiplicity of the edge in the dependency graph.
+    size_t GetReasonCount() const { return reasons_.size(); }
+
+   private:
+    // The reasons associated with this particular edge (one contribution per
+    // BoostingVote). We really don't expect many multiple edges so a vector is
+    // used to reduce allocations. This is semantically a multi-set.
+    std::vector<const char*> reasons_;
+  };
+
+  // A helper for storing edges with different sort orders. Templated so that it
+  // is strongly typed.
+  template <bool kForward>
+  class Edge {
+   public:
+    Edge(const FrameNode* src, const FrameNode* dst) : src_(src), dst_(dst) {}
+    explicit Edge(const BoostingVote* boosting_vote)
+        : src_(boosting_vote->input_frame()),
+          dst_(boosting_vote->output_frame()) {}
+    Edge(const Edge&) = default;
+    ~Edge() {}
+
+    Edge& operator=(const Edge&) = default;
+    Edge& operator=(Edge&&) = delete;
+
+    bool operator==(const Edge& rhs) const {
+      return std::tie(src_, dst_) == std::tie(rhs.src_, rhs.dst_);
+    }
+
+    bool operator!=(const Edge& rhs) const { return !(*this == rhs); }
+
+    // Forward edges sort by (src, dst), while reverse edges sort by (dst, src).
+    bool operator<(const Edge& rhs) const {
+      if (kForward)
+        return std::tie(src_, dst_) < std::tie(rhs.src_, rhs.dst_);
+      return std::tie(dst_, src_) < std::tie(rhs.dst_, rhs.src_);
+    }
+
+    const FrameNode* src() const { return src_; }
+    const FrameNode* dst() const { return dst_; }
+
+   private:
+    const FrameNode* src_ = nullptr;
+    const FrameNode* dst_ = nullptr;
+  };
+  using ForwardEdge = Edge<true>;
+  using ReverseEdge = Edge<false>;
+
+  // EdgeData is stored in the forward map, and a pointer to that data is
+  // included in the reverse edge map.
+  using ForwardEdges = std::map<ForwardEdge, EdgeData>;
+  using ReverseEdges = std::map<ReverseEdge, EdgeData*>;
+
+  // To be called by BoostingVote.
+  void SubmitBoostingVote(const BoostingVote* boosting_vote);
+  void CancelBoostingVote(const BoostingVote* boosting_vote);
+
+  // VoteConsumer implementation:
+  VoteReceipt SubmitVote(VoterId voter_id, const Vote& vote) override;
+  VoteReceipt ChangeVote(VoteReceipt receipt,
+                         AcceptedVote* old_vote,
+                         const Vote& new_vote) override;
+  void VoteInvalidated(AcceptedVote* vote) override;
+
+  // Helper functions for enumerating over incoming and outgoing edges.
+  // |function| should accept a single input parameter that is a
+  // ForwardEdge::iterator or ReverseEdge::iterator, as appropriate, and which
+  // returns a bool. Returning true indicates the iteration should continue,
+  // returning false indicates it should terminate.
+  template <typename Function>
+  void ForEachIncomingEdge(const FrameNode* node, Function&& function);
+  template <typename Function>
+  void ForEachOutgoingEdge(const FrameNode* node, Function&& function);
+
+  // Looks up the NodeData associated with the provided |vote|. The data is
+  // expected to already exist (enforced by a DCHECK).
+  NodeDataMap::iterator GetNodeDataByVote(AcceptedVote* vote);
+
+  // Finds or creates the node data associated with the given node.
+  NodeDataMap::iterator FindOrCreateNodeData(const FrameNode* frame_node);
+  NodeDataMap::iterator FindNodeData(const FrameNode* frame_node);
+
+  // Returns the vote reason that should be associated with the given
+  // node. This will preferentially select the reason that comes with a direct
+  // vote if any is present; otherwise, it will select the active reason of the
+  // active edge that causes the node itself to be active. Complexity is
+  // O(|inbound edge count| + lg |total edge count|). This can return nullptr if
+  // no non-null reasons have been provided.
+  const char* GetVoteReason(const NodeDataMap::value_type* node_data_value);
+
+  // Upstreams the vote for this |frame_node| via its associated NodeData.
+  void UpstreamVoteIfNeeded(NodeDataMap::value_type* node_data_value);
+
+  // Upstreams changes that have been made to the provided set of nodes. This
+  // takes care of deleted nodes if they no longer need to be represented in
+  // the priority flow graph.
+  void UpstreamChanges(const NodeDataPtrSet& changes);
+
+  // Helper for removing a node from the NodeDataMap.
+  void MaybeRemoveNode(NodeDataMap::iterator node_data_it);
+
+  // Marks sub-tree rooted at |node| as inactive, and returns the nodes that
+  // were deactivated in the provided output set.
+  void MarkSubtreeInactive(uint32_t layer_bit,
+                           NodeDataMap::value_type* node,
+                           NodeDataPtrSet* deactivated);
+
+  // Determines if the given node has an inbound active edge, returning an
+  // iterator to it if there is one.
+  ReverseEdges::iterator GetActiveInboundEdge(
+      uint32_t layer_bit,
+      const NodeDataMap::value_type* node);
+
+  // Gets the nearest active ancestor of a given deactivated node. Returns
+  // nullptr if there is none.
+  NodeDataMap::value_type* GetNearestActiveAncestor(
+      uint32_t layer_bit,
+      const NodeDataMap::value_type* deactivated_node);
+
+  // Given a set of inactive nodes, returns a search front corresponding to
+  // all of their nearest active ancestors.
+  void GetNearestActiveAncestors(uint32_t layer_bit,
+                                 const NodeDataPtrSet& deactivated,
+                                 NodeDataPtrSet* active_ancestors);
+
+  // Given a search front of active nodes, explores outwards from those nodes
+  // in order to generate a reachability spanning tree. Empties the
+  // |active_search_front| as the search progresses, and populates |changes|
+  // with the set of nodes that were made active as a result of the search.
+  void MarkNodesActiveFromSearchFront(uint32_t layer_bit,
+                                      NodeDataPtrSet* active_search_front,
+                                      NodeDataPtrSet* activated);
+
+  // Reprocesses the subtree rooted at the provided |node|. This is used to
+  // repair the reachability spanning tree when the active edge inbound to
+  // |node| is deleted. The set of nodes that have seen an active state toggle
+  // or a change in vote reason are stored in |changes|, for use with
+  // "UpstreamChanges".
+  void ReprocessSubtree(uint32_t layer_bit,
+                        NodeDataMap::value_type* node,
+                        NodeDataPtrSet* changes);
+
+  // Used by SubmitVote/ChangeVote and VoteInvalidated.
+  void OnVoteAdded(uint32_t layer_bit,
+                   NodeDataMap::value_type* node,
+                   NodeDataPtrSet* changes);
+  void OnVoteRemoved(uint32_t layer_bit,
+                     NodeDataMap::value_type* node,
+                     NodeDataPtrSet* changes);
+
+  // Our input voter. We'll only accept votes from this voter otherwise we'll
+  // DCHECK.
+  VoterId input_voter_id_ = kInvalidVoterId;
+
+  // Our channel for upstreaming our votes.
+  VotingChannel channel_;
+
+  // Our VotingChannelFactory for providing a VotingChannel to our input voter.
+  VotingChannelFactory factory_;
+
+  // Nodes and associated metadata in the "priority flow graph". An entry exists
+  // in this map for any node that has an active non-default vote, or for any
+  // node that is referenced by the "priority flow graph".
+  NodeDataMap nodes_;
+
+  // The collection of know BoostingVotes, describing the edges in the
+  // "priority flow graph" as adjacency lists. Nodes are stored as instances of
+  // NodeData.
+  ForwardEdges forward_edges_;
+  ReverseEdges reverse_edges_;
+
+  DISALLOW_COPY_AND_ASSIGN(BoostingVoteAggregator);
+};
+
+}  // namespace frame_priority
+}  // namespace performance_manager
+
+#endif  // COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_FRAME_PRIORITY_BOOSTING_VOTE_AGGREGATOR_H_
diff --git a/components/performance_manager/test_support/frame_priority.cc b/components/performance_manager/test_support/frame_priority.cc
index 865712f..73799c9 100644
--- a/components/performance_manager/test_support/frame_priority.cc
+++ b/components/performance_manager/test_support/frame_priority.cc
@@ -53,6 +53,13 @@
   --valid_vote_count_;
 }
 
+void DummyVoteConsumer::ExpectInvalidVote(size_t index) {
+  EXPECT_LT(index, votes_.size());
+  const AcceptedVote& accepted_vote = votes_[index];
+  EXPECT_EQ(this, accepted_vote.consumer());
+  EXPECT_FALSE(accepted_vote.IsValid());
+}
+
 void DummyVoteConsumer::ExpectValidVote(size_t index,
                                         VoterId voter_id,
                                         const FrameNode* frame_node,
diff --git a/components/performance_manager/test_support/frame_priority.h b/components/performance_manager/test_support/frame_priority.h
index 01882a95..a1a6c2c8 100644
--- a/components/performance_manager/test_support/frame_priority.h
+++ b/components/performance_manager/test_support/frame_priority.h
@@ -28,6 +28,8 @@
                          const Vote& new_vote) override;
   void VoteInvalidated(AcceptedVote* vote) override;
 
+  void ExpectInvalidVote(size_t index);
+
   // Checks that the vote at position |index| is valid, and has the
   // corresponding |voter|, |frame_node| and |priority|. If |reason| is non-null
   // then it will be validated as well.
diff --git a/components/test/data/payments/empty_parameters.js b/components/test/data/payments/empty_parameters.js
new file mode 100644
index 0000000..45e3ebc
--- /dev/null
+++ b/components/test/data/payments/empty_parameters.js
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2019 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**
+ * Queries Payment Request with some empty parameters.
+ * Regression test for: https://crbug.com/1022810
+ * @return {Promise<boolean>} - Whether a payment can be made.
+ */
+async function runTest() { // eslint-disable-line no-unused-vars
+  return new PaymentRequest([{supportedMethods: 'basic-card'}], {
+    displayItems: [],
+    id: '',
+    modifiers: [],
+    shippingOptions: [],
+    total: {
+      label: 'Subscription',
+      amount: {
+        value: '1.00',
+        currency: 'USD',
+      },
+    },
+  }).canMakePayment();
+}
diff --git a/components/test/data/payments/empty_parameters_test.html b/components/test/data/payments/empty_parameters_test.html
new file mode 100644
index 0000000..65a7665
--- /dev/null
+++ b/components/test/data/payments/empty_parameters_test.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<!--
+Copyright 2019 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<html>
+<head>
+<title>Empty Parameters Test</title>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1">
+<link rel="stylesheet" type="text/css" href="style.css">
+</head>
+<body>
+<p>Regression test for <a href="https://crbug.com/1022810">Issue 1022810</a>.</p>
+<div><button onclick="runTest()" id="runTest">Run Test</button></div>
+<script src="empty_parameters.js"></script>
+</body>
+</html>
diff --git a/components/viz/common/gpu/metal_api_proxy.mm b/components/viz/common/gpu/metal_api_proxy.mm
index 98a9629..f273a1d 100644
--- a/components/viz/common/gpu/metal_api_proxy.mm
+++ b/components/viz/common/gpu/metal_api_proxy.mm
@@ -382,6 +382,7 @@
                    dispatch_data_t,
                    error,
                    __autoreleasing NSError**)
+
 - (nullable id<MTLLibrary>)
     newLibraryWithSource:(NSString*)source
                  options:(nullable MTLCompileOptions*)options
@@ -549,6 +550,7 @@
 PROXY_METHOD1_SLOW(nullable id<MTLArgumentEncoder>,
                    newArgumentEncoderWithArguments,
                    NSArray<MTLArgumentDescriptor*>*)
+
 #if defined(MAC_OS_X_VERSION_10_14)
 PROXY_METHOD1_SLOW(nullable id<MTLTexture>,
                    newSharedTextureWithDescriptor,
@@ -573,6 +575,27 @@
 PROXY_METHOD1(nullable id<MTLSharedEvent>,
               newSharedEventWithHandle,
               MTLSharedEventHandle*)
-#endif
+#endif  // MAC_OS_X_VERSION_10_14
+
+#if defined(MAC_OS_X_VERSION_10_15)
+PROXY_METHOD0(BOOL, hasUnifiedMemory)
+PROXY_METHOD0(MTLDeviceLocation, location)
+PROXY_METHOD0(NSUInteger, locationNumber)
+PROXY_METHOD0(uint64_t, maxTransferRate)
+PROXY_METHOD0(BOOL, areBarycentricCoordsSupported)
+PROXY_METHOD0(BOOL, supportsShaderBarycentricCoordinates)
+PROXY_METHOD0(uint64_t, peerGroupID)
+PROXY_METHOD0(uint32_t, peerIndex)
+PROXY_METHOD0(uint32_t, peerCount)
+PROXY_METHOD0(nullable NSArray<id<MTLCounterSet>>*, counterSets)
+PROXY_METHOD1(BOOL, supportsFamily, MTLGPUFamily)
+PROXY_METHOD2_SLOW(nullable id<MTLCounterSampleBuffer>,
+                   newCounterSampleBufferWithDescriptor,
+                   MTLCounterSampleBufferDescriptor*,
+                   error,
+                   NSError**)
+PROXY_METHOD2(void, sampleTimestamps, NSUInteger*, gpuTimestamp, NSUInteger*)
+#endif  // MAC_OS_X_VERSION_10_15
+
 #pragma clang diagnostic pop
 @end
diff --git a/content/browser/accessibility/accessibility_auralinux_browsertest.cc b/content/browser/accessibility/accessibility_auralinux_browsertest.cc
index 91b3500..c40504b 100644
--- a/content/browser/accessibility/accessibility_auralinux_browsertest.cc
+++ b/content/browser/accessibility/accessibility_auralinux_browsertest.cc
@@ -186,6 +186,61 @@
 }
 
 IN_PROC_BROWSER_TEST_F(AccessibilityAuraLinuxBrowserTest,
+                       TestMultilingualTextAtOffsetWithBoundaryCharacter) {
+  AtkText* atk_text = SetUpInputField();
+  ASSERT_NE(nullptr, atk_text);
+  AccessibilityNotificationWaiter waiter(shell()->web_contents(),
+                                         ui::kAXModeComplete,
+                                         ax::mojom::Event::kValueChanged);
+  // Place an e acute, and two emoticons in the text field.
+  ExecuteScript(base::UTF8ToUTF16(R"SCRIPT(
+      const input = document.querySelector('input');
+      input.value =
+          'e\u0301\uD83D\uDC69\u200D\u2764\uFE0F\u200D\uD83D\uDC69\uD83D\uDC36';
+      )SCRIPT"));
+  waiter.WaitForNotification();
+
+  int character_count = atk_text_get_character_count(atk_text);
+  // "character_count" is the number of actual characters, not the number of
+  // UTF16 code units.
+  //
+  // Currently, this doesn't properly count grapheme clusters, but it does
+  // handle surrogate pairs.
+  // TODO(nektar): Implement support for base::OffsetAdjuster in AXPosition.
+  ASSERT_EQ(9, character_count);
+
+  // The expected text consists of an e acute, and two emoticons, but
+  // not every multi-byte character is a surrogate pair.
+  CheckTextAtOffset(atk_text, 0, ATK_TEXT_BOUNDARY_CHAR, 0, 2,
+                    base::WideToUTF8(L"e\x0301").c_str());
+  CheckTextAtOffset(atk_text, 1, ATK_TEXT_BOUNDARY_CHAR, 0, 2,
+                    base::WideToUTF8(L"e\x0301").c_str());
+  CheckTextAtOffset(atk_text, 2, ATK_TEXT_BOUNDARY_CHAR, 2, 8,
+                    "\xF0\x9F\x91\xA9\xE2\x80\x8D\xE2\x9D\xA4\xEF\xB8\x8F\xE2"
+                    "\x80\x8D\xF0\x9F\x91\xA9");
+  CheckTextAtOffset(atk_text, 3, ATK_TEXT_BOUNDARY_CHAR, 2, 8,
+                    "\xF0\x9F\x91\xA9\xE2\x80\x8D\xE2\x9D\xA4\xEF\xB8\x8F\xE2"
+                    "\x80\x8D\xF0\x9F\x91\xA9");
+  CheckTextAtOffset(atk_text, 4, ATK_TEXT_BOUNDARY_CHAR, 2, 8,
+                    "\xF0\x9F\x91\xA9\xE2\x80\x8D\xE2\x9D\xA4\xEF\xB8\x8F\xE2"
+                    "\x80\x8D\xF0\x9F\x91\xA9");
+  CheckTextAtOffset(atk_text, 5, ATK_TEXT_BOUNDARY_CHAR, 2, 8,
+                    "\xF0\x9F\x91\xA9\xE2\x80\x8D\xE2\x9D\xA4\xEF\xB8\x8F\xE2"
+                    "\x80\x8D\xF0\x9F\x91\xA9");
+  CheckTextAtOffset(atk_text, 6, ATK_TEXT_BOUNDARY_CHAR, 2, 8,
+                    "\xF0\x9F\x91\xA9\xE2\x80\x8D\xE2\x9D\xA4\xEF\xB8\x8F\xE2"
+                    "\x80\x8D\xF0\x9F\x91\xA9");
+  CheckTextAtOffset(atk_text, 7, ATK_TEXT_BOUNDARY_CHAR, 2, 8,
+                    "\xF0\x9F\x91\xA9\xE2\x80\x8D\xE2\x9D\xA4\xEF\xB8\x8F\xE2"
+                    "\x80\x8D\xF0\x9F\x91\xA9");
+  CheckTextAtOffset(atk_text, 8, ATK_TEXT_BOUNDARY_CHAR, 8, 9,
+                    "\xF0\x9F\x90\xB6");
+  CheckTextAtOffset(atk_text, 9, ATK_TEXT_BOUNDARY_CHAR, 0, 0, nullptr);
+
+  g_object_unref(atk_text);
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityAuraLinuxBrowserTest,
                        TestTextAtOffsetWithBoundaryLine) {
   AtkText* atk_text = SetUpInputField();
 
diff --git a/content/browser/accessibility/accessibility_win_browsertest.cc b/content/browser/accessibility/accessibility_win_browsertest.cc
index af04bfe..61b0d9f 100644
--- a/content/browser/accessibility/accessibility_win_browsertest.cc
+++ b/content/browser/accessibility/accessibility_win_browsertest.cc
@@ -5,6 +5,7 @@
 #include <objbase.h>
 #include <stddef.h>
 #include <stdint.h>
+#include <string.h>
 #include <wrl/client.h>
 
 #include <memory>
@@ -3065,6 +3066,51 @@
 }
 
 IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
+                       TestMultilingualTextAtOffsetWithBoundaryCharacter) {
+  Microsoft::WRL::ComPtr<IAccessibleText> input_text;
+  SetUpInputField(&input_text);
+  AccessibilityNotificationWaiter waiter(shell()->web_contents(),
+                                         ui::kAXModeComplete,
+                                         ax::mojom::Event::kValueChanged);
+  // Place an e acute, and two emoticons in the text field.
+  ExecuteScript(base::UTF8ToUTF16(R"SCRIPT(
+      const input = document.querySelector('input');
+      input.value =
+          'e\u0301\uD83D\uDC69\u200D\u2764\uFE0F\u200D\uD83D\uDC69\uD83D\uDC36';
+      )SCRIPT"));
+  waiter.WaitForNotification();
+
+  LONG n_characters;
+  ASSERT_HRESULT_SUCCEEDED(input_text->get_nCharacters(&n_characters));
+  // "n_characters" is the number of valid text offsets.
+  //
+  // Ordinarily, the number of valid text offsets should equal the number of
+  // actual characters which are only three in this case. However, this is
+  // harder to implement given our current UTF16-based representation of IA2
+  // hyptertext.
+  // TODO(nektar): Implement support for base::OffsetAdjuster in AXPosition.
+  ASSERT_EQ(12, n_characters);
+
+  // The expected text consists of an e acute, and two emoticons.
+  const std::vector<std::wstring> expected_text = {
+      L"e\x0301", L"\xD83D\xDC69\x200D\x2764\xFE0F\x200D\xD83D\xDC69",
+      L"\xD83D\xDC36"};
+  LONG offset = 0;
+  for (const std::wstring& expected_character : expected_text) {
+    LONG expected_start_offset = offset;
+    LONG expected_end_offset =
+        expected_start_offset + LONG{expected_character.length()};
+    for (size_t code_unit_offset = 0;
+         code_unit_offset < expected_character.length(); ++code_unit_offset) {
+      CheckTextAtOffset(input_text, offset, IA2_TEXT_BOUNDARY_CHAR,
+                        expected_start_offset, expected_end_offset,
+                        expected_character);
+      ++offset;
+    }
+  }
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
                        TestTextAtOffsetWithBoundaryWord) {
   Microsoft::WRL::ComPtr<IAccessibleText> input_text;
   SetUpInputField(&input_text);
diff --git a/content/browser/accessibility/ax_platform_node_textrangeprovider_win_browsertest.cc b/content/browser/accessibility/ax_platform_node_textrangeprovider_win_browsertest.cc
index 5dccd2f..54281f8 100644
--- a/content/browser/accessibility/ax_platform_node_textrangeprovider_win_browsertest.cc
+++ b/content/browser/accessibility/ax_platform_node_textrangeprovider_win_browsertest.cc
@@ -1723,6 +1723,30 @@
 }
 
 IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest,
+                       EntireMarkupSuccessiveMoveByCharacter) {
+  AssertMoveByUnitForMarkup(
+      TextUnit_Character, "Test ing.",
+      {L"T", L"e", L"s", L"t", L" ", L"i", L"n", L"g", L"."});
+
+  // The text consists of an e acute, and two emoticons.
+  const std::string html = R"HTML(<!DOCTYPE html>
+      <html>
+        <body>
+          <input type="text" value="">
+          <script>
+            document.querySelector('input').value = 'e\u0301' +
+                '\uD83D\uDC69\u200D\u2764\uFE0F\u200D\uD83D\uDC69' +
+                '\uD83D\uDC36';
+          </script>
+        </body>
+      </html>)HTML";
+  AssertMoveByUnitForMarkup(
+      TextUnit_Character, html,
+      {L"e\x0301", L"\xD83D\xDC69\x200D\x2764\xFE0F\x200D\xD83D\xDC69",
+       L"\xD83D\xDC36"});
+}
+
+IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest,
                        EntireMarkupSuccessiveMoveByWord) {
   AssertMoveByUnitForMarkup(TextUnit_Word, "this is a test.",
                             {L"this ", L"is ", L"a ", L"test."});
diff --git a/content/browser/accessibility/browser_accessibility.cc b/content/browser/accessibility/browser_accessibility.cc
index f30b96b..ddf44cab9 100644
--- a/content/browser/accessibility/browser_accessibility.cc
+++ b/content/browser/accessibility/browser_accessibility.cc
@@ -1373,13 +1373,8 @@
   // On Windows and Linux ATK, it is standard text navigation behavior to stop
   // if we are searching in the backwards direction and the current position is
   // already at the required text boundary.
-  if (direction == ui::AXTextBoundaryDirection::kBackwards) {
-    // AXPosition disallows ui::AXBoundaryBehavior::StopIfAlreadyAtBoundary when
-    // used on character boundaries because it would be non-sensical.
-    if (boundary == ui::AXTextBoundary::kCharacter)
-      return offset;
+  if (direction == ui::AXTextBoundaryDirection::kBackwards)
     boundary_behavior = ui::AXBoundaryBehavior::StopIfAlreadyAtBoundary;
-  }
 
   return GetBoundaryTextOffsetInsideBaseAnchor(
       direction, position,
diff --git a/content/browser/back_forward_cache_browsertest.cc b/content/browser/back_forward_cache_browsertest.cc
index 5d17142c..4b10657 100644
--- a/content/browser/back_forward_cache_browsertest.cc
+++ b/content/browser/back_forward_cache_browsertest.cc
@@ -3800,34 +3800,4 @@
       FROM_HERE);
 }
 
-IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, WebMidiNotCached) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-  GURL url_a(embedded_test_server()->GetURL("/title1.html"));
-  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
-
-  // 1) Navigate to A.
-  ASSERT_TRUE(NavigateToURL(shell(), url_a));
-  RenderFrameHostImpl* rfh_a = current_frame_host();
-  RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
-
-  // - Wait until requestMIDIAccess() promise is resolved.
-  EXPECT_TRUE(ExecJs(rfh_a, "navigator.requestMIDIAccess()"));
-
-  // 2) Navigate to B.
-  ASSERT_TRUE(NavigateToURL(shell(), url_b));
-
-  // - Page A should not be in the cache.
-  EXPECT_TRUE(delete_observer_rfh_a.deleted());
-
-  // 3) Go back.
-  web_contents()->GetController().GoBack();
-  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
-  ExpectNotRestored(
-      {BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
-      FROM_HERE);
-  ExpectBlocklistedFeature(
-      blink::scheduler::WebSchedulerTrackedFeature::kRequestedMIDIPermission,
-      FROM_HERE);
-}
-
 }  // namespace content
diff --git a/content/browser/frame_host/frame_tree.cc b/content/browser/frame_host/frame_tree.cc
index 579d5d4..b0bae88 100644
--- a/content/browser/frame_host/frame_tree.cc
+++ b/content/browser/frame_host/frame_tree.cc
@@ -272,6 +272,17 @@
     }
   }
 
+  // Check whether we're in an inner delegate and |site_instance| corresponds
+  // to the outer delegate.  Subframe proxies aren't needed if this is the
+  // case.
+  bool is_site_instance_for_outer_delegate = false;
+  RenderFrameProxyHost* outer_delegate_proxy =
+      root()->render_manager()->GetProxyToOuterDelegate();
+  if (outer_delegate_proxy) {
+    is_site_instance_for_outer_delegate =
+        (site_instance == outer_delegate_proxy->GetSiteInstance());
+  }
+
   // Proxies are created in the FrameTree in response to a node navigating to a
   // new SiteInstance. Since |source|'s navigation will replace the currently
   // loaded document, the entire subtree under |source| will be removed, and
@@ -299,6 +310,14 @@
         // race described above not possible.
         continue;
       }
+
+      // Do not create proxies for subframes in the outer delegate's
+      // SiteInstance, since there is no need to expose these subframes to the
+      // outer delegate.  See also comments in CreateProxiesForChildFrame() and
+      // https://crbug.com/1013553.
+      if (!node->IsMainFrame() && is_site_instance_for_outer_delegate)
+        continue;
+
       node->render_manager()->CreateRenderFrameProxy(site_instance);
     }
   }
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h
index f8924d67..0df4b5d 100644
--- a/content/browser/frame_host/render_frame_host_impl.h
+++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -995,10 +995,6 @@
                               const url::Origin& source_origin,
                               const base::Optional<url::Origin>& target_origin);
 
-  // mojom::FrameHost:
-  void VisibilityChanged(blink::mojom::FrameVisibility) override;
-  void DidChangeThemeColor(const base::Optional<SkColor>& theme_color) override;
-
   blink::mojom::FrameVisibility visibility() const { return visibility_; }
 
   // A CommitCallbackInterceptor is used to modify parameters for or cancel a
@@ -1206,6 +1202,10 @@
   void DidDisplayInsecureContent() override;
   void DidContainInsecureFormAction() override;
   void SetNeedsOcclusionTracking(bool needs_tracking) override;
+  void LifecycleStateChanged(blink::mojom::FrameLifecycleState state) override;
+  void EvictFromBackForwardCache() override;
+  void VisibilityChanged(blink::mojom::FrameVisibility) override;
+  void DidChangeThemeColor(const base::Optional<SkColor>& theme_color) override;
 
  protected:
   friend class RenderFrameHostFactory;
@@ -1457,7 +1457,6 @@
           validated_params,
       mojom::DidCommitProvisionalLoadInterfaceParamsPtr interface_params)
       override;
-  void EvictFromBackForwardCache() override;
 
   // This function mimics DidCommitProvisionalLoad but is a direct mojo
   // callback from NavigationClient::CommitNavigation.
@@ -1494,7 +1493,6 @@
   void CancelInitialHistoryLoad() override;
   void UpdateEncoding(const std::string& encoding) override;
   void FrameSizeChanged(const gfx::Size& frame_size) override;
-  void LifecycleStateChanged(blink::mojom::FrameLifecycleState state) override;
   void DocumentOnLoadCompleted() override;
   void UpdateActiveSchedulerTrackedFeatures(uint64_t features_mask) override;
   void DidAddMessageToConsole(blink::mojom::ConsoleMessageLevel log_level,
diff --git a/content/browser/indexed_db/leveldb/transactional_leveldb_iterator.cc b/content/browser/indexed_db/leveldb/transactional_leveldb_iterator.cc
index 08f109b..4ebc9b35 100644
--- a/content/browser/indexed_db/leveldb/transactional_leveldb_iterator.cc
+++ b/content/browser/indexed_db/leveldb/transactional_leveldb_iterator.cc
@@ -146,11 +146,14 @@
   if (!s.ok())
     return s;
 
-  // Exit early if not valid.
+  // If invalid, that means the current key has been deleted AND it was at the
+  // end of the database. In this case, seeking to the last item is the same as
+  // 'Prev'-ing from the deleted item.
   if (!IsValid())
-    return WrappedIteratorStatus();
+    iterator_->SeekToLast();
+  else
+    iterator_->Prev();
 
-  iterator_->Prev();
   PrevPastScopesMetadata();
   return WrappedIteratorStatus();
 }
diff --git a/content/browser/indexed_db/leveldb/transactional_leveldb_transaction_unittest.cc b/content/browser/indexed_db/leveldb/transactional_leveldb_transaction_unittest.cc
index 5679199f..b767202 100644
--- a/content/browser/indexed_db/leveldb/transactional_leveldb_transaction_unittest.cc
+++ b/content/browser/indexed_db/leveldb/transactional_leveldb_transaction_unittest.cc
@@ -924,4 +924,74 @@
   EXPECT_TRUE(KeysEqual(it->Key(), key2)) << it->Key() << ", " << key2;
 }
 
+TEST_F(TransactionalLevelDBTransactionTest,
+       IteratorPrevAfterRemovingCurrentKeyAtDatabaseEnd) {
+  SetUpRealDatabase();
+  SetupLevelDBDatabase();
+
+  // This tests that the iterator reloading logic correctly handles not calling
+  // Next when it reloads after the current key was removed.
+
+  const std::string key1("b-key1");
+  const std::string key2("b-key2");
+  const std::string value("value");
+
+  Put(key1, value);
+  Put(key2, value);
+
+  scoped_refptr<TransactionalLevelDBTransaction> transaction =
+      CreateTransaction();
+  std::unique_ptr<TransactionalLevelDBIterator> it =
+      transaction->CreateIterator();
+
+  leveldb::Status s = it->Seek(std::string("b-key2"));
+  ASSERT_TRUE(it->IsValid());
+  EXPECT_TRUE(s.ok());
+
+  // Make sure the iterator is detached, and remove the current key.
+  it->EvictLevelDBIterator();
+  TransactionRemove(transaction.get(), key2);
+
+  // This call reloads the iterator at key "b-key2", which is now deleted. It
+  // should seek to the end of the database instead, which is "b-key1"
+  s = it->Prev();
+  ASSERT_TRUE(it->IsValid());
+  EXPECT_TRUE(s.ok());
+  EXPECT_TRUE(KeysEqual(it->Key(), key1)) << it->Key() << ", " << key1;
+}
+
+TEST_F(TransactionalLevelDBTransactionTest,
+       IteratorPrevAfterRemovingCurrentKeyAtDatabaseStart) {
+  SetUpRealDatabase();
+  SetupLevelDBDatabase();
+
+  // This tests that the iterator reloading logic correctly handles not calling
+  // Next when it reloads after the current key was removed.
+
+  const std::string key1("b-key1");
+  const std::string key2("b-key2");
+  const std::string value("value");
+
+  Put(key1, value);
+  Put(key2, value);
+
+  scoped_refptr<TransactionalLevelDBTransaction> transaction =
+      CreateTransaction();
+  std::unique_ptr<TransactionalLevelDBIterator> it =
+      transaction->CreateIterator();
+
+  leveldb::Status s = it->Seek(std::string("b-key1"));
+  ASSERT_TRUE(it->IsValid());
+  EXPECT_TRUE(s.ok());
+
+  // Make sure the iterator is detached, and remove the current key.
+  it->EvictLevelDBIterator();
+  TransactionRemove(transaction.get(), key1);
+
+  // This call reloads the iterator at key "b-key1", which is now deleted. Since
+  // there is no key before it, it should be invalid.
+  s = it->Prev();
+  ASSERT_FALSE(it->IsValid());
+}
+
 }  // namespace content
diff --git a/content/browser/media/cdm_registry_impl_unittest.cc b/content/browser/media/cdm_registry_impl_unittest.cc
index e3e197b..e919de1 100644
--- a/content/browser/media/cdm_registry_impl_unittest.cc
+++ b/content/browser/media/cdm_registry_impl_unittest.cc
@@ -25,7 +25,7 @@
 namespace {
 
 using VideoCodec = media::VideoCodec;
-using EncryptionMode = media::EncryptionMode;
+using EncryptionScheme = media::EncryptionScheme;
 using CdmSessionType = media::CdmSessionType;
 using CdmProxy = media::CdmProxy;
 
@@ -76,7 +76,7 @@
         kTestCdmName, kTestCdmGuid, base::Version(kVersion1),
         base::FilePath::FromUTF8Unsafe(kTestPath), kTestFileSystemId,
         CdmCapability(
-            {media::kCodecVP8, media::kCodecVP9}, {EncryptionMode::kCenc},
+            {media::kCodecVP8, media::kCodecVP9}, {EncryptionScheme::kCenc},
             {CdmSessionType::kTemporary, CdmSessionType::kPersistentLicense},
             {CdmProxy::Protocol::kIntel}),
         kTestKeySystem, /*supports_sub_key_systems=*/true);
@@ -118,7 +118,7 @@
   EXPECT_EQ(kTestPath, cdm.path.MaybeAsASCII());
   EXPECT_EQ(kTestFileSystemId, cdm.file_system_id);
   EXPECT_VIDEO_CODECS(VideoCodec::kCodecVP8, VideoCodec::kCodecVP9);
-  EXPECT_ENCRYPTION_SCHEMES(EncryptionMode::kCenc);
+  EXPECT_ENCRYPTION_SCHEMES(EncryptionScheme::kCenc);
   EXPECT_SESSION_TYPES(CdmSessionType::kTemporary,
                        CdmSessionType::kPersistentLicense);
   EXPECT_CDM_PROXY_PROTOCOLS(CdmProxy::Protocol::kIntel);
@@ -172,14 +172,14 @@
 
 TEST_F(CdmRegistryImplTest, SupportedEncryptionSchemes) {
   auto cdm_info = GetTestCdmInfo();
-  cdm_info.capability.encryption_schemes = {EncryptionMode::kCenc,
-                                            EncryptionMode::kCbcs};
+  cdm_info.capability.encryption_schemes = {EncryptionScheme::kCenc,
+                                            EncryptionScheme::kCbcs};
   Register(cdm_info);
 
   std::vector<CdmInfo> cdms = cdm_registry_.GetAllRegisteredCdms();
   ASSERT_EQ(1u, cdms.size());
   const CdmInfo& cdm = cdms[0];
-  EXPECT_ENCRYPTION_SCHEMES(EncryptionMode::kCenc, EncryptionMode::kCbcs);
+  EXPECT_ENCRYPTION_SCHEMES(EncryptionScheme::kCenc, EncryptionScheme::kCbcs);
 }
 
 }  // namespace content
diff --git a/content/browser/media/key_system_support_impl.cc b/content/browser/media/key_system_support_impl.cc
index 854ddd9c..93de83d 100644
--- a/content/browser/media/key_system_support_impl.cc
+++ b/content/browser/media/key_system_support_impl.cc
@@ -45,7 +45,7 @@
 // be modified.
 bool IsHardwareSecureCodecsOverriddenFromCommandLine(
     std::vector<media::VideoCodec>* video_codecs,
-    std::vector<media::EncryptionMode>* encryption_schemes) {
+    std::vector<media::EncryptionScheme>* encryption_schemes) {
   DCHECK(video_codecs->empty());
   DCHECK(encryption_schemes->empty());
 
@@ -73,7 +73,7 @@
 
   // Codecs enabled from command line assumes CENC support.
   if (!video_codecs->empty())
-    encryption_schemes->push_back(media::EncryptionMode::kCenc);
+    encryption_schemes->push_back(media::EncryptionScheme::kCenc);
 
   return true;
 }
@@ -82,7 +82,7 @@
     const std::string& key_system,
     const base::flat_set<media::CdmProxy::Protocol>& cdm_proxy_protocols,
     std::vector<media::VideoCodec>* video_codecs,
-    std::vector<media::EncryptionMode>* encryption_schemes) {
+    std::vector<media::EncryptionScheme>* encryption_schemes) {
   DCHECK(video_codecs->empty());
   DCHECK(encryption_schemes->empty());
 
@@ -122,7 +122,7 @@
 #endif
 
   base::flat_set<media::VideoCodec> video_codec_set;
-  base::flat_set<media::EncryptionMode> encryption_scheme_set;
+  base::flat_set<media::EncryptionScheme> encryption_scheme_set;
 
   GetContentClient()->browser()->GetHardwareSecureDecryptionCaps(
       key_system, cdm_proxy_protocols, &video_codec_set,
diff --git a/content/browser/media/key_system_support_impl_unittest.cc b/content/browser/media/key_system_support_impl_unittest.cc
index 660f21a..e687ae11 100644
--- a/content/browser/media/key_system_support_impl_unittest.cc
+++ b/content/browser/media/key_system_support_impl_unittest.cc
@@ -25,7 +25,7 @@
 namespace {
 
 using VideoCodec = media::VideoCodec;
-using EncryptionMode = media::EncryptionMode;
+using EncryptionScheme = media::EncryptionScheme;
 using CdmSessionType = media::CdmSessionType;
 
 const base::Token kTestCdmGuid{1234, 5678};
@@ -68,7 +68,7 @@
   CdmCapability GetTestCdmCapability() {
     return CdmCapability(
         {VideoCodec::kCodecVP8, VideoCodec::kCodecVP9},
-        {EncryptionMode::kCenc, EncryptionMode::kCbcs},
+        {EncryptionScheme::kCenc, EncryptionScheme::kCbcs},
         {CdmSessionType::kTemporary, CdmSessionType::kPersistentLicense}, {});
   }
 
@@ -114,7 +114,7 @@
 
   EXPECT_TRUE(IsSupported("KeySystem2"));
   EXPECT_VIDEO_CODECS(VideoCodec::kCodecVP8, VideoCodec::kCodecVP9);
-  EXPECT_ENCRYPTION_SCHEMES(EncryptionMode::kCenc, EncryptionMode::kCbcs);
+  EXPECT_ENCRYPTION_SCHEMES(EncryptionScheme::kCenc, EncryptionScheme::kCbcs);
   EXPECT_SESSION_TYPES(CdmSessionType::kTemporary,
                        CdmSessionType::kPersistentLicense);
 }
diff --git a/content/browser/renderer_host/media/service_video_capture_device_launcher.cc b/content/browser/renderer_host/media/service_video_capture_device_launcher.cc
index dd15f1d9..019a8f0 100644
--- a/content/browser/renderer_host/media/service_video_capture_device_launcher.cc
+++ b/content/browser/renderer_host/media/service_video_capture_device_launcher.cc
@@ -19,6 +19,7 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "services/video_capture/public/cpp/receiver_media_to_mojo_adapter.h"
+#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
 
 namespace content {
 
@@ -118,7 +119,8 @@
           std::make_unique<media::VideoFrameReceiverOnTaskRunner>(
               std::move(receiver),
               base::CreateSingleThreadTaskRunner({BrowserThread::IO})));
-  mojo::PendingRemote<video_capture::mojom::Receiver> pending_remote_proxy;
+  mojo::PendingRemote<video_capture::mojom::VideoFrameHandler>
+      pending_remote_proxy;
   mojo::MakeSelfOwnedReceiver(
       std::move(receiver_adapter),
       pending_remote_proxy.InitWithNewPipeAndPassReceiver());
diff --git a/content/browser/renderer_host/media/service_video_capture_device_launcher_unittest.cc b/content/browser/renderer_host/media/service_video_capture_device_launcher_unittest.cc
index f621c16..a6d96dfa 100644
--- a/content/browser/renderer_host/media/service_video_capture_device_launcher_unittest.cc
+++ b/content/browser/renderer_host/media/service_video_capture_device_launcher_unittest.cc
@@ -19,6 +19,7 @@
 #include "services/video_capture/public/cpp/mock_push_subscription.h"
 #include "services/video_capture/public/cpp/mock_video_source.h"
 #include "services/video_capture/public/cpp/mock_video_source_provider.h"
+#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -94,15 +95,15 @@
 
     ON_CALL(mock_source_, DoCreatePushSubscription(_, _, _, _, _))
         .WillByDefault(Invoke(
-            [this](
-                mojo::PendingRemote<video_capture::mojom::Receiver> subscriber,
-                const media::VideoCaptureParams& requested_settings,
-                bool force_reopen_with_new_settings,
-                mojo::PendingReceiver<
-                    video_capture::mojom::PushVideoStreamSubscription>
-                    subscription,
-                video_capture::mojom::VideoSource::
-                    CreatePushSubscriptionCallback& callback) {
+            [this](mojo::PendingRemote<video_capture::mojom::VideoFrameHandler>
+                       subscriber,
+                   const media::VideoCaptureParams& requested_settings,
+                   bool force_reopen_with_new_settings,
+                   mojo::PendingReceiver<
+                       video_capture::mojom::PushVideoStreamSubscription>
+                       subscription,
+                   video_capture::mojom::VideoSource::
+                       CreatePushSubscriptionCallback& callback) {
               subscription_receivers_.Add(&mock_subscription_,
                                           std::move(subscription));
               std::move(callback).Run(
@@ -205,7 +206,8 @@
       .WillOnce(Invoke(
           [&create_push_subscription_success_answer_cb, &step_1_run_loop,
            service_result_code](
-              mojo::PendingRemote<video_capture::mojom::Receiver> subscriber,
+              mojo::PendingRemote<video_capture::mojom::VideoFrameHandler>
+                  subscriber,
               const media::VideoCaptureParams& requested_settings,
               bool force_reopen_with_new_settings,
               mojo::PendingReceiver<
@@ -261,7 +263,8 @@
 
   EXPECT_CALL(mock_source_, DoCreatePushSubscription(_, _, _, _, _))
       .WillOnce(Invoke(
-          [](mojo::PendingRemote<video_capture::mojom::Receiver> subscriber,
+          [](mojo::PendingRemote<video_capture::mojom::VideoFrameHandler>
+                 subscriber,
              const media::VideoCaptureParams& requested_settings,
              bool force_reopen_with_new_settings,
              mojo::PendingReceiver<
@@ -274,8 +277,8 @@
             base::ThreadTaskRunnerHandle::Get()->PostTask(
                 FROM_HERE,
                 base::BindOnce(
-                    [](mojo::PendingRemote<video_capture::mojom::Receiver>
-                           subscriber,
+                    [](mojo::PendingRemote<
+                           video_capture::mojom::VideoFrameHandler> subscriber,
                        const media::VideoCaptureParams& requested_settings,
                        mojo::PendingReceiver<
                            video_capture::mojom::PushVideoStreamSubscription>
@@ -342,7 +345,8 @@
   EXPECT_CALL(mock_source_, DoCreatePushSubscription(_, _, _, _, _))
       .WillOnce(Invoke(
           [&create_subscription_cb](
-              mojo::PendingRemote<video_capture::mojom::Receiver> subscriber,
+              mojo::PendingRemote<video_capture::mojom::VideoFrameHandler>
+                  subscriber,
               const media::VideoCaptureParams& requested_settings,
               bool force_reopen_with_new_settings,
               mojo::PendingReceiver<
diff --git a/content/browser/renderer_host/media/service_video_capture_provider_unittest.cc b/content/browser/renderer_host/media/service_video_capture_provider_unittest.cc
index 880a793..5f6d392 100644
--- a/content/browser/renderer_host/media/service_video_capture_provider_unittest.cc
+++ b/content/browser/renderer_host/media/service_video_capture_provider_unittest.cc
@@ -25,6 +25,7 @@
 #include "services/video_capture/public/cpp/mock_video_source_provider.h"
 #include "services/video_capture/public/mojom/producer.mojom.h"
 #include "services/video_capture/public/mojom/video_capture_service.mojom.h"
+#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -106,15 +107,15 @@
 
     ON_CALL(mock_source_, DoCreatePushSubscription(_, _, _, _, _))
         .WillByDefault(Invoke(
-            [this](
-                mojo::PendingRemote<video_capture::mojom::Receiver> subscriber,
-                const media::VideoCaptureParams& requested_settings,
-                bool force_reopen_with_new_settings,
-                mojo::PendingReceiver<
-                    video_capture::mojom::PushVideoStreamSubscription>
-                    subscription,
-                video_capture::mojom::VideoSource::
-                    CreatePushSubscriptionCallback& callback) {
+            [this](mojo::PendingRemote<video_capture::mojom::VideoFrameHandler>
+                       subscriber,
+                   const media::VideoCaptureParams& requested_settings,
+                   bool force_reopen_with_new_settings,
+                   mojo::PendingReceiver<
+                       video_capture::mojom::PushVideoStreamSubscription>
+                       subscription,
+                   video_capture::mojom::VideoSource::
+                       CreatePushSubscriptionCallback& callback) {
               subscription_receivers_.Add(&mock_subscription_,
                                           std::move(subscription));
               std::move(callback).Run(
diff --git a/content/browser/webrtc/webrtc_video_capture_shared_device_browsertest.cc b/content/browser/webrtc/webrtc_video_capture_shared_device_browsertest.cc
index 6929ccc..7b1a57ae 100644
--- a/content/browser/webrtc/webrtc_video_capture_shared_device_browsertest.cc
+++ b/content/browser/webrtc/webrtc_video_capture_shared_device_browsertest.cc
@@ -15,9 +15,10 @@
 #include "media/base/media_switches.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
-#include "services/video_capture/public/cpp/mock_receiver.h"
+#include "services/video_capture/public/cpp/mock_video_frame_handler.h"
 #include "services/video_capture/public/mojom/device.mojom.h"
 #include "services/video_capture/public/mojom/device_factory.mojom.h"
+#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
 #include "services/video_capture/public/mojom/video_source.mojom.h"
 #include "services/video_capture/public/mojom/video_source_provider.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -125,12 +126,14 @@
   void Initialize() {
     DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
     main_task_runner_ = base::ThreadTaskRunnerHandle::Get();
-    mock_receiver_ = std::make_unique<video_capture::MockReceiver>(
-        subscriber_.InitWithNewPipeAndPassReceiver());
+    mock_video_frame_handler_ =
+        std::make_unique<video_capture::MockVideoFrameHandler>(
+            subscriber_.InitWithNewPipeAndPassReceiver());
   }
 
   scoped_refptr<base::TaskRunner> main_task_runner_;
-  std::unique_ptr<video_capture::MockReceiver> mock_receiver_;
+  std::unique_ptr<video_capture::MockVideoFrameHandler>
+      mock_video_frame_handler_;
 
  private:
   void OnDeviceInfosReceived(
@@ -203,7 +206,7 @@
   mojo::Remote<video_capture::mojom::VideoSource> video_source_;
   mojo::Remote<video_capture::mojom::PushVideoStreamSubscription> subscription_;
 
-  mojo::PendingRemote<video_capture::mojom::Receiver> subscriber_;
+  mojo::PendingRemote<video_capture::mojom::VideoFrameHandler> subscriber_;
   base::WeakPtrFactory<WebRtcVideoCaptureSharedDeviceBrowserTest> weak_factory_{
       this};
 
@@ -220,13 +223,13 @@
 
   base::RunLoop receive_frame_from_service_wait_loop;
   auto expected_buffer_handle_tag = GetParam().GetExpectedBufferHandleTag();
-  ON_CALL(*mock_receiver_, DoOnNewBuffer(_, _))
+  ON_CALL(*mock_video_frame_handler_, DoOnNewBuffer(_, _))
       .WillByDefault(Invoke(
           [expected_buffer_handle_tag](
               int32_t, media::mojom::VideoBufferHandlePtr* buffer_handle) {
             ASSERT_EQ(expected_buffer_handle_tag, (*buffer_handle)->which());
           }));
-  EXPECT_CALL(*mock_receiver_, DoOnFrameReadyInBuffer(_, _, _, _))
+  EXPECT_CALL(*mock_video_frame_handler_, DoOnFrameReadyInBuffer(_, _, _, _))
       .WillOnce(InvokeWithoutArgs([&receive_frame_from_service_wait_loop]() {
         receive_frame_from_service_wait_loop.Quit();
       }))
@@ -235,7 +238,8 @@
   OpenDeviceViaService();
   // Note, if we do not wait for the first frame to arrive before opening the
   // device in the Renderer, it could happen that the Renderer takes ove access
-  // to the device before a first frame is received by mock_receiver_.
+  // to the device before a first frame is received by
+  // mock_video_frame_handler_.
   receive_frame_from_service_wait_loop.Run();
 
   OpenDeviceInRendererAndWaitForPlaying();
@@ -251,13 +255,13 @@
 
   base::RunLoop receive_frame_from_service_wait_loop;
   auto expected_buffer_handle_tag = GetParam().GetExpectedBufferHandleTag();
-  ON_CALL(*mock_receiver_, DoOnNewBuffer(_, _))
+  ON_CALL(*mock_video_frame_handler_, DoOnNewBuffer(_, _))
       .WillByDefault(Invoke(
           [expected_buffer_handle_tag](
               int32_t, media::mojom::VideoBufferHandlePtr* buffer_handle) {
             ASSERT_EQ(expected_buffer_handle_tag, (*buffer_handle)->which());
           }));
-  EXPECT_CALL(*mock_receiver_, DoOnFrameReadyInBuffer(_, _, _, _))
+  EXPECT_CALL(*mock_video_frame_handler_, DoOnFrameReadyInBuffer(_, _, _, _))
       .WillOnce(InvokeWithoutArgs([&receive_frame_from_service_wait_loop]() {
         receive_frame_from_service_wait_loop.Quit();
       }))
diff --git a/content/common/frame.mojom b/content/common/frame.mojom
index e062802..a92a595 100644
--- a/content/common/frame.mojom
+++ b/content/common/frame.mojom
@@ -22,7 +22,6 @@
 import "services/network/public/mojom/url_loader_factory.mojom";
 import "services/service_manager/public/mojom/interface_provider.mojom";
 import "services/viz/public/mojom/compositing/surface_id.mojom";
-import "skia/public/mojom/skcolor.mojom";
 import "third_party/blink/public/mojom/blob/blob_url_store.mojom";
 import "third_party/blink/public/mojom/commit_result/commit_result.mojom";
 import "third_party/blink/public/mojom/devtools/console_message.mojom";
@@ -513,7 +512,4 @@
 
   // Sent by the renderer when the frame becomes focused.
   FrameFocused();
-
-  // Notifies the browser that the current frame has changed theme color.
-  DidChangeThemeColor(skia.mojom.SkColor? theme_color);
 };
diff --git a/content/common/media/cdm_info.cc b/content/common/media/cdm_info.cc
index b1844062..c5480ec 100644
--- a/content/common/media/cdm_info.cc
+++ b/content/common/media/cdm_info.cc
@@ -12,7 +12,7 @@
 
 CdmCapability::CdmCapability(
     std::vector<media::VideoCodec> video_codecs,
-    base::flat_set<media::EncryptionMode> encryption_schemes,
+    base::flat_set<media::EncryptionScheme> encryption_schemes,
     base::flat_set<media::CdmSessionType> session_types,
     base::flat_set<media::CdmProxy::Protocol> cdm_proxy_protocols)
     : video_codecs(std::move(video_codecs)),
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index b50b87e4..227d4d91 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -230,7 +230,7 @@
     const std::string& key_system,
     const base::flat_set<media::CdmProxy::Protocol>& cdm_proxy_protocols,
     base::flat_set<media::VideoCodec>* video_codecs,
-    base::flat_set<media::EncryptionMode>* encryption_schemes) {}
+    base::flat_set<media::EncryptionScheme>* encryption_schemes) {}
 
 bool ContentBrowserClient::ShouldAssignSiteForURL(const GURL& url) {
   return true;
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 3ff6710..f1c1cacb 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -87,7 +87,7 @@
 namespace media {
 class AudioLogFactory;
 class AudioManager;
-enum class EncryptionMode;
+enum class EncryptionScheme;
 }  // namespace media
 
 namespace network {
@@ -1130,7 +1130,7 @@
       const std::string& key_system,
       const base::flat_set<media::CdmProxy::Protocol>& cdm_proxy_protocols,
       base::flat_set<media::VideoCodec>* video_codecs,
-      base::flat_set<media::EncryptionMode>* encryption_schemes);
+      base::flat_set<media::EncryptionScheme>* encryption_schemes);
 
   // Populates |mappings| with all files that need to be mapped before launching
   // a child process.
diff --git a/content/public/common/cdm_info.h b/content/public/common/cdm_info.h
index 39b8da2..f8bcf527 100644
--- a/content/public/common/cdm_info.h
+++ b/content/public/common/cdm_info.h
@@ -14,9 +14,7 @@
 #include "base/version.h"
 #include "content/common/content_export.h"
 #include "media/base/content_decryption_module.h"
-// TODO(crbug.com/825041): Move EncryptionMode out of decrypt_config and
-// rename it to EncryptionScheme.
-#include "media/base/decrypt_config.h"
+#include "media/base/encryption_scheme.h"
 #include "media/base/video_codecs.h"
 #include "media/cdm/cdm_proxy.h"
 
@@ -26,7 +24,7 @@
 struct CONTENT_EXPORT CdmCapability {
   CdmCapability();
   CdmCapability(std::vector<media::VideoCodec> video_codecs,
-                base::flat_set<media::EncryptionMode> encryption_schemes,
+                base::flat_set<media::EncryptionScheme> encryption_schemes,
                 base::flat_set<media::CdmSessionType> session_types,
                 base::flat_set<media::CdmProxy::Protocol> cdm_proxy_protocols);
   CdmCapability(const CdmCapability& other);
@@ -46,7 +44,7 @@
   bool supports_vp9_profile2 = false;
 
   // List of encryption schemes supported by the CDM (e.g. cenc).
-  base::flat_set<media::EncryptionMode> encryption_schemes;
+  base::flat_set<media::EncryptionScheme> encryption_schemes;
 
   // List of session types supported by the CDM.
   base::flat_set<media::CdmSessionType> session_types;
diff --git a/content/renderer/pepper/video_decoder_shim.cc b/content/renderer/pepper/video_decoder_shim.cc
index 55886b2..48f18297 100644
--- a/content/renderer/pepper/video_decoder_shim.cc
+++ b/content/renderer/pepper/video_decoder_shim.cc
@@ -895,7 +895,7 @@
       gfx::Size(32, 24),  // Small sizes that won't fail.
       gfx::Rect(32, 24), gfx::Size(32, 24),
       // TODO(bbudge): Verify extra data isn't needed.
-      media::EmptyExtraData(), media::Unencrypted());
+      media::EmptyExtraData(), media::EncryptionScheme::kUnencrypted);
 
   media_task_runner_->PostTask(
       FROM_HERE, base::BindOnce(&VideoDecoderShim::DecoderImpl::Initialize,
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 4d83bfa..0860a47a 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -4837,13 +4837,6 @@
   render_view_->StartNavStateSyncTimerIfNecessary(this);
 }
 
-void RenderFrameImpl::DidChangeThemeColor() {
-  if (frame_->Parent())
-    return;
-
-  GetFrameHost()->DidChangeThemeColor(frame_->GetDocument().ThemeColor());
-}
-
 void RenderFrameImpl::ForwardResourceTimingToParent(
     const blink::WebResourceTimingInfo& info) {
   Send(new FrameHostMsg_ForwardResourceTimingToParent(
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index 07d02f7..b265913c 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -739,7 +739,6 @@
                                        blink::WebHistoryCommitType commit_type,
                                        bool content_initiated) override;
   void DidUpdateCurrentHistoryItem() override;
-  void DidChangeThemeColor() override;
   void ForwardResourceTimingToParent(
       const blink::WebResourceTimingInfo& info) override;
   void DispatchLoad() override;
diff --git a/content/test/data/accessibility/aria/aria-tree-discontinuous-expected-uia-win7.txt b/content/test/data/accessibility/aria/aria-tree-discontinuous-expected-uia-win7.txt
new file mode 100644
index 0000000..583c4bb
--- /dev/null
+++ b/content/test/data/accessibility/aria/aria-tree-discontinuous-expected-uia-win7.txt
@@ -0,0 +1,5 @@
+document
+++tree Selection.CanSelectMultiple=false Selection.IsSelectionRequired=false
+++++treeitem Name='card content' ExpandCollapse.ExpandCollapseState='LeafNode' SelectionItem.IsSelected=false
+++++group
+++++treeitem Name='card content' ExpandCollapse.ExpandCollapseState='LeafNode' SelectionItem.IsSelected=false
\ No newline at end of file
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
index 97fe0a6..314eb2a 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
@@ -396,6 +396,9 @@
 crbug.com/965209 [ mac nvidia-0xfe9 ] conformance2/samplers/multi-context-sampler-test.html [ RetryOnFailure ]
 crbug.com/756537 [ mac nvidia ] deqp/functional/gles3/shaderoperator/* [ Failure ]
 crbug.com/907599 [ mac nvidia debug ] conformance/textures/video/tex-2d-rgb-rgb-unsigned_short_5_6_5.html [ RetryOnFailure ]
+crbug.com/1022831 [ mac nvidia-0xfe9 ] deqp/functional/gles3/vertexarrays/single_attribute.usage.stream_draw.html [ RetryOnFailure ]
+crbug.com/1022831 [ mac nvidia-0xfe9 ] deqp/functional/gles3/vertexarrays/single_attribute.usage.dynamic_copy.html [ RetryOnFailure ]
+crbug.com/1022831 [ mac nvidia-0xfe9 ] deqp/functional/gles3/shaderloop_while.html [ RetryOnFailure ]
 
 # Mac AMD
 # TODO(kbr): uncomment the following two exepectations after test
diff --git a/content/test/test_render_frame.cc b/content/test/test_render_frame.cc
index c4d7a49..446aedc 100644
--- a/content/test/test_render_frame.cc
+++ b/content/test/test_render_frame.cc
@@ -194,9 +194,6 @@
   void UpdateUserGestureCarryoverInfo() override {}
 #endif
 
-  void DidChangeThemeColor(
-      const base::Optional<::SkColor>& theme_color) override {}
-
  private:
   std::unique_ptr<FrameHostMsg_DidCommitProvisionalLoad_Params>
       last_commit_params_;
diff --git a/device/vr/android/gvr/gvr_device.cc b/device/vr/android/gvr/gvr_device.cc
index c1c7e59..b793ff71 100644
--- a/device/vr/android/gvr/gvr_device.cc
+++ b/device/vr/android/gvr/gvr_device.cc
@@ -32,12 +32,11 @@
 
 namespace {
 
-// Default downscale factor for computing the recommended WebVR/WebXR
+// Default downscale factor for computing the recommended WebXR
 // render_width/render_height from the 1:1 pixel mapped size. Using a rather
 // aggressive downscale due to the high overhead of copying pixels
 // twice before handing off to GVR. For comparison, the polyfill
 // uses approximately 0.55 on a Pixel XL.
-static constexpr float kWebVrRecommendedResolutionScale = 0.5;
 static constexpr float kWebXrRecommendedResolutionScale = 0.7;
 
 // The scale factor for WebXR on devices that don't have shared buffer
@@ -107,15 +106,6 @@
 
   device->id = device_id;
 
-  device->capabilities = mojom::VRDisplayCapabilities::New();
-  device->capabilities->has_position = false;
-  device->capabilities->has_external_display = false;
-  device->capabilities->can_present = true;
-
-  std::string vendor = gvr_api->GetViewerVendor();
-  std::string model = gvr_api->GetViewerModel();
-  device->display_name = vendor + " " + model;
-
   gvr::BufferViewportList gvr_buffer_viewports =
       gvr_api->CreateEmptyBufferViewportList();
   gvr_buffer_viewports.SetToRecommendedBufferViewports();
@@ -135,7 +125,6 @@
     device->webxr_default_framebuffer_scale =
         kWebXrNoSharedBufferResolutionScale;
   }
-  device->webvr_default_framebuffer_scale = kWebVrRecommendedResolutionScale;
 
   return device;
 }
@@ -272,12 +261,6 @@
   }
 }
 
-void GvrDevice::EnsureInitialized(EnsureInitializedCallback callback) {
-  Init(base::BindOnce([](EnsureInitializedCallback callback,
-                         bool) { std::move(callback).Run(); },
-                      std::move(callback)));
-}
-
 GvrDelegateProvider* GvrDevice::GetGvrDelegateProvider() {
   // GvrDelegateProviderFactory::Create() may return a different
   // pointer each time. Do not cache it.
diff --git a/device/vr/android/gvr/gvr_device.h b/device/vr/android/gvr/gvr_device.h
index e44b9cb..1098092 100644
--- a/device/vr/android/gvr/gvr_device.h
+++ b/device/vr/android/gvr/gvr_device.h
@@ -31,7 +31,6 @@
       mojom::XRRuntime::RequestSessionCallback callback) override;
   void PauseTracking() override;
   void ResumeTracking() override;
-  void EnsureInitialized(EnsureInitializedCallback callback) override;
   void ShutdownSession(mojom::XRRuntime::ShutdownSessionCallback) override;
 
   void OnDisplayConfigurationChanged(
diff --git a/device/vr/oculus/oculus_device.cc b/device/vr/oculus/oculus_device.cc
index eccbfbb..88930eb 100644
--- a/device/vr/oculus/oculus_device.cc
+++ b/device/vr/oculus/oculus_device.cc
@@ -61,13 +61,6 @@
                                             ovrSession session) {
   mojom::VRDisplayInfoPtr display_info = mojom::VRDisplayInfo::New();
   display_info->id = id;
-  display_info->display_name = std::string("Oculus");
-  display_info->capabilities = mojom::VRDisplayCapabilities::New();
-  display_info->capabilities->has_position = true;
-  display_info->capabilities->has_external_display = true;
-  display_info->capabilities->can_present = true;
-  display_info->webvr_default_framebuffer_scale = 1.0;
-  display_info->webxr_default_framebuffer_scale = 1.0;
 
   ovrHmdDesc hmdDesc = ovr_GetHmdDesc(session);
   display_info->left_eye = GetEyeDetails(session, hmdDesc, ovrEye_Left);
@@ -170,11 +163,6 @@
   outstanding_session_requests_count_++;
 }
 
-void OculusDevice::EnsureInitialized(EnsureInitializedCallback callback) {
-  EnsureValidDisplayInfo();
-  std::move(callback).Run();
-}
-
 bool OculusDevice::EnsureValidDisplayInfo() {
   // Ensure we have had a valid display_info set at least once.
   if (!have_real_display_info_) {
diff --git a/device/vr/oculus/oculus_device.h b/device/vr/oculus/oculus_device.h
index 66c6210..87c1db7 100644
--- a/device/vr/oculus/oculus_device.h
+++ b/device/vr/oculus/oculus_device.h
@@ -35,7 +35,6 @@
   void RequestSession(
       mojom::XRRuntimeSessionOptionsPtr options,
       mojom::XRRuntime::RequestSessionCallback callback) override;
-  void EnsureInitialized(EnsureInitializedCallback callback) override;
   void OnRequestSessionResult(mojom::XRRuntime::RequestSessionCallback callback,
                               bool result,
                               mojom::XRSessionPtr session);
diff --git a/device/vr/openvr/openvr_device.cc b/device/vr/openvr/openvr_device.cc
index 64af13f..62be26b02 100644
--- a/device/vr/openvr/openvr_device.cc
+++ b/device/vr/openvr/openvr_device.cc
@@ -72,15 +72,6 @@
                                             device::mojom::XRDeviceId id) {
   mojom::VRDisplayInfoPtr display_info = mojom::VRDisplayInfo::New();
   display_info->id = id;
-  display_info->display_name =
-      GetOpenVRString(vr_system, vr::Prop_ManufacturerName_String) + " " +
-      GetOpenVRString(vr_system, vr::Prop_ModelNumber_String);
-  display_info->capabilities = mojom::VRDisplayCapabilities::New();
-  display_info->capabilities->has_position = true;
-  display_info->capabilities->has_external_display = true;
-  display_info->capabilities->can_present = true;
-  display_info->webvr_default_framebuffer_scale = 1.0;
-  display_info->webxr_default_framebuffer_scale = 1.0;
 
   display_info->left_eye = mojom::VREyeParameters::New();
   display_info->right_eye = mojom::VREyeParameters::New();
@@ -200,11 +191,6 @@
   outstanding_session_requests_count_++;
 }
 
-void OpenVRDevice::EnsureInitialized(EnsureInitializedCallback callback) {
-  EnsureValidDisplayInfo();
-  std::move(callback).Run();
-}
-
 bool OpenVRDevice::EnsureValidDisplayInfo() {
   // Ensure we have had a valid display_info set at least once.
   if (!have_real_display_info_) {
diff --git a/device/vr/openvr/openvr_device.h b/device/vr/openvr/openvr_device.h
index 3741025..67bca0b 100644
--- a/device/vr/openvr/openvr_device.h
+++ b/device/vr/openvr/openvr_device.h
@@ -38,7 +38,6 @@
   void RequestSession(
       mojom::XRRuntimeSessionOptionsPtr options,
       mojom::XRRuntime::RequestSessionCallback callback) override;
-  void EnsureInitialized(EnsureInitializedCallback callback) override;
 
   void OnPollingEvents();
 
diff --git a/device/vr/openxr/openxr_api_wrapper.cc b/device/vr/openxr/openxr_api_wrapper.cc
index 4135a3a..98c8ef4 100644
--- a/device/vr/openxr/openxr_api_wrapper.cc
+++ b/device/vr/openxr/openxr_api_wrapper.cc
@@ -21,8 +21,6 @@
 namespace device {
 
 namespace {
-
-constexpr char kDefaultRuntimeName[] = "OpenXR";
 constexpr XrSystemId kInvalidSystem = -1;
 // Only supported view configuration:
 constexpr XrViewConfigurationType kSupportedViewConfiguration =
@@ -499,18 +497,6 @@
   return xr_result;
 }
 
-bool OpenXrApiWrapper::HasPosition() const {
-  DCHECK(IsInitialized());
-
-  XrSystemProperties system_properties = {XR_TYPE_SYSTEM_PROPERTIES};
-  if (XR_SUCCEEDED(
-          xrGetSystemProperties(instance_, system_, &system_properties))) {
-    return system_properties.trackingProperties.positionTracking;
-  }
-
-  return false;
-}
-
 // Returns the next predicted display time in nanoseconds.
 XrTime OpenXrApiWrapper::GetPredictedDisplayTime() const {
   DCHECK(IsInitialized());
@@ -652,17 +638,6 @@
       ->recommendedSwapchainSampleCount;
 }
 
-std::string OpenXrApiWrapper::GetRuntimeName() const {
-  DCHECK(HasInstance());
-
-  XrInstanceProperties instance_properties = {XR_TYPE_INSTANCE_PROPERTIES};
-  if (XR_SUCCEEDED(xrGetInstanceProperties(instance_, &instance_properties))) {
-    return instance_properties.runtimeName;
-  } else {
-    return kDefaultRuntimeName;
-  }
-}
-
 // stage bounds is fixed unless we received event
 // XrEventDataReferenceSpaceChangePending
 XrResult OpenXrApiWrapper::UpdateStageBounds() {
diff --git a/device/vr/openxr/openxr_api_wrapper.h b/device/vr/openxr/openxr_api_wrapper.h
index 092c2f041..f9766f0 100644
--- a/device/vr/openxr/openxr_api_wrapper.h
+++ b/device/vr/openxr/openxr_api_wrapper.h
@@ -53,11 +53,9 @@
                        base::Optional<gfx::Point3F>* position) const;
   void GetHeadFromEyes(XrView* left, XrView* right) const;
 
-  bool HasPosition() const;
   gfx::Size GetViewSize() const;
   XrTime GetPredictedDisplayTime() const;
   XrResult GetLuid(LUID* luid) const;
-  std::string GetRuntimeName() const;
   bool GetStageParameters(XrExtent2Df* stage_bounds,
                           gfx::Transform* local_from_stage);
   void RegisterInteractionProfileChangeCallback(
diff --git a/device/vr/openxr/openxr_device.cc b/device/vr/openxr/openxr_device.cc
index f86d5336..f49d47b 100644
--- a/device/vr/openxr/openxr_device.cc
+++ b/device/vr/openxr/openxr_device.cc
@@ -16,13 +16,6 @@
 
 namespace {
 
-constexpr char kDisplayName[] = "OpenXR";
-
-constexpr bool kHasPosition = true;
-constexpr bool kHasExternalDisplay = true;
-constexpr bool kCanPresent = true;
-
-constexpr float kFramebufferScale = 1.0f;
 constexpr float kFov = 45.0f;
 
 constexpr unsigned int kRenderWidth = 1024;
@@ -38,15 +31,6 @@
   mojom::VRDisplayInfoPtr display_info = mojom::VRDisplayInfo::New();
 
   display_info->id = id;
-  display_info->display_name = kDisplayName;
-
-  display_info->capabilities = mojom::VRDisplayCapabilities::New();
-  display_info->capabilities->has_position = kHasPosition;
-  display_info->capabilities->has_external_display = kHasExternalDisplay;
-  display_info->capabilities->can_present = kCanPresent;
-
-  display_info->webvr_default_framebuffer_scale = kFramebufferScale;
-  display_info->webxr_default_framebuffer_scale = kFramebufferScale;
 
   display_info->left_eye = mojom::VREyeParameters::New();
   display_info->right_eye = mojom::VREyeParameters::New();
diff --git a/device/vr/openxr/openxr_render_loop.cc b/device/vr/openxr/openxr_render_loop.cc
index 34b2b4c..c49f012b 100644
--- a/device/vr/openxr/openxr_render_loop.cc
+++ b/device/vr/openxr/openxr_render_loop.cc
@@ -150,22 +150,6 @@
   }
 
   current_display_info_->id = device::mojom::XRDeviceId::OPENXR_DEVICE_ID;
-  current_display_info_->display_name = openxr_->GetRuntimeName();
-
-  current_display_info_->capabilities = mojom::VRDisplayCapabilities::New();
-  current_display_info_->capabilities->can_provide_environment_integration =
-      false;
-  current_display_info_->capabilities->has_position = openxr_->HasPosition();
-
-  // OpenXR is initialized when creating the instance and getting the system
-  // was successful. If we are able to get a system, then we can present to
-  // an external display.
-  current_display_info_->capabilities->has_external_display =
-      openxr_->IsInitialized();
-  current_display_info_->capabilities->can_present = openxr_->IsInitialized();
-
-  current_display_info_->webvr_default_framebuffer_scale = 1.0f;
-  current_display_info_->webxr_default_framebuffer_scale = 1.0f;
 
   gfx::Size view_size = openxr_->GetViewSize();
   current_display_info_->left_eye->render_width = view_size.width();
diff --git a/device/vr/orientation/orientation_device.cc b/device/vr/orientation/orientation_device.cc
index 201100d..cb959d2 100644
--- a/device/vr/orientation/orientation_device.cc
+++ b/device/vr/orientation/orientation_device.cc
@@ -28,16 +28,8 @@
 static constexpr int kDefaultPumpFrequencyHz = 60;
 
 mojom::VRDisplayInfoPtr CreateVRDisplayInfo(mojom::XRDeviceId id) {
-  static const char DEVICE_NAME[] = "VR Orientation Device";
-
   mojom::VRDisplayInfoPtr display_info = mojom::VRDisplayInfo::New();
   display_info->id = id;
-  display_info->display_name = DEVICE_NAME;
-  display_info->capabilities = mojom::VRDisplayCapabilities::New();
-  display_info->capabilities->has_position = false;
-  display_info->capabilities->has_external_display = false;
-  display_info->capabilities->can_present = false;
-
   return display_info;
 }
 
diff --git a/device/vr/public/mojom/isolated_xr_service.mojom b/device/vr/public/mojom/isolated_xr_service.mojom
index 163c9b4..53ae6eb0 100644
--- a/device/vr/public/mojom/isolated_xr_service.mojom
+++ b/device/vr/public/mojom/isolated_xr_service.mojom
@@ -73,14 +73,6 @@
       pending_associated_remote<XRRuntimeEventListener> listener) =>
           (VRDisplayInfo? display_info);
 
-  // Ensure that the runtime has installed most prerequisites, and is ready to
-  // start. May result in updated display info being sent to registered
-  // listeners. RequestSession will fail if this hasn't been called.
-  // NOTE: When crbug.com/980000 is resolved, GvrDevice::EnsureInitialized
-  // won't install GVR runtime; it will be installed in
-  // GvrDevice::RequestSession().
-  EnsureInitialized() => ();
-
   SetInlinePosesEnabled(bool enable);
 };
 
diff --git a/device/vr/public/mojom/vr_service.mojom b/device/vr/public/mojom/vr_service.mojom
index f8c86aa..32a556e 100644
--- a/device/vr/public/mojom/vr_service.mojom
+++ b/device/vr/public/mojom/vr_service.mojom
@@ -223,20 +223,6 @@
   gfx.mojom.Transform hit_matrix;
 };
 
-struct VRDisplayCapabilities {
-  bool has_position;
-  bool has_external_display;
-
-  // Indicates whether the display can actively show imagery on a headset.
-  bool can_present;
-
-  // Whether the display gathers data about the environment (for AR like
-  // planes, point clouds, meshes, etc.). The backend will decide whether
-  // it needs to provide camera frames or not based on whether it is a
-  // see-through HMD or camera-based AR system.
-  bool can_provide_environment_integration;
-};
-
 // Information about the optical properties for an eye in a VRDisplay.
 struct VREyeParameters {
   VRFieldOfView field_of_view;
@@ -266,14 +252,11 @@
 
 struct VRDisplayInfo {
   XRDeviceId id;
-  string display_name;
-  VRDisplayCapabilities capabilities;
   VRStageParameters? stage_parameters;
   // Parameters required to distort a scene for viewing in a VR headset. Only
   // required for devices which have the can_present capability.
   VREyeParameters? left_eye;
   VREyeParameters? right_eye;
-  float webvr_default_framebuffer_scale = 1.0;
   float webxr_default_framebuffer_scale = 1.0;
 };
 
@@ -500,12 +483,6 @@
   RequestSession(XRSessionOptions options) => (RequestSessionResult result);
   SupportsSession(XRSessionOptions options) => (bool supports_session);
 
-  // WebVR 1.1 functionality compatibility method. Returns VRDisplayInfo for an
-  // immersive session if immersive is supported. If (and only if) immersive is
-  // not supported, will return a nullptr. This call might cause device specific
-  // UI to appear.
-  GetImmersiveVRDisplayInfo() => (VRDisplayInfo? info);
-
   // Shuts down an active immersive session. The callback is triggered
   // once teardown is complete and the system is again in a state where
   // a new immersive session could be started.
diff --git a/device/vr/test/fake_vr_device.cc b/device/vr/test/fake_vr_device.cc
index bed7141..48fb681 100644
--- a/device/vr/test/fake_vr_device.cc
+++ b/device/vr/test/fake_vr_device.cc
@@ -18,12 +18,6 @@
 mojom::VRDisplayInfoPtr FakeVRDevice::InitBasicDevice() {
   mojom::VRDisplayInfoPtr display_info = mojom::VRDisplayInfo::New();
   display_info->id = GetId();
-  display_info->display_name = "FakeVRDevice";
-
-  display_info->capabilities = mojom::VRDisplayCapabilities::New();
-  display_info->capabilities->has_position = false;
-  display_info->capabilities->has_external_display = false;
-  display_info->capabilities->can_present = false;
 
   display_info->left_eye = InitEye(45, -0.03f, 1024);
   display_info->right_eye = InitEye(45, 0.03f, 1024);
diff --git a/device/vr/vr_device_base.cc b/device/vr/vr_device_base.cc
index dabda27..2abe5932 100644
--- a/device/vr/vr_device_base.cc
+++ b/device/vr/vr_device_base.cc
@@ -80,10 +80,6 @@
   return runtime_receiver_.BindNewPipeAndPassRemote();
 }
 
-void VRDeviceBase::EnsureInitialized(EnsureInitializedCallback callback) {
-  std::move(callback).Run();
-}
-
 void VRDeviceBase::SetInlinePosesEnabled(bool enable) {
   inline_poses_enabled_ = enable;
 }
diff --git a/device/vr/vr_device_base.h b/device/vr/vr_device_base.h
index feffa80b..c129b6e 100644
--- a/device/vr/vr_device_base.h
+++ b/device/vr/vr_device_base.h
@@ -33,7 +33,6 @@
   void ListenToDeviceChanges(
       mojo::PendingAssociatedRemote<mojom::XRRuntimeEventListener> listener,
       mojom::XRRuntime::ListenToDeviceChangesCallback callback) final;
-  void EnsureInitialized(EnsureInitializedCallback callback) override;
   void SetInlinePosesEnabled(bool enable) override;
   void ShutdownSession(mojom::XRRuntime::ShutdownSessionCallback) override;
 
diff --git a/device/vr/vr_device_base_unittest.cc b/device/vr/vr_device_base_unittest.cc
index b1dde911..7a911a2 100644
--- a/device/vr/vr_device_base_unittest.cc
+++ b/device/vr/vr_device_base_unittest.cc
@@ -79,7 +79,6 @@
   mojom::VRDisplayInfoPtr MakeVRDisplayInfo(mojom::XRDeviceId device_id) {
     mojom::VRDisplayInfoPtr display_info = mojom::VRDisplayInfo::New();
     display_info->id = device_id;
-    display_info->capabilities = mojom::VRDisplayCapabilities::New();
     return display_info;
   }
 
diff --git a/device/vr/windows_mixed_reality/mixed_reality_device.cc b/device/vr/windows_mixed_reality/mixed_reality_device.cc
index 64303fd9..fa44edd 100644
--- a/device/vr/windows_mixed_reality/mixed_reality_device.cc
+++ b/device/vr/windows_mixed_reality/mixed_reality_device.cc
@@ -30,13 +30,6 @@
 mojom::VRDisplayInfoPtr CreateFakeVRDisplayInfo(device::mojom::XRDeviceId id) {
   mojom::VRDisplayInfoPtr display_info = mojom::VRDisplayInfo::New();
   display_info->id = id;
-  display_info->display_name = "Windows Mixed Reality";
-  display_info->capabilities = mojom::VRDisplayCapabilities::New();
-  display_info->capabilities->has_position = true;
-  display_info->capabilities->has_external_display = true;
-  display_info->capabilities->can_present = true;
-  display_info->webvr_default_framebuffer_scale = 1.0;
-  display_info->webxr_default_framebuffer_scale = 1.0;
 
   display_info->left_eye = mojom::VREyeParameters::New();
   display_info->right_eye = mojom::VREyeParameters::New();
diff --git a/device/vr/windows_mixed_reality/mixed_reality_renderloop.cc b/device/vr/windows_mixed_reality/mixed_reality_renderloop.cc
index 2ca5c43..08cc332 100644
--- a/device/vr/windows_mixed_reality/mixed_reality_renderloop.cc
+++ b/device/vr/windows_mixed_reality/mixed_reality_renderloop.cc
@@ -673,18 +673,6 @@
     current_display_info_ = mojom::VRDisplayInfo::New();
     current_display_info_->id =
         device::mojom::XRDeviceId::WINDOWS_MIXED_REALITY_ID;
-    current_display_info_->display_name =
-        "Windows Mixed Reality";  // TODO(billorr): share this string.
-    current_display_info_->capabilities = mojom::VRDisplayCapabilities::New(
-        true /* has_position */, true /* has_external_display */,
-        true /* can_present */,
-        false /* can_provide_environment_integration */);
-
-    // TODO(billorr): consider scaling framebuffers after rendering support is
-    // added.
-    current_display_info_->webvr_default_framebuffer_scale = 1.0f;
-    current_display_info_->webxr_default_framebuffer_scale = 1.0f;
-
     changed = true;
   }
 
diff --git a/docs/windows_build_instructions.md b/docs/windows_build_instructions.md
index 556d939..dda0acc 100644
--- a/docs/windows_build_instructions.md
+++ b/docs/windows_build_instructions.md
@@ -306,18 +306,29 @@
 ```shell
 $ set NINJA_SUMMARIZE_BUILD=1
 $ autoninja -C out\Default base
-    Longest build steps:
-...
-           1.2 weighted s to build base.dll, base.dll.lib, base.dll.pdb (1.2 s CPU time)
-           8.5 weighted s to build obj/base/base/values.obj (30.1 s CPU time)
-    Time by build-step type:
-...
-           1.2 s weighted time to generate 1 PEFile (linking) files (1.2 s CPU time)
-          30.3 s weighted time to generate 45 .obj files (688.8 s CPU time)
-    31.8 s weighted time (693.8 s CPU time, 21.8x parallelism)
-    86 build steps completed, average of 2.71/s
+Longest build steps:
+       0.1 weighted s to build obj/base/base/trace_log.obj (6.7 s elapsed time)
+       0.2 weighted s to build nasm.exe, nasm.exe.pdb (0.2 s elapsed time)
+       0.3 weighted s to build obj/base/base/win_util.obj (12.4 s elapsed time)
+       1.2 weighted s to build base.dll, base.dll.lib (1.2 s elapsed time)
+Time by build-step type:
+       0.0 s weighted time to generate 6 .lib files (0.3 s elapsed time sum)
+       0.1 s weighted time to generate 25 .stamp files (1.2 s elapsed time sum)
+       0.2 s weighted time to generate 20 .o files (2.8 s elapsed time sum)
+       1.7 s weighted time to generate 4 PEFile (linking) files (2.0 s elapsed
+time sum)
+      23.9 s weighted time to generate 770 .obj files (974.8 s elapsed time sum)
+26.1 s weighted time (982.9 s elapsed time sum, 37.7x parallelism)
+839 build steps completed, average of 32.17/s
 ```
 
+The "weighted" time is the elapsed time of each build step divided by the number
+of tasks that were running in parallel. This makes it an excellent approximation
+of how "important" a slow step was. A link that is entirely or mostly serialized
+will have a weighted time that is the same or similar to its elapsed time. A
+compile that runs in parallel with 999 other compiles will have a weighted time
+that is tiny.
+
 You can also generate these reports by manually running the script after a build:
 
 ```shell
diff --git a/extensions/common/image_util.cc b/extensions/common/image_util.cc
index 0b2cd46a..e84bf58 100644
--- a/extensions/common/image_util.cc
+++ b/extensions/common/image_util.cc
@@ -8,7 +8,6 @@
 #include <stdint.h>
 
 #include <algorithm>
-#include <vector>
 
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
@@ -65,9 +64,9 @@
       formatted_color += color_string[i];
     }
   } else if (color_string.length() == 7) {
-    formatted_color = color_string.substr(1, 6);
+    formatted_color.assign(color_string, 1, 6);
   } else if (color_string.length() == 9) {
-    formatted_color = color_string.substr(1, 8);
+    formatted_color.assign(color_string, 1, 8);
   } else {
     return false;
   }
@@ -77,10 +76,9 @@
     formatted_color += "FF";
   }
 
-  // Convert the string to an integer and make sure it is in the correct value
-  // range.
-  std::vector<uint8_t> color_bytes;
-  if (!base::HexStringToBytes(formatted_color, &color_bytes))
+  // Convert the hex string to an integer.
+  std::array<uint8_t, 4> color_bytes;
+  if (!base::HexStringToSpan(formatted_color, color_bytes))
     return false;
 
   *result = SkColorSetARGB(color_bytes[3], color_bytes[0], color_bytes[1],
diff --git a/fuchsia/engine/renderer/web_engine_content_renderer_client.cc b/fuchsia/engine/renderer/web_engine_content_renderer_client.cc
index c7a044b7..615b95a 100644
--- a/fuchsia/engine/renderer/web_engine_content_renderer_client.cc
+++ b/fuchsia/engine/renderer/web_engine_content_renderer_client.cc
@@ -89,8 +89,8 @@
   }
 
   media::EmeConfigRule GetEncryptionSchemeConfigRule(
-      media::EncryptionMode encryption_mode) const override {
-    if (encryption_mode == ::media::EncryptionMode::kCenc) {
+      media::EncryptionScheme encryption_mode) const override {
+    if (encryption_mode == ::media::EncryptionScheme::kCenc) {
       return media::EmeConfigRule::SUPPORTED;
     }
 
@@ -179,8 +179,8 @@
 
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
           switches::kEnableWidevine)) {
-    base::flat_set<media::EncryptionMode> encryption_schemes{
-        media::EncryptionMode::kCenc, media::EncryptionMode::kCbcs};
+    base::flat_set<media::EncryptionScheme> encryption_schemes{
+        media::EncryptionScheme::kCenc, media::EncryptionScheme::kCbcs};
 
     // Fuchsia always decrypts audio into clear buffers and return them back to
     // Chromium. Hardware secured decoders are only available for supported
diff --git a/infra/config/buckets/ci/goma.star b/infra/config/buckets/ci/goma.star
index 4b2da61..6460cbc 100644
--- a/infra/config/buckets/ci/goma.star
+++ b/infra/config/buckets/ci/goma.star
@@ -413,10 +413,12 @@
 
 goma_builder(
     name = 'Chromium Android ARM 32-bit Goma RBE ToT',
+    goma_backend = goma.backend.RBE_TOT,
 )
 
 goma_builder(
     name = 'Chromium Android ARM 32-bit Goma RBE ToT (ATS)',
+    goma_backend = goma.backend.RBE_TOT,
     goma_enable_ats = True,
 )
 
@@ -466,10 +468,12 @@
 
 goma_builder(
     name = 'Chromium Linux Goma RBE ToT',
+    goma_backend = goma.backend.RBE_TOT,
 )
 
 goma_builder(
     name = 'Chromium Linux Goma RBE ToT (ATS)',
+    goma_backend = goma.backend.RBE_TOT,
     goma_enable_ats = True,
 )
 
@@ -525,6 +529,7 @@
 
 goma_mac_builder(
     name = 'Chromium Mac Goma RBE ToT',
+    goma_backend = goma.backend.RBE_TOT,
 )
 
 goma_mac_builder(
@@ -532,9 +537,10 @@
 )
 
 
-def goma_windows_builder(*, name, goma_enable_ats=True, **kwargs):
+def goma_windows_builder(*, name, goma_enable_ats=True, cores=32, **kwargs):
   return goma_builder(
       name = name,
+      cores = cores,
       goma_enable_ats = goma_enable_ats,
       os = os.WINDOWS_DEFAULT,
       **kwargs
@@ -572,9 +578,11 @@
 
 goma_windows_builder(
     name = 'Chromium Win Goma RBE ToT',
+    goma_backend = goma.backend.RBE_TOT,
 )
 
 goma_windows_builder(
     name = 'CrWinGomaStaging',
+    cores = 8,
     goma_enable_ats = False,
 )
diff --git a/infra/config/generated/cr-buildbucket.cfg b/infra/config/generated/cr-buildbucket.cfg
index 14057cd5..52e3a97 100644
--- a/infra/config/generated/cr-buildbucket.cfg
+++ b/infra/config/generated/cr-buildbucket.cfg
@@ -990,6 +990,7 @@
         name: "chromium"
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
         cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"rpc_extra_params\":\"?tot\",\"server_host\":\"staging-goma.chromium.org\"}"
         properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
         properties_j: "mastername:\"chromium.goma\""
       >
@@ -1009,7 +1010,7 @@
         name: "chromium"
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
         cipd_version: "refs/heads/master"
-        properties_j: "$build/goma:{\"enable_ats\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?tot\",\"server_host\":\"staging-goma.chromium.org\"}"
         properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
         properties_j: "mastername:\"chromium.goma\""
       >
@@ -1189,6 +1190,7 @@
         name: "chromium"
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
         cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"rpc_extra_params\":\"?tot\",\"server_host\":\"staging-goma.chromium.org\"}"
         properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
         properties_j: "mastername:\"chromium.goma\""
       >
@@ -1208,7 +1210,7 @@
         name: "chromium"
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
         cipd_version: "refs/heads/master"
-        properties_j: "$build/goma:{\"enable_ats\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?tot\",\"server_host\":\"staging-goma.chromium.org\"}"
         properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
         properties_j: "mastername:\"chromium.goma\""
       >
@@ -1327,7 +1329,7 @@
         name: "chromium"
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
         cipd_version: "refs/heads/master"
-        properties_j: "$build/goma:{\"jobs\":80}"
+        properties_j: "$build/goma:{\"jobs\":80,\"rpc_extra_params\":\"?tot\",\"server_host\":\"staging-goma.chromium.org\"}"
         properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
         properties_j: "mastername:\"chromium.goma\""
       >
@@ -1360,7 +1362,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
       dimensions: "builder:Chromium Win Goma RBE Prod"
-      dimensions: "cores:8"
+      dimensions: "cores:32"
       dimensions: "cpu:x86-64"
       dimensions: "os:Windows-10"
       recipe: <
@@ -1380,7 +1382,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
       dimensions: "builder:Chromium Win Goma RBE Prod (clobber)"
-      dimensions: "cores:8"
+      dimensions: "cores:32"
       dimensions: "cpu:x86-64"
       dimensions: "os:Windows-10"
       recipe: <
@@ -1400,7 +1402,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
       dimensions: "builder:Chromium Win Goma RBE Prod (dbg)"
-      dimensions: "cores:8"
+      dimensions: "cores:32"
       dimensions: "cpu:x86-64"
       dimensions: "os:Windows-10"
       recipe: <
@@ -1420,7 +1422,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
       dimensions: "builder:Chromium Win Goma RBE Prod (dbg) (clobber)"
-      dimensions: "cores:8"
+      dimensions: "cores:32"
       dimensions: "cpu:x86-64"
       dimensions: "os:Windows-10"
       recipe: <
@@ -1440,7 +1442,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
       dimensions: "builder:Chromium Win Goma RBE Staging"
-      dimensions: "cores:8"
+      dimensions: "cores:32"
       dimensions: "cpu:x86-64"
       dimensions: "os:Windows-10"
       recipe: <
@@ -1460,7 +1462,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
       dimensions: "builder:Chromium Win Goma RBE Staging (clobber)"
-      dimensions: "cores:8"
+      dimensions: "cores:32"
       dimensions: "cpu:x86-64"
       dimensions: "os:Windows-10"
       recipe: <
@@ -1480,14 +1482,14 @@
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
       dimensions: "builder:Chromium Win Goma RBE ToT"
-      dimensions: "cores:8"
+      dimensions: "cores:32"
       dimensions: "cpu:x86-64"
       dimensions: "os:Windows-10"
       recipe: <
         name: "chromium"
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
         cipd_version: "refs/heads/master"
-        properties_j: "$build/goma:{\"enable_ats\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?tot\",\"server_host\":\"staging-goma.chromium.org\"}"
         properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
         properties_j: "mastername:\"chromium.goma\""
       >
diff --git a/infra/config/lib/builders.star b/infra/config/lib/builders.star
index 639d057d..9f733a8 100644
--- a/infra/config/lib/builders.star
+++ b/infra/config/lib/builders.star
@@ -68,6 +68,10 @@
             'server_host': 'staging-goma.chromium.org',
             'rpc_extra_params': '?staging',
         },
+        RBE_TOT = {
+            'server_host': 'staging-goma.chromium.org',
+            'rpc_extra_params': '?tot',
+        },
     ),
     jobs = struct(
         J50 = 50,
diff --git a/ios/build/bots/chromium.fyi/ios13-sdk-simulator.json b/ios/build/bots/chromium.fyi/ios13-sdk-simulator.json
index b5eb025..2cc4d8b 100644
--- a/ios/build/bots/chromium.fyi/ios13-sdk-simulator.json
+++ b/ios/build/bots/chromium.fyi/ios13-sdk-simulator.json
@@ -141,22 +141,6 @@
       "pool":"chromium.tests"
     },
     {
-      "include": "eg_cq_tests.json",
-      "device type": "iPad Pro (12.9-inch)",
-      "os": "12.4",
-      "xcode build version": "11b44",
-      "host os": "Mac-10.14.6",
-      "pool":"chromium.tests"
-    },
-    {
-      "include": "eg_tests.json",
-      "device type": "iPhone X",
-      "os": "12.4",
-      "xcode build version": "11b44",
-      "host os": "Mac-10.14.6",
-      "pool":"chromium.tests"
-    },
-    {
       "include": "eg_tests.json",
       "device type": "iPad Air (3rd generation)",
       "os": "12.4",
diff --git a/ios/chrome/browser/ui/webui/BUILD.gn b/ios/chrome/browser/ui/webui/BUILD.gn
index 943a0d9..2f4cf42 100644
--- a/ios/chrome/browser/ui/webui/BUILD.gn
+++ b/ios/chrome/browser/ui/webui/BUILD.gn
@@ -137,11 +137,8 @@
     "//ios/chrome/test/app:test_support",
     "//ios/chrome/test/earl_grey:test_support",
     "//ios/testing/earl_grey:earl_grey_support",
-    "//ios/web",
-    "//ios/web/public/test:element_selector",
     "//net:test_support",
     "//ui/base",
-    "//url",
   ]
   libs = [ "XCTest.framework" ]
 }
@@ -155,15 +152,20 @@
   testonly = true
   sources = [
     "inspect/inspect_ui_egtest.mm",
+    "web_ui_egtest.mm",
   ]
   deps = [
     "//base",
+    "//base/test:test_support",
+    "//components/strings",
+    "//components/version_info",
     "//ios/chrome/browser:chrome_url_constants",
     "//ios/chrome/test/earl_grey:eg_test_support+eg2",
     "//ios/testing/earl_grey:eg_test_support+eg2",
     "//ios/third_party/earl_grey2:test_lib",
     "//ios/web/public/test:element_selector",
     "//net:test_support",
+    "//ui/base",
   ]
   libs = [ "UIKit.framework" ]
 }
diff --git a/ios/chrome/browser/ui/webui/web_ui_egtest.mm b/ios/chrome/browser/ui/webui/web_ui_egtest.mm
index 3f48fcb4..9621dae 100644
--- a/ios/chrome/browser/ui/webui/web_ui_egtest.mm
+++ b/ios/chrome/browser/ui/webui/web_ui_egtest.mm
@@ -2,83 +2,62 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import <EarlGrey/EarlGrey.h>
-#import <EarlGrey/GREYKeyboard.h>
 #import <XCTest/XCTest.h>
 
 #include "base/mac/foundation_util.h"
 #include "base/metrics/field_trial.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
 #include "base/strings/sys_string_conversions.h"
 #import "base/test/ios/wait_util.h"
+#include "components/strings/grit/components_strings.h"
 #include "components/version_info/version_info.h"
 #include "ios/chrome/browser/chrome_url_constants.h"
-#include "ios/chrome/browser/system_flags.h"
-#import "ios/chrome/browser/ui/omnibox/omnibox_text_field_ios.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h"
 #import "ios/chrome/test/earl_grey/chrome_matchers.h"
 #import "ios/chrome/test/earl_grey/chrome_test_case.h"
-#import "ios/web/public/web_client.h"
+#import "ios/testing/earl_grey/earl_grey_test.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "ui/base/device_form_factor.h"
 #include "ui/base/l10n/l10n_util.h"
-#include "url/scheme_host_port.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
+using base::TrimPositions;
 using chrome_test_util::BackButton;
 using chrome_test_util::ForwardButton;
+using chrome_test_util::OmniboxText;
 
 namespace {
 
-// Loads WebUI page with given |host|.
-void LoadWebUIUrl(const std::string& host) {
-  GURL web_ui_url(url::SchemeHostPort(kChromeUIScheme, host, 0).Serialize());
-  [ChromeEarlGrey loadURL:web_ui_url];
+// Returns the url to the web ui page |host|. |url::SchemeHostPort| can not be
+// used when this test is run using EarlGrey2 because the chrome scheme is not
+// registered in the test process and |url::SchemeHostPort| will not build an
+// invalid URL.
+GURL WebUIPageUrlWithHost(const std::string& host) {
+  return GURL(base::StringPrintf("%s://%s", kChromeUIScheme, host.c_str()));
 }
 
-// Adds wait for omnibox text matcher so that omnibox text can be updated.
-// TODO(crbug.com/642207): This method has to be unified with the omniboxText
-// matcher or resides in the same location with the omniboxText matcher.
-id<GREYMatcher> WaitForOmniboxText(std::string text) {
-  MatchesBlock matches = ^BOOL(UIView* view) {
-    if (![view isKindOfClass:[OmniboxTextFieldIOS class]]) {
-      return NO;
-    }
-    OmniboxTextFieldIOS* omnibox =
-        base::mac::ObjCCast<OmniboxTextFieldIOS>(view);
-    GREYAssert(base::test::ios::WaitUntilConditionOrTimeout(
-                   base::test::ios::kWaitForUIElementTimeout,
-                   ^{
-                     return base::SysNSStringToUTF8(omnibox.text) == text;
-                   }),
-               @"Omnibox did not contain %@", base::SysUTF8ToNSString(text));
-    return YES;
-  };
+// Waits for omnibox text to equal |URL| and returns true if it was found or
+// false on timeout. Strips trailing URL slash if present as the omnibox does
+// not display them.
+bool WaitForOmniboxURLString(std::string URL) {
+  const std::string trimmed_URL =
+      base::TrimString(URL, "/", TrimPositions::TRIM_TRAILING).as_string();
 
-  DescribeToBlock describe = ^(id<GREYDescription> description) {
-    [description appendText:@"omnibox text "];
-    [description appendText:base::SysUTF8ToNSString(text)];
-  };
-
-  return grey_allOf(
-      chrome_test_util::Omnibox(),
-      [[GREYElementMatcherBlock alloc] initWithMatchesBlock:matches
-                                           descriptionBlock:describe],
-      nil);
-}
-
-// Validates that the experimental flags container is visible.
-bool WebStateContainsChromeFlagsBody() {
-  NSError* error = nil;
-  id result = chrome_test_util::ExecuteJavaScript(
-      @"document.getElementById('body-container').style.visibility", &error);
-  if (error)
-    return false;
-  NSString* resultString = base::mac::ObjCCastStrict<NSString>(result);
-  return [@"visible" isEqualToString:resultString];
+  // TODO(crbug.com/642207): Unify with the omniboxText matcher or move to the
+  // same location with the omniboxText matcher.
+  return base::test::ios::WaitUntilConditionOrTimeout(
+      base::test::ios::kWaitForUIElementTimeout, ^{
+        NSError* error = nil;
+        [[EarlGrey selectElementWithMatcher:OmniboxText(trimmed_URL)]
+            assertWithMatcher:grey_notNil()
+                        error:&error];
+        return error == nil;
+      });
 }
 
 }  // namespace
@@ -92,22 +71,24 @@
 // Tests that chrome://version renders and contains correct version number and
 // user agent string.
 - (void)testVersion {
-  LoadWebUIUrl(kChromeUIVersionHost);
+  [ChromeEarlGrey loadURL:WebUIPageUrlWithHost(kChromeUIVersionHost)];
 
   // Verify that app version is present on the page.
   const std::string version = version_info::GetVersionNumber();
   [ChromeEarlGrey waitForWebStateContainingText:version];
 
-  // Verify that mobile User Agent string is present on the page.
-  const std::string userAgent =
-      web::GetWebClient()->GetUserAgent(web::UserAgentType::MOBILE);
-  [ChromeEarlGrey waitForWebStateContainingText:userAgent];
+  // Verify that mobile User Agent string is present on the page. Testing for
+  // only a portion of the string is sufficient to ensure the value has been
+  // populated in the UI and it is not blank. However, the exact string value is
+  // not validated as this test does not have access to get the full User Agent
+  // string from the WebClient.
+  [ChromeEarlGrey waitForWebStateContainingText:"AppleWebKit"];
 }
 
 // Tests that clicking on a chrome://terms link from chrome://chrome-urls
 // navigates to terms page.
 - (void)testChromeURLNavigateToTerms {
-  LoadWebUIUrl(kChromeUIChromeURLsHost);
+  [ChromeEarlGrey loadURL:WebUIPageUrlWithHost(kChromeUIChromeURLsHost)];
 
   // Tap on chrome://terms link on the page.
   [ChromeEarlGrey
@@ -115,8 +96,8 @@
                                    stringWithUTF8String:kChromeUITermsHost]];
 
   // Verify that the resulting page is chrome://terms.
-  [[EarlGrey selectElementWithMatcher:WaitForOmniboxText("chrome://terms")]
-      assertWithMatcher:grey_notNil()];
+  GREYAssert(WaitForOmniboxURLString(kChromeUITermsURL),
+             @"Omnibox does not contain URL.");
   const std::string kTermsText = "Google Chrome Terms of Service";
   [ChromeEarlGrey waitForWebStateContainingText:kTermsText];
 }
@@ -124,7 +105,7 @@
 // Tests that back navigation functions properly after navigation via anchor
 // click.
 - (void)testChromeURLBackNavigationFromAnchorClick {
-  LoadWebUIUrl(kChromeUIChromeURLsHost);
+  [ChromeEarlGrey loadURL:GURL(kChromeUIChromeURLsURL)];
 
   // Tap on chrome://version link on the page.
   [ChromeEarlGrey
@@ -132,16 +113,15 @@
                                    stringWithUTF8String:kChromeUIVersionHost]];
 
   // Verify that the resulting page is chrome://version.
-  [[EarlGrey selectElementWithMatcher:WaitForOmniboxText("chrome://version")]
-      assertWithMatcher:grey_notNil()];
+  GREYAssert(WaitForOmniboxURLString(kChromeUIVersionURL),
+             @"Omnibox did not contain URL.");
   [ChromeEarlGrey waitForWebStateContainingText:"The Chromium Authors"];
 
   // Tap the back button in the toolbar and verify that the resulting page is
   // the previously visited page chrome://chrome-urls.
   [[EarlGrey selectElementWithMatcher:BackButton()] performAction:grey_tap()];
-  [[EarlGrey
-      selectElementWithMatcher:WaitForOmniboxText("chrome://chrome-urls")]
-      assertWithMatcher:grey_notNil()];
+  GREYAssert(WaitForOmniboxURLString(kChromeUIChromeURLsURL),
+             @"Omnibox did not contain URL.");
   [ChromeEarlGrey waitForWebStateContainingText:"List of Chrome URLs"];
 }
 
@@ -149,39 +129,37 @@
 // properly.
 - (void)testChromeURLBackAndForwardAndReloadNavigation {
   // Navigate to the first URL chrome://version.
-  LoadWebUIUrl(kChromeUIVersionHost);
+  [ChromeEarlGrey loadURL:GURL(kChromeUIVersionURL)];
 
   // Navigate to the second URL chrome://chrome-urls.
-  LoadWebUIUrl(kChromeUIChromeURLsHost);
+  [ChromeEarlGrey loadURL:GURL(kChromeUIChromeURLsURL)];
 
   // Tap the back button in the toolbar and verify that the resulting page's URL
   // corresponds to the first URL chrome://version that was loaded.
   [[EarlGrey selectElementWithMatcher:BackButton()] performAction:grey_tap()];
-  [[EarlGrey selectElementWithMatcher:WaitForOmniboxText("chrome://version")]
-      assertWithMatcher:grey_notNil()];
+  GREYAssert(WaitForOmniboxURLString(kChromeUIVersionURL),
+             @"Omnibox did not contain URL.");
 
   // Tap the forward button in the toolbar and verify that the resulting page's
   // URL corresponds the second URL chrome://chrome-urls that was loaded.
   [[EarlGrey selectElementWithMatcher:ForwardButton()]
       performAction:grey_tap()];
-  [[EarlGrey
-      selectElementWithMatcher:WaitForOmniboxText("chrome://chrome-urls")]
-      assertWithMatcher:grey_notNil()];
+  GREYAssert(WaitForOmniboxURLString(kChromeUIChromeURLsURL),
+             @"Omnibox did not contain URL.");
 
   // Tap the back button in the toolbar then reload, and verify that the
   // resulting page corresponds to the first URL.
   [[EarlGrey selectElementWithMatcher:BackButton()] performAction:grey_tap()];
   [ChromeEarlGrey waitForPageToFinishLoading];
   [ChromeEarlGrey reload];
-  [[EarlGrey selectElementWithMatcher:WaitForOmniboxText("chrome://version")]
-      assertWithMatcher:grey_notNil()];
+  GREYAssert(WaitForOmniboxURLString(kChromeUIVersionURL),
+             @"Omnibox did not contain URL.");
 
   // Make sure forward navigation is still possible.
   [[EarlGrey selectElementWithMatcher:ForwardButton()]
       performAction:grey_tap()];
-  [[EarlGrey
-      selectElementWithMatcher:WaitForOmniboxText("chrome://chrome-urls")]
-      assertWithMatcher:grey_notNil()];
+  GREYAssert(WaitForOmniboxURLString(kChromeUIChromeURLsURL),
+             @"Omnibox did not contain URL.");
 }
 
 // Tests that all URLs on chrome://chrome-urls page load without error.
@@ -189,16 +167,15 @@
   // Load WebUI pages and verify they load without any error.
   for (size_t i = 0; i < kNumberOfChromeHostURLs; ++i) {
     const char* host = kChromeHostURLs[i];
-    // Exclude non-WebUI pages, as they do not go through a "loading" phase as
-    // expected in LoadWebUIUrl.
+    // Exclude non-WebUI pages, as they do not go through a "loading" phase.
     if (host == kChromeUINewTabHost) {
       continue;
     }
-    LoadWebUIUrl(host);
-    const std::string chrome_url_path =
-        url::SchemeHostPort(kChromeUIScheme, kChromeHostURLs[i], 0).Serialize();
-    [[EarlGrey selectElementWithMatcher:WaitForOmniboxText(chrome_url_path)]
-        assertWithMatcher:grey_notNil()];
+    GURL URL = WebUIPageUrlWithHost(host);
+    [ChromeEarlGrey loadURL:URL];
+
+    GREYAssert(WaitForOmniboxURLString(URL.spec()),
+               @"Omnibox did not contain URL.");
   }
 }
 
@@ -209,8 +186,8 @@
   [ChromeEarlGrey loadURL:GURL(kChromeInvalidURL)];
 
   // Verify that the resulting page is an error page.
-  [[EarlGrey selectElementWithMatcher:WaitForOmniboxText(kChromeInvalidURL)]
-      assertWithMatcher:grey_notNil()];
+  GREYAssert(WaitForOmniboxURLString(kChromeInvalidURL),
+             @"Omnibox did not contain URL.");
   std::string errorMessage = net::ErrorToShortString(net::ERR_INVALID_URL);
   [ChromeEarlGrey waitForWebStateContainingText:errorMessage];
 }
@@ -219,15 +196,13 @@
 - (void)testBackForwardFromWebURL {
   GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");
 
-  // Not using kChromeUIVersionURL because it has a final "/" that is not
-  // displayed in Omnibox.
-  const char kChromeVersionURL[] = "chrome://version";
   const char kChromeVersionWebText[] = "The Chromium Authors";
   const char kWebPageText[] = "pony";
 
-  LoadWebUIUrl(kChromeUIVersionHost);
-  [[EarlGrey selectElementWithMatcher:WaitForOmniboxText(kChromeVersionURL)]
-      assertWithMatcher:grey_notNil()];
+  [ChromeEarlGrey loadURL:GURL(kChromeUIVersionURL)];
+
+  GREYAssert(WaitForOmniboxURLString(kChromeUIVersionURL),
+             @"Omnibox did not contain URL.");
   [ChromeEarlGrey waitForWebStateContainingText:kChromeVersionWebText];
 
   GURL webURL = self.testServer->GetURL("/pony.html");
@@ -235,34 +210,35 @@
   [ChromeEarlGrey waitForWebStateContainingText:kWebPageText];
 
   [ChromeEarlGrey goBack];
-  [[EarlGrey selectElementWithMatcher:WaitForOmniboxText(kChromeVersionURL)]
-      assertWithMatcher:grey_notNil()];
+  GREYAssert(WaitForOmniboxURLString(kChromeUIVersionURL),
+             @"Omnibox did not contain URL.");
   [ChromeEarlGrey waitForWebStateContainingText:kChromeVersionWebText];
 
   [ChromeEarlGrey goForward];
   [ChromeEarlGrey waitForWebStateContainingText:kWebPageText];
 
   [ChromeEarlGrey goBack];
-  [[EarlGrey selectElementWithMatcher:WaitForOmniboxText(kChromeVersionURL)]
-      assertWithMatcher:grey_notNil()];
+  GREYAssert(WaitForOmniboxURLString(kChromeUIVersionURL),
+             @"Omnibox did not contain URL.");
   [ChromeEarlGrey waitForWebStateContainingText:kChromeVersionWebText];
 }
 
 - (void)testChromeFlagsOnNTP {
   // Start with NTP and load chrome://flags.
-  LoadWebUIUrl(kChromeUIFlagsHost);
+  [ChromeEarlGrey loadURL:GURL(kChromeUIFlagsURL)];
 
-  // Not using kChromeUIFlagsHost because it has a final "/" that is not
-  // displayed in Omnibox.
-  [[EarlGrey selectElementWithMatcher:WaitForOmniboxText("chrome://flags")]
-      assertWithMatcher:grey_notNil()];
+  GREYAssert(WaitForOmniboxURLString(kChromeUIFlagsURL),
+             @"Omnibox did not contain URL.");
 
   // Validates that some of the expected text on the page exists.
   [ChromeEarlGrey waitForWebStateContainingText:"Experiments"];
   [ChromeEarlGrey waitForWebStateContainingText:"Available"];
+
   // Validates that the experimental flags container is visible.
-  GREYAssert(WebStateContainsChromeFlagsBody(),
-             @"JavaScript error or body-container is not visible");
+  NSString* flags_page_warning =
+      l10n_util::GetNSString(IDS_FLAGS_UI_PAGE_WARNING);
+  [ChromeEarlGrey waitForWebStateContainingText:base::SysNSStringToUTF8(
+                                                    flags_page_warning)];
 }
 
 - (void)testChromeFlagsOnWebsite {
@@ -275,30 +251,27 @@
   [ChromeEarlGrey waitForWebStateContainingText:kWebPageText];
 
   // Then load chrome://flags in the same tab that has loaded a website.
-  LoadWebUIUrl(kChromeUIFlagsHost);
+  [ChromeEarlGrey loadURL:WebUIPageUrlWithHost(kChromeUIFlagsHost)];
 
-  // Not using kChromeUIFlagsHost because it has a final "/" that is not
-  // displayed in Omnibox.
-  [[EarlGrey selectElementWithMatcher:WaitForOmniboxText("chrome://flags")]
-      assertWithMatcher:grey_notNil()];
+  GREYAssert(WaitForOmniboxURLString(kChromeUIFlagsURL),
+             @"Omnibox did not contain URL.");
 
   // Validates that some of the expected text on the page exists.
   [ChromeEarlGrey waitForWebStateContainingText:"Experiments"];
   [ChromeEarlGrey waitForWebStateContainingText:"Available"];
-  // Validates that the experimental flags container is visible.
-  GREYAssert(WebStateContainsChromeFlagsBody(),
-             @"JavaScript error or body-container is not visible");
+
+  NSString* flags_page_warning =
+      l10n_util::GetNSString(IDS_FLAGS_UI_PAGE_WARNING);
+  [ChromeEarlGrey waitForWebStateContainingText:base::SysNSStringToUTF8(
+                                                    flags_page_warning)];
 }
 
 - (void)testChromePasswordManagerInternalsSite {
-  LoadWebUIUrl(kChromeUIPasswordManagerInternalsHost);
+  GURL URL = WebUIPageUrlWithHost(kChromeUIPasswordManagerInternalsHost);
+  [ChromeEarlGrey loadURL:URL];
 
-  // Not using kChromeUIPasswordManagerInternalsHost because it has a final "/"
-  // that is not displayed in Omnibox.
-  [[EarlGrey
-      selectElementWithMatcher:WaitForOmniboxText(
-                                   "chrome://password-manager-internals")]
-      assertWithMatcher:grey_notNil()];
+  GREYAssert(WaitForOmniboxURLString(URL.spec()),
+             @"Omnibox did not contain URL.");
 
   // Validates that some of the expected text on the page exists.
   [ChromeEarlGrey waitForWebStateContainingText:"Variations"];
@@ -306,13 +279,11 @@
 }
 
 - (void)testChromeAutofillInternalsSite {
-  LoadWebUIUrl(kChromeUIAutofillInternalsHost);
+  GURL URL = WebUIPageUrlWithHost(kChromeUIAutofillInternalsHost);
+  [ChromeEarlGrey loadURL:URL];
 
-  // Not using kChromeUIAutofillInternalsHost because it has a final "/" that
-  // is not displayed in Omnibox.
-  [[EarlGrey selectElementWithMatcher:WaitForOmniboxText(
-                                          "chrome://autofill-internals")]
-      assertWithMatcher:grey_notNil()];
+  GREYAssert(WaitForOmniboxURLString(URL.spec()),
+             @"Omnibox did not contain URL.");
 
   // Validates that some of the expected text on the page exists.
   [ChromeEarlGrey waitForWebStateContainingText:"Variations"];
diff --git a/ios/web_view/BUILD.gn b/ios/web_view/BUILD.gn
index 28a3bcf..5a4b392 100644
--- a/ios/web_view/BUILD.gn
+++ b/ios/web_view/BUILD.gn
@@ -150,8 +150,6 @@
   "internal/language/web_view_language_model_manager_factory.mm",
   "internal/language/web_view_url_language_histogram_factory.h",
   "internal/language/web_view_url_language_histogram_factory.mm",
-  "internal/passwords/mock_credentials_filter.h",
-  "internal/passwords/mock_credentials_filter.mm",
   "internal/passwords/web_view_password_feature_manager.h",
   "internal/passwords/web_view_password_feature_manager.mm",
   "internal/passwords/web_view_password_manager_client.h",
diff --git a/ios/web_view/internal/app/application_context.mm b/ios/web_view/internal/app/application_context.mm
index 536c6ee..cfae793 100644
--- a/ios/web_view/internal/app/application_context.mm
+++ b/ios/web_view/internal/app/application_context.mm
@@ -72,7 +72,6 @@
 
 void ApplicationContext::SaveState() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  // TODO(crbug.com/723854): Commit prefs when entering background.
   if (local_state_) {
     local_state_->CommitPendingWrite();
   }
diff --git a/ios/web_view/internal/autofill/cwv_credit_card_saver.mm b/ios/web_view/internal/autofill/cwv_credit_card_saver.mm
index 10605849..a49f421 100644
--- a/ios/web_view/internal/autofill/cwv_credit_card_saver.mm
+++ b/ios/web_view/internal/autofill/cwv_credit_card_saver.mm
@@ -113,7 +113,6 @@
 
     _saveCompletionHandler = completionHandler;
     DCHECK(_uploadSaveCardCallback);
-    // TODO(crbug.com/993130): Provide additional card details if required.
     std::move(_uploadSaveCardCallback)
         .Run(autofill::AutofillClient::ACCEPTED,
              /*user_provided_card_details=*/{});
diff --git a/ios/web_view/internal/passwords/mock_credentials_filter.h b/ios/web_view/internal/passwords/mock_credentials_filter.h
deleted file mode 100644
index 79f536d1..0000000
--- a/ios/web_view/internal/passwords/mock_credentials_filter.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_WEB_VIEW_INTERNAL_PASSWORDS_MOCK_CREDENTIALS_FILTER_H_
-#define IOS_WEB_VIEW_INTERNAL_PASSWORDS_MOCK_CREDENTIALS_FILTER_H_
-
-#include "base/macros.h"
-#include "components/password_manager/core/browser/credentials_filter.h"
-
-namespace ios_web_view {
-
-// Mock of the CredentialsFilter API, to be used in tests. This filter does
-// not filter out anything.
-class MockCredentialsFilter : public password_manager::CredentialsFilter {
- public:
-  MockCredentialsFilter();
-
-  ~MockCredentialsFilter() override;
-
-  // CredentialsFilter
-  bool ShouldSave(const autofill::PasswordForm& form) const override;
-  bool ShouldSaveGaiaPasswordHash(
-      const autofill::PasswordForm& form) const override;
-  bool ShouldSaveEnterprisePasswordHash(
-      const autofill::PasswordForm& form) const override;
-  void ReportFormLoginSuccess(
-      const password_manager::PasswordFormManager& form_manager) const override;
-  bool IsSyncAccountEmail(const std::string& username) const override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(MockCredentialsFilter);
-};
-
-}  // namespace ios_web_view
-
-#endif  // IOS_WEB_VIEW_INTERNAL_PASSWORDS_MOCK_CREDENTIALS_FILTER_H_
diff --git a/ios/web_view/internal/passwords/mock_credentials_filter.mm b/ios/web_view/internal/passwords/mock_credentials_filter.mm
deleted file mode 100644
index a650253..0000000
--- a/ios/web_view/internal/passwords/mock_credentials_filter.mm
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ios/web_view/internal/passwords/mock_credentials_filter.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-namespace ios_web_view {
-
-MockCredentialsFilter::MockCredentialsFilter() = default;
-
-MockCredentialsFilter::~MockCredentialsFilter() = default;
-
-bool MockCredentialsFilter::ShouldSave(
-    const autofill::PasswordForm& form) const {
-  return true;
-}
-
-bool MockCredentialsFilter::ShouldSaveGaiaPasswordHash(
-    const autofill::PasswordForm& form) const {
-  return false;
-}
-
-bool MockCredentialsFilter::ShouldSaveEnterprisePasswordHash(
-    const autofill::PasswordForm& form) const {
-  return false;
-}
-
-void MockCredentialsFilter::ReportFormLoginSuccess(
-    const password_manager::PasswordFormManager& form_manager) const {}
-
-bool MockCredentialsFilter::IsSyncAccountEmail(
-    const std::string& username) const {
-  return false;
-}
-
-}  // namespace ios_web_view
diff --git a/ios/web_view/internal/passwords/web_view_password_manager_client.h b/ios/web_view/internal/passwords/web_view_password_manager_client.h
index 7f182ff..05cc5831 100644
--- a/ios/web_view/internal/passwords/web_view_password_manager_client.h
+++ b/ios/web_view/internal/passwords/web_view_password_manager_client.h
@@ -12,8 +12,8 @@
 #import "components/password_manager/core/browser/password_manager_client.h"
 #include "components/password_manager/core/browser/password_manager_client_helper.h"
 #include "components/password_manager/core/browser/password_manager_metrics_recorder.h"
+#include "components/password_manager/core/browser/sync_credentials_filter.h"
 #include "components/prefs/pref_member.h"
-#include "ios/web_view/internal/passwords/mock_credentials_filter.h"
 #include "ios/web_view/internal/passwords/web_view_password_feature_manager.h"
 
 namespace ios_web_view {
@@ -125,8 +125,7 @@
   // password_manager::prefs::kCredentialsEnableService.
   BooleanPrefMember saving_passwords_enabled_;
 
-  // TODO(crbug.com/867297) Replace with SyncCredentialsFilter
-  const MockCredentialsFilter credentials_filter_;
+  const password_manager::SyncCredentialsFilter credentials_filter_;
 
   std::unique_ptr<autofill::LogManager> log_manager_;
 
diff --git a/ios/web_view/internal/passwords/web_view_password_manager_client.mm b/ios/web_view/internal/passwords/web_view_password_manager_client.mm
index a8c25de89..bb28e55 100644
--- a/ios/web_view/internal/passwords/web_view_password_manager_client.mm
+++ b/ios/web_view/internal/passwords/web_view_password_manager_client.mm
@@ -21,6 +21,8 @@
 #include "ios/web_view/internal/app/application_context.h"
 #import "ios/web_view/internal/passwords/web_view_password_manager_log_router_factory.h"
 #include "ios/web_view/internal/passwords/web_view_password_store_factory.h"
+#include "ios/web_view/internal/signin/web_view_identity_manager_factory.h"
+#import "ios/web_view/internal/sync/web_view_profile_sync_service_factory.h"
 #include "ios/web_view/internal/web_view_browser_state.h"
 #include "net/cert/cert_status_flags.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
@@ -35,14 +37,24 @@
 using password_manager::PasswordStore;
 using password_manager::SyncState;
 
-// TODO(crbug.com/867297): Support sync service and signin manager.
+namespace {
+
+const syncer::SyncService* GetSyncService(
+    ios_web_view::WebViewBrowserState* browser_state) {
+  return ios_web_view::WebViewProfileSyncServiceFactory::GetForBrowserState(
+      browser_state);
+}
+
+}  // namespace
 
 namespace ios_web_view {
-// TODO(crbug.com/867297): Replace with sync credentials filter.
+
 WebViewPasswordManagerClient::WebViewPasswordManagerClient(
     id<CWVPasswordManagerClientDelegate> delegate)
     : delegate_(delegate),
-      credentials_filter_(),
+      credentials_filter_(
+          this,
+          base::BindRepeating(&GetSyncService, delegate_.browserState)),
       log_manager_(autofill::LogManager::Create(
           ios_web_view::WebViewPasswordManagerLogRouterFactory::
               GetForBrowserState(delegate_.browserState),
@@ -55,9 +67,9 @@
 WebViewPasswordManagerClient::~WebViewPasswordManagerClient() = default;
 
 SyncState WebViewPasswordManagerClient::GetPasswordSyncState() const {
-  // Disable sync for Demo.
-  // TODO(crbug.com/867297): Enable sync.
-  return password_manager::NOT_SYNCING;
+  const syncer::SyncService* sync_service =
+      GetSyncService(delegate_.browserState);
+  return password_manager_util::GetPasswordSyncState(sync_service);
 }
 
 bool WebViewPasswordManagerClient::PromptUserToChooseCredentials(
@@ -204,14 +216,13 @@
 }
 
 signin::IdentityManager* WebViewPasswordManagerClient::GetIdentityManager() {
-  NOTREACHED();
-  return nullptr;
+  return WebViewIdentityManagerFactory::GetForBrowserState(
+      delegate_.browserState);
 }
 
 scoped_refptr<network::SharedURLLoaderFactory>
 WebViewPasswordManagerClient::GetURLLoaderFactory() {
-  NOTREACHED();
-  return nullptr;
+  return (delegate_.browserState)->GetSharedURLLoaderFactory();
 }
 
 bool WebViewPasswordManagerClient::IsIsolationForPasswordSitesEnabled() const {
diff --git a/ios/web_view/internal/sync/web_view_sync_client.mm b/ios/web_view/internal/sync/web_view_sync_client.mm
index 4aa731a..76bbc72c 100644
--- a/ios/web_view/internal/sync/web_view_sync_client.mm
+++ b/ios/web_view/internal/sync/web_view_sync_client.mm
@@ -135,7 +135,7 @@
 syncer::DataTypeController::TypeVector
 WebViewSyncClient::CreateDataTypeControllers(
     syncer::SyncService* sync_service) {
-  // iOS WebView uses butter sync and so has no need to record user consents.
+  // //ios/web_view clients are supposed to record their own consents.
   syncer::DataTypeController::TypeVector type_vector =
       component_factory_->CreateCommonDataTypeControllers(GetDisabledTypes(),
                                                           sync_service);
diff --git a/ios/web_view/internal/web_view_web_main_parts.mm b/ios/web_view/internal/web_view_web_main_parts.mm
index 9860c03..1539afc 100644
--- a/ios/web_view/internal/web_view_web_main_parts.mm
+++ b/ios/web_view/internal/web_view_web_main_parts.mm
@@ -50,8 +50,11 @@
 
 #if BUILDFLAG(IOS_WEB_VIEW_ENABLE_SYNC)
   std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
-  std::string enable_features =
-      base::JoinString({autofill::features::kAutofillUpstream.name}, ",");
+  std::string enable_features = base::JoinString(
+      {autofill::features::kAutofillUpstream.name,
+       autofill::features::kAutofillNoLocalSaveOnUploadSuccess.name,
+       autofill::features::kAutofillNoLocalSaveOnUnmaskSuccess.name},
+      ",");
   std::string disabled_features = base::JoinString(
       {// Allows form_structure.cc to run heuristics on single field forms.
        // This is needed to find autofillable password forms with less than 3
diff --git a/ios/web_view/shell/shell_view_controller.m b/ios/web_view/shell/shell_view_controller.m
index c820966..7060648 100644
--- a/ios/web_view/shell/shell_view_controller.m
+++ b/ios/web_view/shell/shell_view_controller.m
@@ -339,6 +339,10 @@
                          message:message
                   preferredStyle:UIAlertControllerStyleActionSheet];
     for (CWVCreditCard* creditCard in creditCards) {
+      // Cards from Google Play can only be deleted on the Google Pay website.
+      if (creditCard.fromGooglePay) {
+        continue;
+      }
       NSString* title =
           [NSString stringWithFormat:@"Delete %@",
                                      @([creditCards indexOfObject:creditCard])];
@@ -350,6 +354,27 @@
                                  }];
       [alertController addAction:action];
     }
+    __weak ShellViewController* weakSelf = self;
+    [alertController
+        addAction:[UIAlertAction
+                      actionWithTitle:@"Manage Google pay cards"
+                                style:UIAlertActionStyleDefault
+                              handler:^(UIAlertAction* action) {
+                                __weak ShellViewController* strongSelf =
+                                    weakSelf;
+                                NSString* URL;
+                                if ([CWVFlags sharedInstance]
+                                        .usesSyncAndWalletSandbox) {
+                                  URL = @"https://pay.sandbox.google.com/"
+                                        @"payments/home#paymentMethods";
+                                } else {
+                                  URL = @"https://pay.google.com/payments/"
+                                        @"home#paymentMethods";
+                                }
+                                NSURLRequest* request = [NSURLRequest
+                                    requestWithURL:[NSURL URLWithString:URL]];
+                                [strongSelf.webView loadRequest:request];
+                              }]];
     [alertController
         addAction:[UIAlertAction actionWithTitle:@"Done"
                                            style:UIAlertActionStyleCancel
diff --git a/media/audio/alive_checker.cc b/media/audio/alive_checker.cc
index 66dce3d9..a369d464 100644
--- a/media/audio/alive_checker.cc
+++ b/media/audio/alive_checker.cc
@@ -63,16 +63,17 @@
     if (power_observer_helper_factory_callback.is_null()) {
       power_observer_ = std::make_unique<PowerObserverHelper>(
           task_runner_, base::DoNothing(),
-          base::Bind(
+          base::BindRepeating(
               &AliveChecker::SetLastAliveNotificationTimeToNowOnTaskRunner,
               base::Unretained(this)));
     } else {
       power_observer_ =
           std::move(power_observer_helper_factory_callback)
               .Run(task_runner_, base::DoNothing(),
-                   base::Bind(&AliveChecker::
-                                  SetLastAliveNotificationTimeToNowOnTaskRunner,
-                              base::Unretained(this)));
+                   base::BindRepeating(
+                       &AliveChecker::
+                           SetLastAliveNotificationTimeToNowOnTaskRunner,
+                       base::Unretained(this)));
     }
   } else {
     // If |pause_check_during_suspend| is false, we expect an empty factory
diff --git a/media/audio/audio_debug_file_writer.cc b/media/audio/audio_debug_file_writer.cc
index 2f78df5..8fd566a 100644
--- a/media/audio/audio_debug_file_writer.cc
+++ b/media/audio/audio_debug_file_writer.cc
@@ -289,8 +289,9 @@
   file_task_runner_->PostTask(
       FROM_HERE,
       // Callback takes ownership of |data|:
-      base::Bind(&AudioFileWriter::Write, base::Unretained(file_writer_.get()),
-                 base::Owned(data.release())));
+      base::BindOnce(&AudioFileWriter::Write,
+                     base::Unretained(file_writer_.get()),
+                     base::Owned(data.release())));
 }
 
 bool AudioDebugFileWriter::WillWrite() {
diff --git a/media/audio/audio_input_device.cc b/media/audio/audio_input_device.cc
index 1deb93a..26060207 100644
--- a/media/audio/audio_input_device.cc
+++ b/media/audio/audio_input_device.cc
@@ -256,7 +256,7 @@
   const bool pause_check_during_suspend = true;
 #endif
   alive_checker_ = std::make_unique<AliveChecker>(
-      base::Bind(&AudioInputDevice::DetectedDeadInputStream, this),
+      base::BindRepeating(&AudioInputDevice::DetectedDeadInputStream, this),
       base::TimeDelta::FromSeconds(kCheckMissingCallbacksIntervalSeconds),
       base::TimeDelta::FromSeconds(kMissingCallbacksTimeBeforeErrorSeconds),
       stop_at_first_alive_notification, pause_check_during_suspend);
diff --git a/media/audio/audio_input_unittest.cc b/media/audio/audio_input_unittest.cc
index 3763e79..ea9a28a5 100644
--- a/media/audio/audio_input_unittest.cc
+++ b/media/audio/audio_input_unittest.cc
@@ -85,42 +85,35 @@
   }
 
   void MakeAudioInputStreamOnAudioThread() {
-    RunOnAudioThread(
-        base::Bind(&AudioInputTest::MakeAudioInputStream,
-                   base::Unretained(this)));
+    RunOnAudioThread(base::BindOnce(&AudioInputTest::MakeAudioInputStream,
+                                    base::Unretained(this)));
   }
 
   void CloseAudioInputStreamOnAudioThread() {
-    RunOnAudioThread(
-        base::Bind(&AudioInputStream::Close,
-                   base::Unretained(audio_input_stream_)));
+    RunOnAudioThread(base::BindOnce(&AudioInputStream::Close,
+                                    base::Unretained(audio_input_stream_)));
     audio_input_stream_ = NULL;
   }
 
   void OpenAndCloseAudioInputStreamOnAudioThread() {
     RunOnAudioThread(
-        base::Bind(&AudioInputTest::OpenAndClose,
-                   base::Unretained(this)));
+        base::BindOnce(&AudioInputTest::OpenAndClose, base::Unretained(this)));
   }
 
   void OpenStopAndCloseAudioInputStreamOnAudioThread() {
-    RunOnAudioThread(
-        base::Bind(&AudioInputTest::OpenStopAndClose,
-                   base::Unretained(this)));
+    RunOnAudioThread(base::BindOnce(&AudioInputTest::OpenStopAndClose,
+                                    base::Unretained(this)));
   }
 
   void OpenAndStartAudioInputStreamOnAudioThread(
       AudioInputStream::AudioInputCallback* sink) {
-    RunOnAudioThread(
-        base::Bind(&AudioInputTest::OpenAndStart,
-                   base::Unretained(this),
-                   sink));
+    RunOnAudioThread(base::BindOnce(&AudioInputTest::OpenAndStart,
+                                    base::Unretained(this), sink));
   }
 
   void StopAndCloseAudioInputStreamOnAudioThread() {
     RunOnAudioThread(
-        base::Bind(&AudioInputTest::StopAndClose,
-                   base::Unretained(this)));
+        base::BindOnce(&AudioInputTest::StopAndClose, base::Unretained(this)));
   }
 
   void MakeAudioInputStream() {
@@ -163,9 +156,9 @@
   }
 
   // Synchronously runs the provided callback/closure on the audio thread.
-  void RunOnAudioThread(const base::Closure& closure) {
+  void RunOnAudioThread(base::OnceClosure closure) {
     DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
-    closure.Run();
+    std::move(closure).Run();
   }
 
   void OnLogMessage(const std::string& message) {}
diff --git a/media/audio/audio_low_latency_input_output_unittest.cc b/media/audio/audio_low_latency_input_output_unittest.cc
index c1042cc..2fb5767 100644
--- a/media/audio/audio_low_latency_input_output_unittest.cc
+++ b/media/audio/audio_low_latency_input_output_unittest.cc
@@ -253,7 +253,7 @@
       const AudioParameters& params) {
     return audio_manager->MakeAudioInputStream(
         params, AudioDeviceDescription::kDefaultDeviceId,
-        base::Bind(&OnLogMessage));
+        base::BindRepeating(&OnLogMessage));
   }
 };
 
@@ -269,8 +269,8 @@
 
   static StreamType* CreateStream(AudioManager* audio_manager,
       const AudioParameters& params) {
-    return audio_manager->MakeAudioOutputStream(params, std::string(),
-                                                base::Bind(&OnLogMessage));
+    return audio_manager->MakeAudioOutputStream(
+        params, std::string(), base::BindRepeating(&OnLogMessage));
   }
 };
 
diff --git a/media/audio/audio_output_resampler.cc b/media/audio/audio_output_resampler.cc
index 546a58b..5959825 100644
--- a/media/audio/audio_output_resampler.cc
+++ b/media/audio/audio_output_resampler.cc
@@ -184,10 +184,11 @@
       output_params_(output_params),
       original_output_params_(output_params),
       device_id_(output_device_id),
-      reinitialize_timer_(FROM_HERE,
-                          close_delay_,
-                          base::Bind(&AudioOutputResampler::Reinitialize,
-                                     base::Unretained(this))),
+      reinitialize_timer_(
+          FROM_HERE,
+          close_delay_,
+          base::BindRepeating(&AudioOutputResampler::Reinitialize,
+                              base::Unretained(this))),
       register_debug_recording_source_callback_(
           register_debug_recording_source_callback) {
   DCHECK(audio_manager->GetTaskRunner()->BelongsToCurrentThread());
diff --git a/media/base/BUILD.gn b/media/base/BUILD.gn
index 247f0f8..a4733e86 100644
--- a/media/base/BUILD.gn
+++ b/media/base/BUILD.gn
@@ -414,7 +414,7 @@
 if (is_android) {
   java_cpp_enum("java_enums") {
     sources = [
-      "decrypt_config.h",
+      "encryption_scheme.h",
       "video_codecs.h",
     ]
   }
diff --git a/media/base/android/java/src/org/chromium/media/MediaCodecBridge.java b/media/base/android/java/src/org/chromium/media/MediaCodecBridge.java
index 9d87a7599f2..54386a7 100644
--- a/media/base/android/java/src/org/chromium/media/MediaCodecBridge.java
+++ b/media/base/android/java/src/org/chromium/media/MediaCodecBridge.java
@@ -556,16 +556,16 @@
         }
     }
 
-    // Incoming |native| values are as defined in media/base/decrypt_config.h. Translated values
+    // Incoming |native| values are as defined in media/base/encryption_scheme.h. Translated values
     // are from MediaCodec. At present, these values are in sync. Returns
     // MEDIA_CODEC_UNKNOWN_CIPHER_MODE in the case of unknown incoming value.
-    private int translateEncryptionModeValue(int nativeValue) {
+    private int translateEncryptionSchemeValue(int nativeValue) {
         switch (nativeValue) {
-            case EncryptionMode.UNENCRYPTED:
+            case EncryptionScheme.UNENCRYPTED:
                 return MediaCodec.CRYPTO_MODE_UNENCRYPTED;
-            case EncryptionMode.CENC:
+            case EncryptionScheme.CENC:
                 return MediaCodec.CRYPTO_MODE_AES_CTR;
-            case EncryptionMode.CBCS:
+            case EncryptionScheme.CBCS:
                 return MediaCodec.CRYPTO_MODE_AES_CBC;
             default:
                 Log.e(TAG, "Unsupported cipher mode: " + nativeValue);
@@ -579,7 +579,7 @@
             int[] numBytesOfClearData, int[] numBytesOfEncryptedData, int numSubSamples,
             int cipherMode, int patternEncrypt, int patternSkip, long presentationTimeUs) {
         try {
-            cipherMode = translateEncryptionModeValue(cipherMode);
+            cipherMode = translateEncryptionSchemeValue(cipherMode);
             if (cipherMode == MEDIA_CODEC_UNKNOWN_CIPHER_MODE) {
                 return MediaCodecStatus.ERROR;
             }
diff --git a/media/base/android/media_codec_bridge.h b/media/base/android/media_codec_bridge.h
index 86b02cb..f9cb49f 100644
--- a/media/base/android/media_codec_bridge.h
+++ b/media/base/android/media_codec_bridge.h
@@ -14,9 +14,10 @@
 #include "base/android/jni_android.h"
 #include "base/android/scoped_java_ref.h"
 #include "base/macros.h"
+#include "base/optional.h"
 #include "base/time/time.h"
-#include "media/base/decrypt_config.h"
 #include "media/base/encryption_pattern.h"
+#include "media/base/encryption_scheme.h"
 #include "media/base/media_export.h"
 #include "ui/gfx/geometry/size.h"
 
@@ -93,7 +94,7 @@
       const std::string& key_id,
       const std::string& iv,
       const std::vector<SubsampleEntry>& subsamples,
-      EncryptionMode encryption_scheme,
+      EncryptionScheme encryption_scheme,
       base::Optional<EncryptionPattern> encryption_pattern,
       base::TimeDelta presentation_time) = 0;
 
diff --git a/media/base/android/media_codec_bridge_impl.cc b/media/base/android/media_codec_bridge_impl.cc
index a5aa079..48350424 100644
--- a/media/base/android/media_codec_bridge_impl.cc
+++ b/media/base/android/media_codec_bridge_impl.cc
@@ -416,7 +416,7 @@
     const std::string& key_id,
     const std::string& iv,
     const std::vector<SubsampleEntry>& subsamples,
-    EncryptionMode encryption_scheme,
+    EncryptionScheme encryption_scheme,
     base::Optional<EncryptionPattern> encryption_pattern,
     base::TimeDelta presentation_time) {
   DVLOG(3) << __func__ << " " << index << ": " << data_size;
diff --git a/media/base/android/media_codec_bridge_impl.h b/media/base/android/media_codec_bridge_impl.h
index ae44336..35c224c7 100644
--- a/media/base/android/media_codec_bridge_impl.h
+++ b/media/base/android/media_codec_bridge_impl.h
@@ -121,7 +121,7 @@
       const std::string& key_id,
       const std::string& iv,
       const std::vector<SubsampleEntry>& subsamples,
-      EncryptionMode encryption_scheme,
+      EncryptionScheme encryption_scheme,
       base::Optional<EncryptionPattern> encryption_pattern,
       base::TimeDelta presentation_time) override;
   void QueueEOS(int input_buffer_index) override;
diff --git a/media/base/android/media_codec_bridge_impl_unittest.cc b/media/base/android/media_codec_bridge_impl_unittest.cc
index 8278cf1..60328e5 100644
--- a/media/base/android/media_codec_bridge_impl_unittest.cc
+++ b/media/base/android/media_codec_bridge_impl_unittest.cc
@@ -281,7 +281,8 @@
     int64_t codec_delay = 0) {
   AudioDecoderConfig config;
   config.Initialize(codec, kSampleFormatPlanarF32, CHANNEL_LAYOUT_STEREO, 44100,
-                    extra_data, Unencrypted(), seek_preroll, codec_delay);
+                    extra_data, EncryptionScheme::kUnencrypted, seek_preroll,
+                    codec_delay);
   return config;
 }
 
diff --git a/media/base/android/media_codec_loop.cc b/media/base/android/media_codec_loop.cc
index 61b9a996..5722736 100644
--- a/media/base/android/media_codec_loop.cc
+++ b/media/base/android/media_codec_loop.cc
@@ -197,7 +197,7 @@
 
   media::MediaCodecStatus status = MEDIA_CODEC_OK;
 
-  if (input_data.encryption_scheme != EncryptionMode::kUnencrypted) {
+  if (input_data.encryption_scheme != EncryptionScheme::kUnencrypted) {
     // Note that input_data might not have a valid memory ptr if this is a
     // re-send of a buffer that was sent before decryption keys arrived.
 
diff --git a/media/base/android/media_codec_loop.h b/media/base/android/media_codec_loop.h
index ebb8106..3eb0d81 100644
--- a/media/base/android/media_codec_loop.h
+++ b/media/base/android/media_codec_loop.h
@@ -127,7 +127,7 @@
     base::TimeDelta presentation_time;
 
     bool is_eos = false;
-    EncryptionMode encryption_scheme = EncryptionMode::kUnencrypted;
+    EncryptionScheme encryption_scheme = EncryptionScheme::kUnencrypted;
     base::Optional<EncryptionPattern> encryption_pattern;
   };
 
diff --git a/media/base/android/mock_media_codec_bridge.h b/media/base/android/mock_media_codec_bridge.h
index a2e03978..85325f56 100644
--- a/media/base/android/mock_media_codec_bridge.h
+++ b/media/base/android/mock_media_codec_bridge.h
@@ -43,7 +43,7 @@
                        const std::string& key_id,
                        const std::string& iv,
                        const std::vector<SubsampleEntry>& subsamples,
-                       EncryptionMode encryption_scheme,
+                       EncryptionScheme encryption_scheme,
                        base::Optional<EncryptionPattern> encryption_pattern,
                        base::TimeDelta presentation_time));
   MOCK_METHOD1(QueueEOS, void(int input_buffer_index));
diff --git a/media/base/audio_decoder_config.cc b/media/base/audio_decoder_config.cc
index f499bc5..27351dc2 100644
--- a/media/base/audio_decoder_config.cc
+++ b/media/base/audio_decoder_config.cc
@@ -12,13 +12,12 @@
 
 AudioDecoderConfig::AudioDecoderConfig() {}
 
-AudioDecoderConfig::AudioDecoderConfig(
-    AudioCodec codec,
-    SampleFormat sample_format,
-    ChannelLayout channel_layout,
-    int samples_per_second,
-    const std::vector<uint8_t>& extra_data,
-    const EncryptionScheme& encryption_scheme) {
+AudioDecoderConfig::AudioDecoderConfig(AudioCodec codec,
+                                       SampleFormat sample_format,
+                                       ChannelLayout channel_layout,
+                                       int samples_per_second,
+                                       const std::vector<uint8_t>& extra_data,
+                                       EncryptionScheme encryption_scheme) {
   Initialize(codec, sample_format, channel_layout, samples_per_second,
              extra_data, encryption_scheme, base::TimeDelta(), 0);
 }
@@ -31,7 +30,7 @@
                                     ChannelLayout channel_layout,
                                     int samples_per_second,
                                     const std::vector<uint8_t>& extra_data,
-                                    const EncryptionScheme& encryption_scheme,
+                                    EncryptionScheme encryption_scheme,
                                     base::TimeDelta seek_preroll,
                                     int codec_delay) {
   codec_ = codec;
@@ -71,7 +70,7 @@
           (channel_layout() == config.channel_layout()) &&
           (samples_per_second() == config.samples_per_second()) &&
           (extra_data() == config.extra_data()) &&
-          (encryption_scheme().Matches(config.encryption_scheme())) &&
+          (encryption_scheme() == config.encryption_scheme()) &&
           (sample_format() == config.sample_format()) &&
           (seek_preroll() == config.seek_preroll()) &&
           (codec_delay() == config.codec_delay()) &&
@@ -109,16 +108,17 @@
 
 void AudioDecoderConfig::SetIsEncrypted(bool is_encrypted) {
   if (!is_encrypted) {
-    DCHECK(encryption_scheme_.is_encrypted()) << "Config is already clear.";
-    encryption_scheme_ = Unencrypted();
+    DCHECK_NE(encryption_scheme_, EncryptionScheme::kUnencrypted)
+        << "Config is already clear.";
+    encryption_scheme_ = EncryptionScheme::kUnencrypted;
   } else {
-    DCHECK(!encryption_scheme_.is_encrypted())
+    DCHECK_EQ(encryption_scheme_, EncryptionScheme::kUnencrypted)
         << "Config is already encrypted.";
     // TODO(xhwang): This is only used to guide decoder selection, so set
     // a common encryption scheme that should be supported by all decrypting
     // decoders. We should be able to remove this when we support switching
     // decoders at run time. See http://crbug.com/695595
-    encryption_scheme_ = AesCtrEncryptionScheme();
+    encryption_scheme_ = EncryptionScheme::kCenc;
   }
 }
 
diff --git a/media/base/audio_decoder_config.h b/media/base/audio_decoder_config.h
index b251bf7..a7f294d 100644
--- a/media/base/audio_decoder_config.h
+++ b/media/base/audio_decoder_config.h
@@ -35,7 +35,7 @@
                      ChannelLayout channel_layout,
                      int samples_per_second,
                      const std::vector<uint8_t>& extra_data,
-                     const EncryptionScheme& encryption_scheme);
+                     EncryptionScheme encryption_scheme);
 
   AudioDecoderConfig(const AudioDecoderConfig& other);
 
@@ -47,7 +47,7 @@
                   ChannelLayout channel_layout,
                   int samples_per_second,
                   const std::vector<uint8_t>& extra_data,
-                  const EncryptionScheme& encryption_scheme,
+                  EncryptionScheme encryption_scheme,
                   base::TimeDelta seek_preroll,
                   int codec_delay);
 
@@ -83,12 +83,12 @@
   // Whether the audio stream is potentially encrypted.
   // Note that in a potentially encrypted audio stream, individual buffers
   // can be encrypted or not encrypted.
-  bool is_encrypted() const { return encryption_scheme_.is_encrypted(); }
+  bool is_encrypted() const {
+    return encryption_scheme_ != EncryptionScheme::kUnencrypted;
+  }
 
   // Encryption scheme used for encrypted buffers.
-  const EncryptionScheme& encryption_scheme() const {
-    return encryption_scheme_;
-  }
+  EncryptionScheme encryption_scheme() const { return encryption_scheme_; }
 
   // Sets the config to be encrypted or not encrypted manually. This can be
   // useful for decryptors that decrypts an encrypted stream to a clear stream.
@@ -119,7 +119,7 @@
   int samples_per_second_ = 0;
   int bytes_per_frame_ = 0;
   std::vector<uint8_t> extra_data_;
-  EncryptionScheme encryption_scheme_;
+  EncryptionScheme encryption_scheme_ = EncryptionScheme::kUnencrypted;
 
   // Layout and count of the *stream* being decoded.
   ChannelLayout channel_layout_ = CHANNEL_LAYOUT_UNSUPPORTED;
diff --git a/media/base/bitstream_buffer.h b/media/base/bitstream_buffer.h
index ca5333a6..9280c80 100644
--- a/media/base/bitstream_buffer.h
+++ b/media/base/bitstream_buffer.h
@@ -76,7 +76,7 @@
   scoped_refptr<DecoderBuffer> ToDecoderBuffer();
 
   // TODO(crbug.com/813845): As this is only used by Android, include
-  // EncryptionMode and optional EncryptionPattern when updating for Android.
+  // EncryptionScheme and optional EncryptionPattern when updating for Android.
   void SetDecryptionSettings(const std::string& key_id,
                              const std::string& iv,
                              const std::vector<SubsampleEntry>& subsamples);
diff --git a/media/base/decrypt_config.cc b/media/base/decrypt_config.cc
index 385e159..ce994949 100644
--- a/media/base/decrypt_config.cc
+++ b/media/base/decrypt_config.cc
@@ -13,29 +13,12 @@
 
 namespace media {
 
-namespace {
-
-const char* EncryptionModeAsString(EncryptionMode mode) {
-  switch (mode) {
-    case EncryptionMode::kUnencrypted:
-      return "Unencrypted";
-    case EncryptionMode::kCenc:
-      return "CENC";
-    case EncryptionMode::kCbcs:
-      return "CBCS";
-    default:
-      return "Unknown";
-  }
-}
-
-}  // namespace
-
 // static
 std::unique_ptr<DecryptConfig> DecryptConfig::CreateCencConfig(
     const std::string& key_id,
     const std::string& iv,
     const std::vector<SubsampleEntry>& subsamples) {
-  return std::make_unique<DecryptConfig>(EncryptionMode::kCenc, key_id, iv,
+  return std::make_unique<DecryptConfig>(EncryptionScheme::kCenc, key_id, iv,
                                          subsamples, base::nullopt);
 }
 
@@ -45,29 +28,29 @@
     const std::string& iv,
     const std::vector<SubsampleEntry>& subsamples,
     base::Optional<EncryptionPattern> encryption_pattern) {
-  return std::make_unique<DecryptConfig>(EncryptionMode::kCbcs, key_id, iv,
+  return std::make_unique<DecryptConfig>(EncryptionScheme::kCbcs, key_id, iv,
                                          subsamples,
                                          std::move(encryption_pattern));
 }
 
 DecryptConfig::DecryptConfig(
-    const EncryptionMode& encryption_mode,
+    EncryptionScheme encryption_scheme,
     const std::string& key_id,
     const std::string& iv,
     const std::vector<SubsampleEntry>& subsamples,
     base::Optional<EncryptionPattern> encryption_pattern)
-    : encryption_mode_(encryption_mode),
+    : encryption_scheme_(encryption_scheme),
       key_id_(key_id),
       iv_(iv),
       subsamples_(subsamples),
       encryption_pattern_(std::move(encryption_pattern)) {
   // Unencrypted blocks should not have a DecryptConfig.
-  DCHECK_NE(encryption_mode_, EncryptionMode::kUnencrypted);
+  DCHECK_NE(encryption_scheme_, EncryptionScheme::kUnencrypted);
   CHECK_GT(key_id_.size(), 0u);
   CHECK_EQ(iv_.size(), static_cast<size_t>(DecryptConfig::kDecryptionKeySize));
 
-  // Pattern not allowed for non-'cbcs' modes.
-  DCHECK(encryption_mode_ == EncryptionMode::kCbcs || !encryption_pattern_);
+  // Pattern not allowed for non-'cbcs' schemes.
+  DCHECK(encryption_scheme_ == EncryptionScheme::kCbcs || !encryption_pattern_);
 }
 
 DecryptConfig::~DecryptConfig() = default;
@@ -79,7 +62,7 @@
 std::unique_ptr<DecryptConfig> DecryptConfig::CopyNewSubsamplesIV(
     const std::vector<SubsampleEntry>& subsamples,
     const std::string& iv) {
-  return std::make_unique<DecryptConfig>(encryption_mode_, key_id_, iv,
+  return std::make_unique<DecryptConfig>(encryption_scheme_, key_id_, iv,
                                          subsamples, encryption_pattern_);
 }
 
@@ -90,7 +73,7 @@
 bool DecryptConfig::Matches(const DecryptConfig& config) const {
   if (key_id() != config.key_id() || iv() != config.iv() ||
       subsamples().size() != config.subsamples().size() ||
-      encryption_mode_ != config.encryption_mode_ ||
+      encryption_scheme_ != config.encryption_scheme_ ||
       encryption_pattern_ != config.encryption_pattern_) {
     return false;
   }
@@ -108,7 +91,7 @@
 std::ostream& DecryptConfig::Print(std::ostream& os) const {
   os << "key_id:'" << base::HexEncode(key_id_.data(), key_id_.size()) << "'"
      << " iv:'" << base::HexEncode(iv_.data(), iv_.size()) << "'"
-     << " mode:" << EncryptionModeAsString(encryption_mode_);
+     << " scheme:" << encryption_scheme_;
 
   if (encryption_pattern_) {
     os << " pattern:" << encryption_pattern_->crypt_byte_block() << ":"
diff --git a/media/base/decrypt_config.h b/media/base/decrypt_config.h
index cdc4c21..79905419 100644
--- a/media/base/decrypt_config.h
+++ b/media/base/decrypt_config.h
@@ -15,21 +15,12 @@
 #include "base/macros.h"
 #include "base/optional.h"
 #include "media/base/encryption_pattern.h"
+#include "media/base/encryption_scheme.h"
 #include "media/base/media_export.h"
 #include "media/base/subsample_entry.h"
 
 namespace media {
 
-// The encryption mode. The definitions are from ISO/IEC 23001-7:2016.
-// TODO(crbug.com/825041): Merge this with existing media::EncryptionScheme.
-// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.media
-enum class EncryptionMode {
-  kUnencrypted = 0,
-  kCenc,  // 'cenc' subsample encryption using AES-CTR mode.
-  kCbcs,  // 'cbcs' pattern encryption using AES-CBC mode.
-  kMaxValue = kCbcs
-};
-
 // Contains all information that a decryptor needs to decrypt a media sample.
 class MEDIA_EXPORT DecryptConfig {
  public:
@@ -58,7 +49,7 @@
       const std::vector<SubsampleEntry>& subsamples,
       base::Optional<EncryptionPattern> encryption_pattern);
 
-  DecryptConfig(const EncryptionMode& encryption_mode,
+  DecryptConfig(EncryptionScheme encryption_scheme,
                 const std::string& key_id,
                 const std::string& iv,
                 const std::vector<SubsampleEntry>& subsamples,
@@ -68,7 +59,7 @@
   const std::string& key_id() const { return key_id_; }
   const std::string& iv() const { return iv_; }
   const std::vector<SubsampleEntry>& subsamples() const { return subsamples_; }
-  const EncryptionMode& encryption_mode() const { return encryption_mode_; }
+  EncryptionScheme encryption_scheme() const { return encryption_scheme_; }
   const base::Optional<EncryptionPattern>& encryption_pattern() const {
     return encryption_pattern_;
   }
@@ -93,7 +84,7 @@
  private:
   DecryptConfig(const DecryptConfig& other);
 
-  const EncryptionMode encryption_mode_;
+  const EncryptionScheme encryption_scheme_;
   const std::string key_id_;
 
   // Initialization vector.
diff --git a/media/base/decrypt_config_unittest.cc b/media/base/decrypt_config_unittest.cc
index a42266d..e39d7a7 100644
--- a/media/base/decrypt_config_unittest.cc
+++ b/media/base/decrypt_config_unittest.cc
@@ -26,7 +26,7 @@
   EXPECT_EQ(config->key_id(), kDefaultKeyId);
   EXPECT_EQ(config->iv(), kDefaultIV);
   EXPECT_EQ(config->subsamples().size(), 0u);
-  EXPECT_EQ(config->encryption_mode(), EncryptionMode::kCenc);
+  EXPECT_EQ(config->encryption_scheme(), EncryptionScheme::kCenc);
 
   // Now with single subsample entry.
   config =
@@ -36,7 +36,7 @@
   EXPECT_EQ(config->subsamples().size(), 1u);
   EXPECT_EQ(config->subsamples()[0].clear_bytes, 1u);
   EXPECT_EQ(config->subsamples()[0].cypher_bytes, 2u);
-  EXPECT_EQ(config->encryption_mode(), EncryptionMode::kCenc);
+  EXPECT_EQ(config->encryption_scheme(), EncryptionScheme::kCenc);
 
   // Now with multiple subsample entries.
   config = DecryptConfig::CreateCencConfig(kDefaultKeyId, kAlternateIV,
@@ -46,7 +46,7 @@
   EXPECT_EQ(config->subsamples().size(), 4u);
   EXPECT_EQ(config->subsamples()[1].clear_bytes, 3u);
   EXPECT_EQ(config->subsamples()[3].cypher_bytes, 8u);
-  EXPECT_EQ(config->encryption_mode(), EncryptionMode::kCenc);
+  EXPECT_EQ(config->encryption_scheme(), EncryptionScheme::kCenc);
 }
 
 TEST(DecryptConfigTest, CbcsConstruction) {
@@ -55,7 +55,7 @@
   EXPECT_EQ(config->key_id(), kDefaultKeyId);
   EXPECT_EQ(config->iv(), kDefaultIV);
   EXPECT_EQ(config->subsamples().size(), 0u);
-  EXPECT_EQ(config->encryption_mode(), EncryptionMode::kCbcs);
+  EXPECT_EQ(config->encryption_scheme(), EncryptionScheme::kCbcs);
   EXPECT_TRUE(config->HasPattern());
   EXPECT_EQ(config->encryption_pattern()->crypt_byte_block(), 1u);
   EXPECT_EQ(config->encryption_pattern()->skip_byte_block(), 2u);
@@ -71,7 +71,7 @@
   EXPECT_EQ(config->subsamples()[0].cypher_bytes, 2u);
   EXPECT_EQ(config->subsamples()[3].clear_bytes, 7u);
   EXPECT_EQ(config->subsamples()[3].cypher_bytes, 8u);
-  EXPECT_EQ(config->encryption_mode(), EncryptionMode::kCbcs);
+  EXPECT_EQ(config->encryption_scheme(), EncryptionScheme::kCbcs);
   EXPECT_TRUE(config->HasPattern());
   EXPECT_EQ(config->encryption_pattern()->crypt_byte_block(), 1u);
   EXPECT_EQ(config->encryption_pattern()->skip_byte_block(), 0u);
@@ -84,7 +84,7 @@
   EXPECT_EQ(config->subsamples().size(), 1u);
   EXPECT_EQ(config->subsamples()[0].clear_bytes, 1u);
   EXPECT_EQ(config->subsamples()[0].cypher_bytes, 2u);
-  EXPECT_EQ(config->encryption_mode(), EncryptionMode::kCbcs);
+  EXPECT_EQ(config->encryption_scheme(), EncryptionScheme::kCbcs);
   EXPECT_FALSE(config->HasPattern());
 }
 
diff --git a/media/base/encryption_scheme.cc b/media/base/encryption_scheme.cc
index d20fbf4..40de184 100644
--- a/media/base/encryption_scheme.cc
+++ b/media/base/encryption_scheme.cc
@@ -10,46 +10,17 @@
 
 namespace media {
 
-EncryptionScheme::EncryptionScheme() = default;
-
-EncryptionScheme::EncryptionScheme(CipherMode mode,
-                                   const EncryptionPattern& pattern)
-    : mode_(mode), pattern_(pattern) {}
-
-EncryptionScheme::~EncryptionScheme() = default;
-
-bool EncryptionScheme::is_encrypted() const {
-  return mode_ != CIPHER_MODE_UNENCRYPTED;
-}
-
-EncryptionScheme::CipherMode EncryptionScheme::mode() const {
-  return mode_;
-}
-
-const EncryptionPattern& EncryptionScheme::pattern() const {
-  return pattern_;
-}
-
-bool EncryptionScheme::Matches(const EncryptionScheme& other) const {
-  return mode_ == other.mode_ && pattern_ == other.pattern_;
-}
-
-std::ostream& operator<<(std::ostream& os,
-                         const EncryptionScheme& encryption_scheme) {
-  if (!encryption_scheme.is_encrypted())
-    return os << "Unencrypted";
-
-  if (encryption_scheme.mode() == EncryptionScheme::CIPHER_MODE_AES_CTR)
-    return os << "CENC";
-
-  if (encryption_scheme.mode() == EncryptionScheme::CIPHER_MODE_AES_CBC) {
-    return os << "CBCS with pattern ("
-              << encryption_scheme.pattern().crypt_byte_block() << ","
-              << encryption_scheme.pattern().skip_byte_block() << ")";
+std::ostream& operator<<(std::ostream& os, EncryptionScheme scheme) {
+  switch (scheme) {
+    case EncryptionScheme::kUnencrypted:
+      return os << "Unencrypted";
+    case EncryptionScheme::kCenc:
+      return os << "CENC";
+    case EncryptionScheme::kCbcs:
+      return os << "CBCS";
+    default:
+      return os << "Unknown";
   }
-
-  NOTREACHED();
-  return os << "Unknown EncryptionScheme, mode = " << encryption_scheme.mode();
 }
 
 }  // namespace media
diff --git a/media/base/encryption_scheme.h b/media/base/encryption_scheme.h
index 420f4e7..00bd931 100644
--- a/media/base/encryption_scheme.h
+++ b/media/base/encryption_scheme.h
@@ -5,51 +5,24 @@
 #ifndef MEDIA_BASE_ENCRYPTION_SCHEME_H_
 #define MEDIA_BASE_ENCRYPTION_SCHEME_H_
 
-#include <stdint.h>
-
 #include <iosfwd>
 
-#include "media/base/encryption_pattern.h"
 #include "media/base/media_export.h"
 
 namespace media {
 
-// Specification of whether and how the stream is encrypted (in whole or part).
-class MEDIA_EXPORT EncryptionScheme {
- public:
-  // Algorithm and mode used for encryption. CIPHER_MODE_UNENCRYPTED indicates
-  // no encryption.
-  enum CipherMode {
-    CIPHER_MODE_UNENCRYPTED,
-    CIPHER_MODE_AES_CTR,
-    CIPHER_MODE_AES_CBC,
-    CIPHER_MODE_MAX = CIPHER_MODE_AES_CBC
-  };
-
-  // The default constructor makes an instance that indicates no encryption.
-  EncryptionScheme();
-
-  // This constructor allows specification of the cipher mode and the pattern.
-  EncryptionScheme(CipherMode mode, const EncryptionPattern& pattern);
-  ~EncryptionScheme();
-
-  bool Matches(const EncryptionScheme& other) const;
-
-  bool is_encrypted() const;
-  CipherMode mode() const;
-  const EncryptionPattern& pattern() const;
-
- private:
-  CipherMode mode_ = CIPHER_MODE_UNENCRYPTED;
-  EncryptionPattern pattern_;
-
-  // Allow copy and assignment.
+// The encryption mode. The definitions are from ISO/IEC 23001-7:2016.
+// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.media
+enum class EncryptionScheme {
+  kUnencrypted = 0,
+  kCenc,  // 'cenc' subsample encryption using AES-CTR mode.
+  kCbcs,  // 'cbcs' pattern encryption using AES-CBC mode.
+  kMaxValue = kCbcs
 };
 
 // For logging use only.
-MEDIA_EXPORT std::ostream& operator<<(
-    std::ostream& os,
-    const EncryptionScheme& encryption_scheme);
+MEDIA_EXPORT std::ostream& operator<<(std::ostream& os,
+                                      EncryptionScheme encryption_scheme);
 
 }  // namespace media
 
diff --git a/media/base/fake_demuxer_stream.cc b/media/base/fake_demuxer_stream.cc
index 5a6526a..dac0819 100644
--- a/media/base/fake_demuxer_stream.cc
+++ b/media/base/fake_demuxer_stream.cc
@@ -165,7 +165,7 @@
       VideoDecoderConfig::AlphaMode::kIsOpaque, VideoColorSpace(),
       kNoTransformation, next_coded_size_, kVisibleRect, next_coded_size_,
       EmptyExtraData(),
-      is_encrypted_ ? AesCtrEncryptionScheme() : Unencrypted());
+      is_encrypted_ ? EncryptionScheme::kCenc : EncryptionScheme::kUnencrypted);
   next_coded_size_.Enlarge(kWidthDelta, kHeightDelta);
 }
 
diff --git a/media/base/ipc/media_param_traits.cc b/media/base/ipc/media_param_traits.cc
index 64db44e7..46c977a7 100644
--- a/media/base/ipc/media_param_traits.cc
+++ b/media/base/ipc/media_param_traits.cc
@@ -113,28 +113,6 @@
   static void Log(const param_type& p, std::string* l);
 };
 
-void ParamTraits<media::EncryptionScheme>::Write(base::Pickle* m,
-                                                 const param_type& p) {
-  WriteParam(m, p.mode());
-  WriteParam(m, p.pattern());
-}
-
-bool ParamTraits<media::EncryptionScheme>::Read(const base::Pickle* m,
-                                                base::PickleIterator* iter,
-                                                param_type* r) {
-  media::EncryptionScheme::CipherMode mode;
-  media::EncryptionPattern pattern;
-  if (!ReadParam(m, iter, &mode) || !ReadParam(m, iter, &pattern))
-    return false;
-  *r = media::EncryptionScheme(mode, pattern);
-  return true;
-}
-
-void ParamTraits<media::EncryptionScheme>::Log(const param_type& p,
-                                               std::string* l) {
-  l->append(base::StringPrintf("<EncryptionScheme>"));
-}
-
 void ParamTraits<media::EncryptionPattern>::Write(base::Pickle* m,
                                                   const param_type& p) {
   WriteParam(m, p.crypt_byte_block());
diff --git a/media/base/ipc/media_param_traits.h b/media/base/ipc/media_param_traits.h
index b4205f5..4771d463 100644
--- a/media/base/ipc/media_param_traits.h
+++ b/media/base/ipc/media_param_traits.h
@@ -11,7 +11,6 @@
 
 namespace media {
 class AudioParameters;
-class EncryptionScheme;
 }
 
 namespace IPC {
@@ -36,16 +35,6 @@
   static void Log(const param_type& p, std::string* l);
 };
 
-template <>
-struct ParamTraits<media::EncryptionScheme> {
-  typedef media::EncryptionScheme param_type;
-  static void Write(base::Pickle* m, const param_type& p);
-  static bool Read(const base::Pickle* m,
-                   base::PickleIterator* iter,
-                   param_type* r);
-  static void Log(const param_type& p, std::string* l);
-};
-
 }  // namespace IPC
 
 #endif  // MEDIA_BASE_IPC_MEDIA_PARAM_TRAITS_H_
diff --git a/media/base/ipc/media_param_traits_macros.h b/media/base/ipc/media_param_traits_macros.h
index 966177e..805726b 100644
--- a/media/base/ipc/media_param_traits_macros.h
+++ b/media/base/ipc/media_param_traits_macros.h
@@ -104,11 +104,8 @@
 
 IPC_ENUM_TRAITS_MAX_VALUE(media::EmeInitDataType, media::EmeInitDataType::MAX)
 
-IPC_ENUM_TRAITS_MAX_VALUE(media::EncryptionMode,
-                          media::EncryptionMode::kMaxValue)
-
-IPC_ENUM_TRAITS_MAX_VALUE(media::EncryptionScheme::CipherMode,
-                          media::EncryptionScheme::CipherMode::CIPHER_MODE_MAX)
+IPC_ENUM_TRAITS_MAX_VALUE(media::EncryptionScheme,
+                          media::EncryptionScheme::kMaxValue)
 
 IPC_ENUM_TRAITS_MAX_VALUE(media::HdcpVersion,
                           media::HdcpVersion::kHdcpVersionMax)
diff --git a/media/base/key_system_properties.h b/media/base/key_system_properties.h
index c61b903..b8f1695 100644
--- a/media/base/key_system_properties.h
+++ b/media/base/key_system_properties.h
@@ -28,7 +28,7 @@
 
   // Returns the configuration rule for supporting |encryption_scheme|.
   virtual EmeConfigRule GetEncryptionSchemeConfigRule(
-      EncryptionMode encryption_scheme) const = 0;
+      EncryptionScheme encryption_scheme) const = 0;
 
   // Returns the codecs supported by this key system.
   virtual SupportedCodecs GetSupportedCodecs() const = 0;
diff --git a/media/base/key_systems.cc b/media/base/key_systems.cc
index 82c00b09..88cb98d 100644
--- a/media/base/key_systems.cc
+++ b/media/base/key_systems.cc
@@ -134,12 +134,12 @@
   }
 
   media::EmeConfigRule GetEncryptionSchemeConfigRule(
-      media::EncryptionMode encryption_scheme) const override {
+      media::EncryptionScheme encryption_scheme) const override {
     switch (encryption_scheme) {
-      case media::EncryptionMode::kCenc:
-      case media::EncryptionMode::kCbcs:
+      case media::EncryptionScheme::kCenc:
+      case media::EncryptionScheme::kCbcs:
         return media::EmeConfigRule::SUPPORTED;
-      case media::EncryptionMode::kUnencrypted:
+      case media::EncryptionScheme::kUnencrypted:
         break;
     }
     NOTREACHED();
@@ -251,7 +251,7 @@
 
   EmeConfigRule GetEncryptionSchemeConfigRule(
       const std::string& key_system,
-      EncryptionMode encryption_scheme) const override;
+      EncryptionScheme encryption_scheme) const override;
 
   EmeConfigRule GetContentTypeConfigRule(
       const std::string& key_system,
@@ -565,7 +565,7 @@
 
 EmeConfigRule KeySystemsImpl::GetEncryptionSchemeConfigRule(
     const std::string& key_system,
-    EncryptionMode encryption_scheme) const {
+    EncryptionScheme encryption_scheme) const {
   DCHECK(thread_checker_.CalledOnValidThread());
 
   auto key_system_iter = key_system_properties_map_.find(key_system);
diff --git a/media/base/key_systems.h b/media/base/key_systems.h
index 645f434..c912c1bb 100644
--- a/media/base/key_systems.h
+++ b/media/base/key_systems.h
@@ -42,7 +42,7 @@
   // Returns the configuration rule for supporting |encryption_scheme|.
   virtual EmeConfigRule GetEncryptionSchemeConfigRule(
       const std::string& key_system,
-      EncryptionMode encryption_scheme) const = 0;
+      EncryptionScheme encryption_scheme) const = 0;
 
   // Returns the configuration rule for supporting a container and a list of
   // codecs.
diff --git a/media/base/key_systems_unittest.cc b/media/base/key_systems_unittest.cc
index 3782240..29c042a 100644
--- a/media/base/key_systems_unittest.cc
+++ b/media/base/key_systems_unittest.cc
@@ -93,9 +93,9 @@
   std::string GetKeySystemName() const override { return name_; }
 
   EmeConfigRule GetEncryptionSchemeConfigRule(
-      EncryptionMode encryption_scheme) const override {
-    return (encryption_scheme == EncryptionMode::kUnencrypted ||
-            encryption_scheme == EncryptionMode::kCenc)
+      EncryptionScheme encryption_scheme) const override {
+    return (encryption_scheme == EncryptionScheme::kUnencrypted ||
+            encryption_scheme == EncryptionScheme::kCenc)
                ? EmeConfigRule::SUPPORTED
                : EmeConfigRule::NOT_SUPPORTED;
   }
@@ -125,12 +125,12 @@
   // Pretend clear (unencrypted) and 'cenc' content are always supported. But
   // 'cbcs' is not supported by hardware secure codecs.
   EmeConfigRule GetEncryptionSchemeConfigRule(
-      EncryptionMode encryption_scheme) const override {
+      EncryptionScheme encryption_scheme) const override {
     switch (encryption_scheme) {
-      case media::EncryptionMode::kUnencrypted:
-      case media::EncryptionMode::kCenc:
+      case media::EncryptionScheme::kUnencrypted:
+      case media::EncryptionScheme::kCenc:
         return media::EmeConfigRule::SUPPORTED;
-      case media::EncryptionMode::kCbcs:
+      case media::EncryptionScheme::kCbcs:
         return media::EmeConfigRule::HW_SECURE_CODECS_NOT_ALLOWED;
     }
     NOTREACHED();
@@ -170,7 +170,7 @@
 };
 
 void ExpectEncryptionSchemeConfigRule(const std::string& key_system,
-                                      EncryptionMode encryption_scheme,
+                                      EncryptionScheme encryption_scheme,
                                       EmeConfigRule expected_rule) {
   EXPECT_EQ(expected_rule,
             KeySystems::GetInstance()->GetEncryptionSchemeConfigRule(
@@ -626,11 +626,11 @@
 
 TEST_F(KeySystemsTest,
        IsSupportedKeySystem_UsesAesDecryptor_EncryptionSchemes) {
-  ExpectEncryptionSchemeConfigRule(kUsesAes, EncryptionMode::kUnencrypted,
+  ExpectEncryptionSchemeConfigRule(kUsesAes, EncryptionScheme::kUnencrypted,
                                    EmeConfigRule::SUPPORTED);
-  ExpectEncryptionSchemeConfigRule(kUsesAes, EncryptionMode::kCenc,
+  ExpectEncryptionSchemeConfigRule(kUsesAes, EncryptionScheme::kCenc,
                                    EmeConfigRule::SUPPORTED);
-  ExpectEncryptionSchemeConfigRule(kUsesAes, EncryptionMode::kCbcs,
+  ExpectEncryptionSchemeConfigRule(kUsesAes, EncryptionScheme::kCbcs,
                                    EmeConfigRule::NOT_SUPPORTED);
 }
 
@@ -760,11 +760,11 @@
   if (!CanRunExternalKeySystemTests())
     return;
 
-  ExpectEncryptionSchemeConfigRule(kExternal, EncryptionMode::kUnencrypted,
+  ExpectEncryptionSchemeConfigRule(kExternal, EncryptionScheme::kUnencrypted,
                                    EmeConfigRule::SUPPORTED);
-  ExpectEncryptionSchemeConfigRule(kExternal, EncryptionMode::kCenc,
+  ExpectEncryptionSchemeConfigRule(kExternal, EncryptionScheme::kCenc,
                                    EmeConfigRule::SUPPORTED);
-  ExpectEncryptionSchemeConfigRule(kExternal, EncryptionMode::kCbcs,
+  ExpectEncryptionSchemeConfigRule(kExternal, EncryptionScheme::kCbcs,
                                    EmeConfigRule::HW_SECURE_CODECS_NOT_ALLOWED);
 }
 
diff --git a/media/base/media_util.cc b/media/base/media_util.cc
index 501574d..66f23be 100644
--- a/media/base/media_util.cc
+++ b/media/base/media_util.cc
@@ -5,7 +5,6 @@
 #include "media/base/media_util.h"
 
 #include "base/metrics/histogram_macros.h"
-#include "media/base/encryption_pattern.h"
 
 namespace media {
 
@@ -42,15 +41,6 @@
   return std::vector<uint8_t>();
 }
 
-EncryptionScheme Unencrypted() {
-  return EncryptionScheme();
-}
-
-EncryptionScheme AesCtrEncryptionScheme() {
-  return EncryptionScheme(EncryptionScheme::CIPHER_MODE_AES_CTR,
-                          EncryptionPattern());
-}
-
 void ReportPepperVideoDecoderOutputPictureCountHW(int height) {
   UMA_HISTOGRAM_ENUMERATION("Media.PepperVideoDecoderOutputPictureCount.HW",
                             GetMediaVideoHeight(height));
diff --git a/media/base/media_util.h b/media/base/media_util.h
index d5f2600..65aee57e 100644
--- a/media/base/media_util.h
+++ b/media/base/media_util.h
@@ -9,7 +9,6 @@
 #include <vector>
 
 #include "base/macros.h"
-#include "media/base/encryption_scheme.h"
 #include "media/base/media_export.h"
 #include "media/base/media_log.h"
 
@@ -19,11 +18,6 @@
 // constructed with empty extra data.
 MEDIA_EXPORT std::vector<uint8_t> EmptyExtraData();
 
-// The following helper functions return new instances of EncryptionScheme that
-// indicate widely used settings.
-MEDIA_EXPORT EncryptionScheme Unencrypted();
-MEDIA_EXPORT EncryptionScheme AesCtrEncryptionScheme();
-
 // Helpers for PPAPI UMAs. There wasn't an obvious place to put them in
 // //content/renderer/pepper.
 MEDIA_EXPORT void ReportPepperVideoDecoderOutputPictureCountHW(int height);
diff --git a/media/base/pipeline_impl.cc b/media/base/pipeline_impl.cc
index c126e5d..89a53086 100644
--- a/media/base/pipeline_impl.cc
+++ b/media/base/pipeline_impl.cc
@@ -158,6 +158,9 @@
   void DestroyRenderer();
   void ReportMetadata(StartType start_type);
 
+  // Returns whether there's any encrypted stream in the demuxer.
+  bool HasEncryptedStream();
+
   const scoped_refptr<base::SingleThreadTaskRunner> media_task_runner_;
   const scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
   CreateRendererCB create_renderer_cb_;
@@ -192,6 +195,10 @@
   // Series of tasks to Start(), Seek(), and Resume().
   std::unique_ptr<SerialRunner> pending_callbacks_;
 
+  // Callback to store the |done_cb| when CreateRenderer() needs to wait for a
+  // CDM to be set. Should only be set in kStarting or kResuming states.
+  PipelineStatusCallback create_renderer_done_cb_;
+
   // Called from non-media threads when an error occurs.
   PipelineStatusCB error_cb_;
 
@@ -480,6 +487,17 @@
   if (!shared_state_.renderer) {
     cdm_context_ = cdm_context;
     std::move(cdm_attached_cb).Run(true);
+
+    if (create_renderer_done_cb_) {
+      DCHECK(state_ == kStarting || state_ == kResuming);
+      DVLOG(1) << __func__ << ": CDM set; continue pipeline start/resume.";
+      // Use BindToCurrentLoop to make sure OnRendererCreated() is called on the
+      // media task runner.
+      create_renderer_cb_.Run(BindToCurrentLoop(base::BindOnce(
+          &RendererWrapper::OnRendererCreated, weak_factory_.GetWeakPtr(),
+          std::move(create_renderer_done_cb_))));
+    }
+
     return;
   }
 
@@ -889,6 +907,14 @@
     PipelineStatusCallback done_cb) {
   DVLOG(1) << __func__;
   DCHECK(media_task_runner_->BelongsToCurrentThread());
+  DCHECK(state_ == kStarting || state_ == kResuming);
+
+  if (HasEncryptedStream() && !cdm_context_) {
+    DVLOG(1) << __func__ << ": Has encrypted stream but CDM is not set.";
+    create_renderer_done_cb_ = std::move(done_cb);
+    OnWaiting(WaitingReason::kNoCdm);
+    return;
+  }
 
   // Use BindToCurrentLoop to make sure OnRendererCreated() is called on the
   // media task runner.
@@ -1016,6 +1042,21 @@
       base::BindOnce(&PipelineImpl::OnSeekDone, weak_pipeline_, true));
 }
 
+bool PipelineImpl::RendererWrapper::HasEncryptedStream() {
+  auto streams = demuxer_->GetAllStreams();
+
+  for (auto* stream : streams) {
+    if (stream->type() == DemuxerStream::AUDIO &&
+        stream->audio_decoder_config().is_encrypted())
+      return true;
+    if (stream->type() == DemuxerStream::VIDEO &&
+        stream->video_decoder_config().is_encrypted())
+      return true;
+  }
+
+  return false;
+}
+
 PipelineImpl::PipelineImpl(
     scoped_refptr<base::SingleThreadTaskRunner> media_task_runner,
     scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
diff --git a/media/base/pipeline_impl_unittest.cc b/media/base/pipeline_impl_unittest.cc
index 8a99a09..61dcf1f 100644
--- a/media/base/pipeline_impl_unittest.cc
+++ b/media/base/pipeline_impl_unittest.cc
@@ -32,6 +32,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/gfx/geometry/size.h"
 
+using ::base::test::RunClosure;
 using ::base::test::RunOnceCallback;
 using ::base::test::RunOnceClosure;
 using ::testing::_;
@@ -97,6 +98,7 @@
     MOCK_METHOD1(OnSeek, void(PipelineStatus));
     MOCK_METHOD1(OnSuspend, void(PipelineStatus));
     MOCK_METHOD1(OnResume, void(PipelineStatus));
+    MOCK_METHOD1(OnCdmAttached, void(bool));
 
    private:
     DISALLOW_COPY_AND_ASSIGN(CallbackHelper);
@@ -175,7 +177,7 @@
     return stream;
   }
 
-  // Sets up expectations to allow the video renderer to initialize.
+  // Sets up expectations to allow the renderer to initialize.
   void SetRendererExpectations() {
     EXPECT_CALL(*renderer_, OnInitialize(_, _, _))
         .WillOnce(
@@ -190,6 +192,17 @@
         base::Bind(&CallbackHelper::OnStart, base::Unretained(&callbacks_)));
   }
 
+  void SetRendererPostStartExpectations() {
+    EXPECT_CALL(*renderer_, SetPlaybackRate(0.0));
+    EXPECT_CALL(*renderer_, SetVolume(1.0f));
+    EXPECT_CALL(*renderer_, StartPlayingFrom(start_time_))
+        .WillOnce(SetBufferingState(&renderer_client_, BUFFERING_HAVE_ENOUGH,
+                                    BUFFERING_CHANGE_REASON_UNKNOWN));
+    EXPECT_CALL(callbacks_,
+                OnBufferingStateChange(BUFFERING_HAVE_ENOUGH,
+                                       BUFFERING_CHANGE_REASON_UNKNOWN));
+  }
+
   // Suspension status of the pipeline post Start().
   enum class PostStartStatus {
     kNormal,
@@ -206,17 +219,8 @@
 
     if (start_status == PIPELINE_OK) {
       EXPECT_CALL(callbacks_, OnMetadata(_)).WillOnce(SaveArg<0>(&metadata_));
-      if (post_start_status == PostStartStatus::kNormal) {
-        EXPECT_CALL(*renderer_, SetPlaybackRate(0.0));
-        EXPECT_CALL(*renderer_, SetVolume(1.0f));
-        EXPECT_CALL(*renderer_, StartPlayingFrom(start_time_))
-            .WillOnce(SetBufferingState(&renderer_client_,
-                                        BUFFERING_HAVE_ENOUGH,
-                                        BUFFERING_CHANGE_REASON_UNKNOWN));
-        EXPECT_CALL(callbacks_,
-                    OnBufferingStateChange(BUFFERING_HAVE_ENOUGH,
-                                           BUFFERING_CHANGE_REASON_UNKNOWN));
-      }
+      if (post_start_status == PostStartStatus::kNormal)
+        SetRendererPostStartExpectations();
     }
 
     StartPipeline(start_type);
@@ -233,15 +237,27 @@
     audio_stream_ = CreateStream(DemuxerStream::AUDIO);
   }
 
-  void CreateVideoStream() {
+  void CreateVideoStream(bool is_encrypted = false) {
     video_stream_ = CreateStream(DemuxerStream::VIDEO);
-    video_stream_->set_video_decoder_config(video_decoder_config_);
+    video_stream_->set_video_decoder_config(
+        is_encrypted ? TestVideoConfig::NormalEncrypted()
+                     : TestVideoConfig::Normal());
   }
 
-  MockDemuxerStream* audio_stream() { return audio_stream_.get(); }
+  void CreateEncryptedVideoStream() { CreateVideoStream(true); }
 
+  MockDemuxerStream* audio_stream() { return audio_stream_.get(); }
   MockDemuxerStream* video_stream() { return video_stream_.get(); }
 
+  void SetCdmAndExpect(bool expected_result) {
+    EXPECT_CALL(*renderer_, OnSetCdm(_, _)).WillOnce(RunOnceCallback<1>(true));
+    EXPECT_CALL(callbacks_, OnCdmAttached(expected_result));
+    pipeline_->SetCdm(&cdm_context_,
+                      base::BindRepeating(&CallbackHelper::OnCdmAttached,
+                                          base::Unretained(&callbacks_)));
+    base::RunLoop().RunUntilIdle();
+  }
+
   void ExpectSeek(const base::TimeDelta& seek_time, bool underflowed) {
     EXPECT_CALL(*demuxer_, AbortPendingReads());
     EXPECT_CALL(*demuxer_, OnSeek(seek_time, _))
@@ -338,6 +354,7 @@
   base::test::SingleThreadTaskEnvironment task_environment_;
   NullMediaLog media_log_;
   std::unique_ptr<PipelineImpl> pipeline_;
+  StrictMock<MockCdmContext> cdm_context_;
 
   std::unique_ptr<StrictMock<MockDemuxer>> demuxer_;
   DemuxerHost* demuxer_host_;
@@ -531,6 +548,40 @@
   EXPECT_TRUE(metadata_.has_video);
 }
 
+TEST_F(PipelineImplTest, EncryptedStream_SetCdmBeforeStart) {
+  CreateEncryptedVideoStream();
+  MockDemuxerStreamVector streams;
+  streams.push_back(video_stream());
+  SetDemuxerExpectations(&streams);
+
+  SetCdmAndExpect(true);
+  SetRendererExpectations();
+  StartPipelineAndExpect(PIPELINE_OK);
+}
+
+TEST_F(PipelineImplTest, EncryptedStream_SetCdmAfterStart) {
+  CreateEncryptedVideoStream();
+  MockDemuxerStreamVector streams;
+  streams.push_back(video_stream());
+  SetDemuxerExpectations(&streams);
+
+  // Demuxer initialization and metadata reporting don't wait for CDM.
+  EXPECT_CALL(callbacks_, OnMetadata(_)).WillOnce(SaveArg<0>(&metadata_));
+
+  base::RunLoop run_loop;
+  EXPECT_CALL(callbacks_, OnWaiting(_))
+      .WillOnce(RunClosure(run_loop.QuitClosure()));
+  pipeline_->Start(
+      Pipeline::StartType::kNormal, demuxer_.get(), &callbacks_,
+      base::Bind(&CallbackHelper::OnStart, base::Unretained(&callbacks_)));
+  run_loop.Run();
+
+  SetRendererExpectations();
+  EXPECT_CALL(callbacks_, OnStart(PIPELINE_OK));
+  SetRendererPostStartExpectations();
+  SetCdmAndExpect(true);
+}
+
 TEST_F(PipelineImplTest, Seek) {
   CreateAudioStream();
   CreateVideoStream();
@@ -685,7 +736,6 @@
   SetRendererExpectations();
   StartPipelineAndExpect(PIPELINE_OK);
 
-
   // The ended callback shouldn't run until all renderers have ended.
   EXPECT_CALL(callbacks_, OnEnded());
   renderer_client_->OnEnded();
diff --git a/media/base/test_helpers.cc b/media/base/test_helpers.cc
index 0bc65a0..97bca27 100644
--- a/media/base/test_helpers.cc
+++ b/media/base/test_helpers.cc
@@ -139,7 +139,7 @@
       codec, profile, VideoDecoderConfig::AlphaMode::kIsOpaque, color_space,
       VideoTransformation(rotation), coded_size, visible_rect, natural_size,
       EmptyExtraData(),
-      is_encrypted ? AesCtrEncryptionScheme() : Unencrypted());
+      is_encrypted ? EncryptionScheme::kCenc : EncryptionScheme::kUnencrypted);
 }
 
 static VideoCodecProfile MinProfile(VideoCodec codec) {
@@ -243,13 +243,13 @@
 AudioDecoderConfig TestAudioConfig::Normal() {
   return AudioDecoderConfig(kCodecVorbis, kSampleFormatPlanarF32,
                             CHANNEL_LAYOUT_STEREO, 44100, EmptyExtraData(),
-                            Unencrypted());
+                            EncryptionScheme::kUnencrypted);
 }
 
 AudioDecoderConfig TestAudioConfig::NormalEncrypted() {
   return AudioDecoderConfig(kCodecVorbis, kSampleFormatPlanarF32,
                             CHANNEL_LAYOUT_STEREO, 44100, EmptyExtraData(),
-                            AesCtrEncryptionScheme());
+                            EncryptionScheme::kCenc);
 }
 
 // static
diff --git a/media/base/video_decoder_config.cc b/media/base/video_decoder_config.cc
index c98ad08..dfe1f75 100644
--- a/media/base/video_decoder_config.cc
+++ b/media/base/video_decoder_config.cc
@@ -73,17 +73,16 @@
       alpha_mode_(AlphaMode::kIsOpaque),
       transformation_(kNoTransformation) {}
 
-VideoDecoderConfig::VideoDecoderConfig(
-    VideoCodec codec,
-    VideoCodecProfile profile,
-    AlphaMode alpha_mode,
-    const VideoColorSpace& color_space,
-    VideoTransformation rotation,
-    const gfx::Size& coded_size,
-    const gfx::Rect& visible_rect,
-    const gfx::Size& natural_size,
-    const std::vector<uint8_t>& extra_data,
-    const EncryptionScheme& encryption_scheme) {
+VideoDecoderConfig::VideoDecoderConfig(VideoCodec codec,
+                                       VideoCodecProfile profile,
+                                       AlphaMode alpha_mode,
+                                       const VideoColorSpace& color_space,
+                                       VideoTransformation rotation,
+                                       const gfx::Size& coded_size,
+                                       const gfx::Rect& visible_rect,
+                                       const gfx::Size& natural_size,
+                                       const std::vector<uint8_t>& extra_data,
+                                       EncryptionScheme encryption_scheme) {
   Initialize(codec, profile, alpha_mode, color_space, rotation, coded_size,
              visible_rect, natural_size, extra_data, encryption_scheme);
 }
@@ -119,7 +118,7 @@
                                     const gfx::Rect& visible_rect,
                                     const gfx::Size& natural_size,
                                     const std::vector<uint8_t>& extra_data,
-                                    const EncryptionScheme& encryption_scheme) {
+                                    EncryptionScheme encryption_scheme) {
   codec_ = codec;
   profile_ = profile;
   alpha_mode_ = alpha_mode;
@@ -146,7 +145,7 @@
          visible_rect() == config.visible_rect() &&
          natural_size() == config.natural_size() &&
          extra_data() == config.extra_data() &&
-         encryption_scheme().Matches(config.encryption_scheme()) &&
+         encryption_scheme() == config.encryption_scheme() &&
          color_space_info() == config.color_space_info() &&
          hdr_metadata() == config.hdr_metadata();
 }
@@ -197,16 +196,17 @@
 
 void VideoDecoderConfig::SetIsEncrypted(bool is_encrypted) {
   if (!is_encrypted) {
-    DCHECK(encryption_scheme_.is_encrypted()) << "Config is already clear.";
-    encryption_scheme_ = Unencrypted();
+    DCHECK_NE(encryption_scheme_, EncryptionScheme::kUnencrypted)
+        << "Config is already clear.";
+    encryption_scheme_ = EncryptionScheme::kUnencrypted;
   } else {
-    DCHECK(!encryption_scheme_.is_encrypted())
+    DCHECK_EQ(encryption_scheme_, EncryptionScheme::kUnencrypted)
         << "Config is already encrypted.";
     // TODO(xhwang): This is only used to guide decoder selection, so set
     // a common encryption scheme that should be supported by all decrypting
     // decoders. We should be able to remove this when we support switching
     // decoders at run time. See http://crbug.com/695595
-    encryption_scheme_ = AesCtrEncryptionScheme();
+    encryption_scheme_ = EncryptionScheme::kCenc;
   }
 }
 
diff --git a/media/base/video_decoder_config.h b/media/base/video_decoder_config.h
index afe139a..e9ca279 100644
--- a/media/base/video_decoder_config.h
+++ b/media/base/video_decoder_config.h
@@ -48,7 +48,7 @@
                      const gfx::Rect& visible_rect,
                      const gfx::Size& natural_size,
                      const std::vector<uint8_t>& extra_data,
-                     const EncryptionScheme& encryption_scheme);
+                     EncryptionScheme encryption_scheme);
   VideoDecoderConfig(const VideoDecoderConfig& other);
 
   ~VideoDecoderConfig();
@@ -63,7 +63,7 @@
                   const gfx::Rect& visible_rect,
                   const gfx::Size& natural_size,
                   const std::vector<uint8_t>& extra_data,
-                  const EncryptionScheme& encryption_scheme);
+                  EncryptionScheme encryption_scheme);
 
   // Returns true if this object has appropriate configuration values, false
   // otherwise.
@@ -140,12 +140,12 @@
   // Whether the video stream is potentially encrypted.
   // Note that in a potentially encrypted video stream, individual buffers
   // can be encrypted or not encrypted.
-  bool is_encrypted() const { return encryption_scheme_.is_encrypted(); }
+  bool is_encrypted() const {
+    return encryption_scheme_ != EncryptionScheme::kUnencrypted;
+  }
 
   // Encryption scheme used for encrypted buffers.
-  const EncryptionScheme& encryption_scheme() const {
-    return encryption_scheme_;
-  }
+  EncryptionScheme encryption_scheme() const { return encryption_scheme_; }
 
   // Color space of the image data.
   void set_color_space_info(const VideoColorSpace& color_space);
@@ -175,7 +175,7 @@
 
   std::vector<uint8_t> extra_data_;
 
-  EncryptionScheme encryption_scheme_;
+  EncryptionScheme encryption_scheme_ = EncryptionScheme::kUnencrypted;
 
   VideoColorSpace color_space_info_;
   base::Optional<HDRMetadata> hdr_metadata_;
diff --git a/media/base/video_decoder_config_unittest.cc b/media/base/video_decoder_config_unittest.cc
index 636e7a4..6c083a0 100644
--- a/media/base/video_decoder_config_unittest.cc
+++ b/media/base/video_decoder_config_unittest.cc
@@ -19,14 +19,14 @@
                             VideoDecoderConfig::AlphaMode::kIsOpaque,
                             VideoColorSpace(), kNoTransformation, kCodedSize,
                             kVisibleRect, kNaturalSize, EmptyExtraData(),
-                            Unencrypted());
+                            EncryptionScheme::kUnencrypted);
   EXPECT_TRUE(config.IsValidConfig());
   EXPECT_EQ(config.alpha_mode(), VideoDecoderConfig::AlphaMode::kIsOpaque);
 
   config.Initialize(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN,
                     VideoDecoderConfig::AlphaMode::kHasAlpha, VideoColorSpace(),
                     kNoTransformation, kCodedSize, kVisibleRect, kNaturalSize,
-                    EmptyExtraData(), Unencrypted());
+                    EmptyExtraData(), EncryptionScheme::kUnencrypted);
   EXPECT_EQ(config.alpha_mode(), VideoDecoderConfig::AlphaMode::kHasAlpha);
 }
 
@@ -35,7 +35,7 @@
   VideoDecoderConfig config(
       kCodecVP8, VP8PROFILE_ANY, VideoDecoderConfig::AlphaMode::kIsOpaque,
       VideoColorSpace(), kNoTransformation, kCodedSize, kVisibleRect,
-      natural_size, EmptyExtraData(), Unencrypted());
+      natural_size, EmptyExtraData(), EncryptionScheme::kUnencrypted);
   EXPECT_FALSE(config.IsValidConfig());
 }
 
@@ -44,7 +44,7 @@
   VideoDecoderConfig config(
       kCodecVP8, VP8PROFILE_ANY, VideoDecoderConfig::AlphaMode::kIsOpaque,
       VideoColorSpace(), kNoTransformation, kCodedSize, kVisibleRect,
-      natural_size, EmptyExtraData(), Unencrypted());
+      natural_size, EmptyExtraData(), EncryptionScheme::kUnencrypted);
   EXPECT_FALSE(config.IsValidConfig());
 }
 
@@ -53,7 +53,7 @@
   VideoDecoderConfig config(
       kCodecVP8, VP8PROFILE_ANY, VideoDecoderConfig::AlphaMode::kIsOpaque,
       VideoColorSpace(), kNoTransformation, kCodedSize, kVisibleRect,
-      natural_size, EmptyExtraData(), Unencrypted());
+      natural_size, EmptyExtraData(), EncryptionScheme::kUnencrypted);
   EXPECT_FALSE(config.IsValidConfig());
 }
 
@@ -62,7 +62,7 @@
   VideoDecoderConfig config(
       kCodecVP8, VP8PROFILE_ANY, VideoDecoderConfig::AlphaMode::kIsOpaque,
       VideoColorSpace(), kNoTransformation, kCodedSize, kVisibleRect,
-      natural_size, EmptyExtraData(), Unencrypted());
+      natural_size, EmptyExtraData(), EncryptionScheme::kUnencrypted);
   EXPECT_FALSE(config.IsValidConfig());
 }
 
@@ -73,7 +73,7 @@
   VideoDecoderConfig config(
       kCodecVP8, VP8PROFILE_ANY, VideoDecoderConfig::AlphaMode::kIsOpaque,
       VideoColorSpace(), kNoTransformation, kCodedSize, kVisibleRect,
-      natural_size, EmptyExtraData(), Unencrypted());
+      natural_size, EmptyExtraData(), EncryptionScheme::kUnencrypted);
   EXPECT_FALSE(config.IsValidConfig());
 }
 
@@ -87,7 +87,7 @@
   VideoDecoderConfig config(
       kCodecVP8, VP8PROFILE_ANY, VideoDecoderConfig::AlphaMode::kIsOpaque,
       VideoColorSpace(), kNoTransformation, kCodedSize, kVisibleRect,
-      natural_size, EmptyExtraData(), Unencrypted());
+      natural_size, EmptyExtraData(), EncryptionScheme::kUnencrypted);
   EXPECT_FALSE(config.IsValidConfig());
 }
 
diff --git a/media/base/video_thumbnail_decoder_unittest.cc b/media/base/video_thumbnail_decoder_unittest.cc
index 5cdf459e..57550ae1 100644
--- a/media/base/video_thumbnail_decoder_unittest.cc
+++ b/media/base/video_thumbnail_decoder_unittest.cc
@@ -38,7 +38,7 @@
     VideoDecoderConfig valid_config(
         kCodecVP8, VP8PROFILE_ANY, VideoDecoderConfig::AlphaMode::kIsOpaque,
         VideoColorSpace(), kNoTransformation, gfx::Size(1, 1), gfx::Rect(1, 1),
-        gfx::Size(1, 1), EmptyExtraData(), Unencrypted());
+        gfx::Size(1, 1), EmptyExtraData(), EncryptionScheme::kUnencrypted);
 
     thumbnail_decoder_ = std::make_unique<VideoThumbnailDecoder>(
         std::move(mock_video_decoder), valid_config, std::vector<uint8_t>{0u});
diff --git a/media/blink/key_system_config_selector.cc b/media/blink/key_system_config_selector.cc
index faed6ec7f..6a98a4f1 100644
--- a/media/blink/key_system_config_selector.cc
+++ b/media/blink/key_system_config_selector.cc
@@ -378,11 +378,11 @@
     // compatibility and simplicity, we treat kNotSpecified the same as kCenc.
     case EmeEncryptionScheme::kNotSpecified:
     case EmeEncryptionScheme::kCenc:
-      return key_systems_->GetEncryptionSchemeConfigRule(key_system,
-                                                         EncryptionMode::kCenc);
+      return key_systems_->GetEncryptionSchemeConfigRule(
+          key_system, EncryptionScheme::kCenc);
     case EmeEncryptionScheme::kCbcs:
-      return key_systems_->GetEncryptionSchemeConfigRule(key_system,
-                                                         EncryptionMode::kCbcs);
+      return key_systems_->GetEncryptionSchemeConfigRule(
+          key_system, EncryptionScheme::kCbcs);
   }
 
   NOTREACHED();
diff --git a/media/blink/key_system_config_selector_unittest.cc b/media/blink/key_system_config_selector_unittest.cc
index 2299401..f484498 100644
--- a/media/blink/key_system_config_selector_unittest.cc
+++ b/media/blink/key_system_config_selector_unittest.cc
@@ -77,17 +77,18 @@
 constexpr EncryptionScheme kDisallowHwSecureCodecEncryptionScheme =
     EncryptionScheme::kCbcs;
 
-EncryptionMode ConvertEncryptionScheme(EncryptionScheme encryption_scheme) {
+media::EncryptionScheme ConvertEncryptionScheme(
+    EncryptionScheme encryption_scheme) {
   switch (encryption_scheme) {
     case EncryptionScheme::kNotSpecified:
     case EncryptionScheme::kCenc:
-      return EncryptionMode::kCenc;
+      return media::EncryptionScheme::kCenc;
     case EncryptionScheme::kCbcs:
-      return EncryptionMode::kCbcs;
+      return media::EncryptionScheme::kCbcs;
   }
 
   NOTREACHED();
-  return EncryptionMode::kUnencrypted;
+  return media::EncryptionScheme::kUnencrypted;
 }
 
 WebString MakeCodecs(const std::string& a, const std::string& b) {
@@ -215,7 +216,7 @@
 
   EmeConfigRule GetEncryptionSchemeConfigRule(
       const std::string& key_system,
-      EncryptionMode encryption_scheme) const override {
+      media::EncryptionScheme encryption_scheme) const override {
     if (encryption_scheme ==
         ConvertEncryptionScheme(kSupportedEncryptionScheme)) {
       return EmeConfigRule::SUPPORTED;
diff --git a/media/blink/video_decode_stats_reporter_unittest.cc b/media/blink/video_decode_stats_reporter_unittest.cc
index 7ce60a1..1dfba937 100644
--- a/media/blink/video_decode_stats_reporter_unittest.cc
+++ b/media/blink/video_decode_stats_reporter_unittest.cc
@@ -52,7 +52,7 @@
   return VideoDecoderConfig(
       codec, profile, VideoDecoderConfig::AlphaMode::kIsOpaque,
       VideoColorSpace::JPEG(), kNoTransformation, coded_size, visible_rect,
-      natural_size, EmptyExtraData(), Unencrypted());
+      natural_size, EmptyExtraData(), EncryptionScheme::kUnencrypted);
 }
 
 PipelineStatistics MakeStats(int frames_decoded,
diff --git a/media/blink/watch_time_reporter_unittest.cc b/media/blink/watch_time_reporter_unittest.cc
index 0a12e04..4cb4b2d 100644
--- a/media/blink/watch_time_reporter_unittest.cc
+++ b/media/blink/watch_time_reporter_unittest.cc
@@ -1102,8 +1102,8 @@
       has_video_ ? H264PROFILE_MAIN : VIDEO_CODEC_PROFILE_UNKNOWN,
       has_audio_ ? "FirstAudioDecoder" : "",
       has_video_ ? "FirstVideoDecoder" : "",
-      has_audio_ ? EncryptionMode::kCenc : EncryptionMode::kUnencrypted,
-      has_video_ ? EncryptionMode::kCbcs : EncryptionMode::kUnencrypted,
+      has_audio_ ? EncryptionScheme::kCenc : EncryptionScheme::kUnencrypted,
+      has_video_ ? EncryptionScheme::kCbcs : EncryptionScheme::kUnencrypted,
       has_video_ ? gfx::Size(800, 600) : gfx::Size());
 
   // Get a pointer to our original properties since we're not allowed to use
@@ -1137,7 +1137,7 @@
       .Times((has_audio_ && has_video_) ? 3 : 2);
   wtr_->UpdateSecondaryProperties(mojom::SecondaryPlaybackProperties::New(
       kUnknownAudioCodec, kUnknownVideoCodec, VIDEO_CODEC_PROFILE_UNKNOWN, "",
-      "", EncryptionMode::kUnencrypted, EncryptionMode::kUnencrypted,
+      "", EncryptionScheme::kUnencrypted, EncryptionScheme::kUnencrypted,
       kSizeJustRight));
   EXPECT_TRUE(IsMonitoring());
 
@@ -1159,7 +1159,7 @@
       .Times((has_audio_ && has_video_) ? 3 : 2);
   wtr_->UpdateSecondaryProperties(mojom::SecondaryPlaybackProperties::New(
       kUnknownAudioCodec, kUnknownVideoCodec, VIDEO_CODEC_PROFILE_UNKNOWN, "",
-      "", EncryptionMode::kUnencrypted, EncryptionMode::kUnencrypted,
+      "", EncryptionScheme::kUnencrypted, EncryptionScheme::kUnencrypted,
       kSizeTooSmall));
   EXPECT_WATCH_TIME_FINALIZED();
   CycleReportingTimer();
diff --git a/media/blink/webmediaplayer_impl.cc b/media/blink/webmediaplayer_impl.cc
index c6c8b77..657890d0 100644
--- a/media/blink/webmediaplayer_impl.cc
+++ b/media/blink/webmediaplayer_impl.cc
@@ -172,31 +172,19 @@
 
 // These values are persisted to UMA. Entries should not be renumbered and
 // numeric values should never be reused.
-// TODO(crbug.com/825041): This should use EncryptionMode when kUnencrypted
+// TODO(crbug.com/825041): This should use EncryptionScheme when kUnencrypted
 // removed.
 enum class EncryptionSchemeUMA { kCenc = 0, kCbcs = 1, kCount };
 
 EncryptionSchemeUMA DetermineEncryptionSchemeUMAValue(
-    const EncryptionScheme& encryption_scheme) {
-  if (encryption_scheme.mode() == EncryptionScheme::CIPHER_MODE_AES_CBC)
+    EncryptionScheme encryption_scheme) {
+  if (encryption_scheme == EncryptionScheme::kCbcs)
     return EncryptionSchemeUMA::kCbcs;
 
-  DCHECK_EQ(encryption_scheme.mode(), EncryptionScheme::CIPHER_MODE_AES_CTR);
+  DCHECK_EQ(encryption_scheme, EncryptionScheme::kCenc);
   return EncryptionSchemeUMA::kCenc;
 }
 
-EncryptionMode DetermineEncryptionMode(
-    const EncryptionScheme& encryption_scheme) {
-  switch (encryption_scheme.mode()) {
-    case EncryptionScheme::CIPHER_MODE_UNENCRYPTED:
-      return EncryptionMode::kUnencrypted;
-    case EncryptionScheme::CIPHER_MODE_AES_CTR:
-      return EncryptionMode::kCenc;
-    case EncryptionScheme::CIPHER_MODE_AES_CBC:
-      return EncryptionMode::kCbcs;
-  }
-}
-
 #if BUILDFLAG(ENABLE_FFMPEG)
 // Returns true if |url| represents (or is likely to) a local file.
 bool IsLocalFile(const GURL& url) {
@@ -3206,10 +3194,8 @@
           pipeline_metadata_.video_decoder_config.codec(),
           pipeline_metadata_.video_decoder_config.profile(),
           audio_decoder_name_, video_decoder_name_,
-          DetermineEncryptionMode(
-              pipeline_metadata_.audio_decoder_config.encryption_scheme()),
-          DetermineEncryptionMode(
-              pipeline_metadata_.video_decoder_config.encryption_scheme()),
+          pipeline_metadata_.audio_decoder_config.encryption_scheme(),
+          pipeline_metadata_.video_decoder_config.encryption_scheme(),
           pipeline_metadata_.natural_size));
 }
 
@@ -3585,11 +3571,11 @@
 
 void WebMediaPlayerImpl::RecordEncryptionScheme(
     const std::string& stream_name,
-    const EncryptionScheme& encryption_scheme) {
+    EncryptionScheme encryption_scheme) {
   DCHECK(stream_name == "Audio" || stream_name == "Video");
 
   // If the stream is not encrypted, don't record it.
-  if (encryption_scheme.mode() == EncryptionScheme::CIPHER_MODE_UNENCRYPTED)
+  if (encryption_scheme == EncryptionScheme::kUnencrypted)
     return;
 
   base::UmaHistogramEnumeration(
diff --git a/media/blink/webmediaplayer_impl.h b/media/blink/webmediaplayer_impl.h
index 14ebb86c..2fba773 100644
--- a/media/blink/webmediaplayer_impl.h
+++ b/media/blink/webmediaplayer_impl.h
@@ -27,6 +27,7 @@
 #include "cc/layers/surface_layer.h"
 #include "components/viz/common/gpu/context_provider.h"
 #include "media/base/cdm_config.h"
+#include "media/base/encryption_scheme.h"
 #include "media/base/media_observer.h"
 #include "media/base/media_tracks.h"
 #include "media/base/overlay_info.h"
@@ -77,7 +78,6 @@
 namespace media {
 class CdmContextRef;
 class ChunkDemuxer;
-class EncryptionScheme;
 class VideoDecodeStatsReporter;
 class MediaLog;
 class UrlIndex;
@@ -594,7 +594,7 @@
   // Records the encryption scheme used by the stream |stream_name|. This is
   // only recorded when metadata is available.
   void RecordEncryptionScheme(const std::string& stream_name,
-                              const EncryptionScheme& encryption_scheme);
+                              EncryptionScheme encryption_scheme);
 
   // Returns whether the player is currently displayed in Picture-in-Picture.
   // It will return true even if the player is in AutoPIP mode.
diff --git a/media/capture/video/video_frame_receiver.h b/media/capture/video/video_frame_receiver.h
index aaa69b788..d06cb16 100644
--- a/media/capture/video/video_frame_receiver.h
+++ b/media/capture/video/video_frame_receiver.h
@@ -16,9 +16,10 @@
 // clients. On some platforms, VideoCaptureDeviceClient calls these methods from
 // OS or capture driver provided threads which do not have a task runner and
 // cannot be posted back to. The mostly equivalent interface
-// video_capture::mojom::Receiver cannot be used by VideoCaptureDeviceClient
-// directly, because creating a video_catpure::mojom::ScopedAccessPermissionPtr
-// for passing into OnFrameReadyInBuffer() requires a thread with a task runner.
+// video_capture::mojom::VideoFrameHandler cannot be used by
+// VideoCaptureDeviceClient directly, because creating a
+// video_catpure::mojom::ScopedAccessPermissionPtr for passing into
+// OnFrameReadyInBuffer() requires a thread with a task runner.
 class CAPTURE_EXPORT VideoFrameReceiver {
  public:
   virtual ~VideoFrameReceiver() {}
diff --git a/media/cast/sender/h264_vt_encoder_unittest.cc b/media/cast/sender/h264_vt_encoder_unittest.cc
index 3c1c7b7..5c95e614 100644
--- a/media/cast/sender/h264_vt_encoder_unittest.cc
+++ b/media/cast/sender/h264_vt_encoder_unittest.cc
@@ -305,7 +305,7 @@
   VideoDecoderConfig config(
       kCodecH264, H264PROFILE_MAIN, alpha_mode, VideoColorSpace(),
       kNoTransformation, frame_->coded_size(), frame_->visible_rect(),
-      frame_->natural_size(), EmptyExtraData(), Unencrypted());
+      frame_->natural_size(), EmptyExtraData(), EncryptionScheme::kUnencrypted);
   scoped_refptr<EndToEndFrameChecker> checker(new EndToEndFrameChecker(config));
 
   VideoEncoder::FrameEncodedCallback cb =
diff --git a/media/cdm/aes_decryptor.cc b/media/cdm/aes_decryptor.cc
index 176050a..4abcdfa7 100644
--- a/media/cdm/aes_decryptor.cc
+++ b/media/cdm/aes_decryptor.cc
@@ -155,10 +155,10 @@
   CHECK(input.data_size());
   CHECK(input.decrypt_config());
 
-  if (input.decrypt_config()->encryption_mode() == EncryptionMode::kCenc)
+  if (input.decrypt_config()->encryption_scheme() == EncryptionScheme::kCenc)
     return DecryptCencBuffer(input, key);
 
-  if (input.decrypt_config()->encryption_mode() == EncryptionMode::kCbcs)
+  if (input.decrypt_config()->encryption_scheme() == EncryptionScheme::kCbcs)
     return DecryptCbcsBuffer(input, key);
 
   DVLOG(1) << "Only 'cenc' and 'cbcs' modes supported.";
diff --git a/media/cdm/cbcs_decryptor.cc b/media/cdm/cbcs_decryptor.cc
index 94c18ad..10f4dfd 100644
--- a/media/cdm/cbcs_decryptor.cc
+++ b/media/cdm/cbcs_decryptor.cc
@@ -126,7 +126,7 @@
 
   const DecryptConfig* decrypt_config = input.decrypt_config();
   DCHECK(decrypt_config) << "No need to call Decrypt() on unencrypted buffer.";
-  DCHECK_EQ(EncryptionMode::kCbcs, decrypt_config->encryption_mode());
+  DCHECK_EQ(EncryptionScheme::kCbcs, decrypt_config->encryption_scheme());
 
   DCHECK(decrypt_config->HasPattern());
   const EncryptionPattern pattern =
diff --git a/media/cdm/cdm_type_conversion.cc b/media/cdm/cdm_type_conversion.cc
index 159d63d2..e786316 100644
--- a/media/cdm/cdm_type_conversion.cc
+++ b/media/cdm/cdm_type_conversion.cc
@@ -203,31 +203,17 @@
   return cdm::kInternalError;
 }
 
-cdm::EncryptionScheme ToCdmEncryptionScheme(const EncryptionScheme& scheme) {
-  switch (scheme.mode()) {
-    case EncryptionScheme::CIPHER_MODE_UNENCRYPTED:
+cdm::EncryptionScheme ToCdmEncryptionScheme(EncryptionScheme scheme) {
+  switch (scheme) {
+    case EncryptionScheme::kUnencrypted:
       return cdm::EncryptionScheme::kUnencrypted;
-    case EncryptionScheme::CIPHER_MODE_AES_CTR:
+    case EncryptionScheme::kCenc:
       return cdm::EncryptionScheme::kCenc;
-    case EncryptionScheme::CIPHER_MODE_AES_CBC:
+    case EncryptionScheme::kCbcs:
       return cdm::EncryptionScheme::kCbcs;
   }
 
-  NOTREACHED() << "Unexpected EncryptionScheme mode " << scheme.mode();
-  return cdm::EncryptionScheme::kUnencrypted;
-}
-
-cdm::EncryptionScheme ToCdmEncryptionScheme(const EncryptionMode& mode) {
-  switch (mode) {
-    case EncryptionMode::kUnencrypted:
-      return cdm::EncryptionScheme::kUnencrypted;
-    case EncryptionMode::kCenc:
-      return cdm::EncryptionScheme::kCenc;
-    case EncryptionMode::kCbcs:
-      return cdm::EncryptionScheme::kCbcs;
-  }
-
-  NOTREACHED() << "Unexpected EncryptionMode";
+  NOTREACHED() << "Unexpected EncryptionScheme";
   return cdm::EncryptionScheme::kUnencrypted;
 }
 
@@ -626,7 +612,7 @@
   input_buffer->num_subsamples = num_subsamples;
 
   input_buffer->encryption_scheme =
-      ToCdmEncryptionScheme(decrypt_config->encryption_mode());
+      ToCdmEncryptionScheme(decrypt_config->encryption_scheme());
   if (decrypt_config->HasPattern()) {
     input_buffer->pattern = {
         decrypt_config->encryption_pattern()->crypt_byte_block(),
diff --git a/media/cdm/cdm_type_conversion.h b/media/cdm/cdm_type_conversion.h
index 2d9e670..d7b303a 100644
--- a/media/cdm/cdm_type_conversion.h
+++ b/media/cdm/cdm_type_conversion.h
@@ -51,9 +51,7 @@
 MEDIA_EXPORT cdm::KeyStatus ToCdmKeyStatus(CdmKeyInformation::KeyStatus status);
 
 MEDIA_EXPORT cdm::EncryptionScheme ToCdmEncryptionScheme(
-    const EncryptionScheme& scheme);
-MEDIA_EXPORT cdm::EncryptionScheme ToCdmEncryptionScheme(
-    const EncryptionMode& mode);
+    EncryptionScheme scheme);
 
 MEDIA_EXPORT CdmPromise::Exception ToMediaCdmPromiseException(
     cdm::Exception exception);
diff --git a/media/cdm/cenc_decryptor.cc b/media/cdm/cenc_decryptor.cc
index ae0442f..647362e 100644
--- a/media/cdm/cenc_decryptor.cc
+++ b/media/cdm/cenc_decryptor.cc
@@ -68,7 +68,7 @@
 
   const DecryptConfig* decrypt_config = input.decrypt_config();
   DCHECK(decrypt_config) << "No need to call Decrypt() on unencrypted buffer.";
-  DCHECK_EQ(EncryptionMode::kCenc, decrypt_config->encryption_mode());
+  DCHECK_EQ(EncryptionScheme::kCenc, decrypt_config->encryption_scheme());
 
   const std::string& iv = decrypt_config->iv();
   DCHECK_EQ(iv.size(), static_cast<size_t>(DecryptConfig::kDecryptionKeySize));
diff --git a/media/cdm/library_cdm/clear_key_cdm/cdm_video_decoder.cc b/media/cdm/library_cdm/clear_key_cdm/cdm_video_decoder.cc
index 7e62ba0..0321d2d 100644
--- a/media/cdm/library_cdm/clear_key_cdm/cdm_video_decoder.cc
+++ b/media/cdm/library_cdm/clear_key_cdm/cdm_video_decoder.cc
@@ -59,7 +59,7 @@
       gfx::Rect(coded_size), coded_size,
       std::vector<uint8_t>(config.extra_data,
                            config.extra_data + config.extra_data_size),
-      Unencrypted());
+      EncryptionScheme::kUnencrypted);
 
   return media_config;
 }
diff --git a/media/ffmpeg/ffmpeg_common.cc b/media/ffmpeg/ffmpeg_common.cc
index 72dc400..ddce138 100644
--- a/media/ffmpeg/ffmpeg_common.cc
+++ b/media/ffmpeg/ffmpeg_common.cc
@@ -26,7 +26,7 @@
 EncryptionScheme GetEncryptionScheme(const AVStream* stream) {
   AVDictionaryEntry* key =
       av_dict_get(stream->metadata, "enc_key_id", nullptr, 0);
-  return key ? AesCtrEncryptionScheme() : Unencrypted();
+  return key ? EncryptionScheme::kCenc : EncryptionScheme::kUnencrypted;
 }
 
 }  // namespace
@@ -318,10 +318,9 @@
   return AV_SAMPLE_FMT_NONE;
 }
 
-bool AVCodecContextToAudioDecoderConfig(
-    const AVCodecContext* codec_context,
-    const EncryptionScheme& encryption_scheme,
-    AudioDecoderConfig* config) {
+bool AVCodecContextToAudioDecoderConfig(const AVCodecContext* codec_context,
+                                        EncryptionScheme encryption_scheme,
+                                        AudioDecoderConfig* config) {
   DCHECK_EQ(codec_context->codec_type, AVMEDIA_TYPE_AUDIO);
 
   AudioCodec codec = CodecIDToAudioCodec(codec_context->codec_id);
diff --git a/media/ffmpeg/ffmpeg_common.h b/media/ffmpeg/ffmpeg_common.h
index 3c6c62b..fbcfa14 100644
--- a/media/ffmpeg/ffmpeg_common.h
+++ b/media/ffmpeg/ffmpeg_common.h
@@ -15,6 +15,7 @@
 #include "base/time/time.h"
 #include "media/base/audio_codecs.h"
 #include "media/base/channel_layout.h"
+#include "media/base/encryption_scheme.h"
 #include "media/base/media_export.h"
 #include "media/base/sample_format.h"
 #include "media/base/video_codecs.h"
@@ -43,7 +44,6 @@
 constexpr int64_t kNoFFmpegTimestamp = static_cast<int64_t>(AV_NOPTS_VALUE);
 
 class AudioDecoderConfig;
-class EncryptionScheme;
 class VideoDecoderConfig;
 
 // The following implement the deleters declared in ffmpeg_deleters.h (which
@@ -114,7 +114,7 @@
 // is not modified.
 MEDIA_EXPORT bool AVCodecContextToAudioDecoderConfig(
     const AVCodecContext* codec_context,
-    const EncryptionScheme& encryption_scheme,
+    EncryptionScheme encryption_scheme,
     AudioDecoderConfig* config);
 
 // Converts FFmpeg's channel layout to chrome's ChannelLayout.  |channels| can
diff --git a/media/filters/android/media_codec_audio_decoder.cc b/media/filters/android/media_codec_audio_decoder.cc
index 209d799..c88bcab 100644
--- a/media/filters/android/media_codec_audio_decoder.cc
+++ b/media/filters/android/media_codec_audio_decoder.cc
@@ -314,7 +314,7 @@
       input_data.key_id = decrypt_config->key_id();
       input_data.iv = decrypt_config->iv();
       input_data.subsamples = decrypt_config->subsamples();
-      input_data.encryption_scheme = decrypt_config->encryption_mode();
+      input_data.encryption_scheme = decrypt_config->encryption_scheme();
       input_data.encryption_pattern = decrypt_config->encryption_pattern();
     }
     input_data.presentation_time = decoder_buffer->timestamp();
diff --git a/media/filters/audio_decoder_unittest.cc b/media/filters/audio_decoder_unittest.cc
index 62315337..ddac5fd0 100644
--- a/media/filters/audio_decoder_unittest.cc
+++ b/media/filters/audio_decoder_unittest.cc
@@ -209,7 +209,8 @@
 
     AudioDecoderConfig config;
     ASSERT_TRUE(AVCodecContextToAudioDecoderConfig(
-        reader_->codec_context_for_testing(), Unencrypted(), &config));
+        reader_->codec_context_for_testing(), EncryptionScheme::kUnencrypted,
+        &config));
 
 #if defined(OS_ANDROID) && BUILDFLAG(USE_PROPRIETARY_CODECS)
     // MEDIA_CODEC type requires config->extra_data() for AAC codec. For ADTS
@@ -224,7 +225,7 @@
                     &channel_layout, nullptr, nullptr, &extra_data),
                 0);
       config.Initialize(kCodecAAC, kSampleFormatS16, channel_layout,
-                        sample_rate, extra_data, Unencrypted(),
+                        sample_rate, extra_data, EncryptionScheme::kUnencrypted,
                         base::TimeDelta(), 0);
       ASSERT_FALSE(config.extra_data().empty());
     }
diff --git a/media/filters/audio_timestamp_validator_unittest.cc b/media/filters/audio_timestamp_validator_unittest.cc
index 2424091..f21e28c 100644
--- a/media/filters/audio_timestamp_validator_unittest.cc
+++ b/media/filters/audio_timestamp_validator_unittest.cc
@@ -63,8 +63,9 @@
 TEST_P(AudioTimestampValidatorTest, WarnForEraticTimes) {
   AudioDecoderConfig decoder_config;
   decoder_config.Initialize(kCodec, kSampleFormat, kChannelLayout,
-                            kSamplesPerSecond, EmptyExtraData(), Unencrypted(),
-                            kSeekPreroll, codec_delay_);
+                            kSamplesPerSecond, EmptyExtraData(),
+                            EncryptionScheme::kUnencrypted, kSeekPreroll,
+                            codec_delay_);
 
   // Validator should fail to stabilize pattern for timestamp expectations.
   EXPECT_MEDIA_LOG(
@@ -112,8 +113,9 @@
 TEST_P(AudioTimestampValidatorTest, NoWarningForValidTimes) {
   AudioDecoderConfig decoder_config;
   decoder_config.Initialize(kCodec, kSampleFormat, kChannelLayout,
-                            kSamplesPerSecond, EmptyExtraData(), Unencrypted(),
-                            kSeekPreroll, codec_delay_);
+                            kSamplesPerSecond, EmptyExtraData(),
+                            EncryptionScheme::kUnencrypted, kSeekPreroll,
+                            codec_delay_);
 
   // Validator should quickly stabilize pattern for timestamp expectations.
   EXPECT_MEDIA_LOG(HasSubstr("Failed to reconcile encoded audio times "
@@ -151,8 +153,9 @@
 TEST_P(AudioTimestampValidatorTest, SingleWarnForSingleLargeGap) {
   AudioDecoderConfig decoder_config;
   decoder_config.Initialize(kCodec, kSampleFormat, kChannelLayout,
-                            kSamplesPerSecond, EmptyExtraData(), Unencrypted(),
-                            kSeekPreroll, codec_delay_);
+                            kSamplesPerSecond, EmptyExtraData(),
+                            EncryptionScheme::kUnencrypted, kSeekPreroll,
+                            codec_delay_);
 
   AudioTimestampValidator validator(decoder_config, &media_log_);
 
@@ -196,8 +199,9 @@
 TEST_P(AudioTimestampValidatorTest, RepeatedWarnForSlowAccumulatingDrift) {
   AudioDecoderConfig decoder_config;
   decoder_config.Initialize(kCodec, kSampleFormat, kChannelLayout,
-                            kSamplesPerSecond, EmptyExtraData(), Unencrypted(),
-                            kSeekPreroll, codec_delay_);
+                            kSamplesPerSecond, EmptyExtraData(),
+                            EncryptionScheme::kUnencrypted, kSeekPreroll,
+                            codec_delay_);
 
   AudioTimestampValidator validator(decoder_config, &media_log_);
 
diff --git a/media/filters/decrypting_audio_decoder_unittest.cc b/media/filters/decrypting_audio_decoder_unittest.cc
index 1932020..acb150e 100644
--- a/media/filters/decrypting_audio_decoder_unittest.cc
+++ b/media/filters/decrypting_audio_decoder_unittest.cc
@@ -108,7 +108,7 @@
 
     config_.Initialize(kCodecVorbis, kSampleFormatPlanarF32,
                        CHANNEL_LAYOUT_STEREO, kSampleRate, EmptyExtraData(),
-                       AesCtrEncryptionScheme(), base::TimeDelta(), 0);
+                       EncryptionScheme::kCenc, base::TimeDelta(), 0);
     InitializeAndExpectResult(config_, true);
   }
 
@@ -276,7 +276,7 @@
 TEST_F(DecryptingAudioDecoderTest, Initialize_InvalidAudioConfig) {
   AudioDecoderConfig config(kUnknownAudioCodec, kUnknownSampleFormat,
                             CHANNEL_LAYOUT_STEREO, 0, EmptyExtraData(),
-                            AesCtrEncryptionScheme());
+                            EncryptionScheme::kCenc);
 
   InitializeAndExpectResult(config, false);
 }
@@ -289,7 +289,7 @@
 
   AudioDecoderConfig config(kCodecVorbis, kSampleFormatPlanarF32,
                             CHANNEL_LAYOUT_STEREO, kSampleRate,
-                            EmptyExtraData(), AesCtrEncryptionScheme());
+                            EmptyExtraData(), EncryptionScheme::kCenc);
   InitializeAndExpectResult(config, false);
 }
 
@@ -297,7 +297,7 @@
   SetCdmType(CDM_WITHOUT_DECRYPTOR);
   AudioDecoderConfig config(kCodecVorbis, kSampleFormatPlanarF32,
                             CHANNEL_LAYOUT_STEREO, kSampleRate,
-                            EmptyExtraData(), AesCtrEncryptionScheme());
+                            EmptyExtraData(), EncryptionScheme::kCenc);
   InitializeAndExpectResult(config, false);
 }
 
@@ -362,7 +362,7 @@
   // channel layout and samples_per_second.
   AudioDecoderConfig new_config(kCodecVorbis, kSampleFormatPlanarS16,
                                 CHANNEL_LAYOUT_5_1, 88200, EmptyExtraData(),
-                                AesCtrEncryptionScheme());
+                                EncryptionScheme::kCenc);
   EXPECT_NE(new_config.bits_per_channel(), config_.bits_per_channel());
   EXPECT_NE(new_config.channel_layout(), config_.channel_layout());
   EXPECT_NE(new_config.samples_per_second(), config_.samples_per_second());
@@ -384,7 +384,7 @@
   // channel layout and samples_per_second.
   AudioDecoderConfig new_config(kCodecVorbis, kSampleFormatPlanarS16,
                                 CHANNEL_LAYOUT_5_1, 88200, EmptyExtraData(),
-                                Unencrypted());
+                                EncryptionScheme::kUnencrypted);
   EXPECT_NE(new_config.bits_per_channel(), config_.bits_per_channel());
   EXPECT_NE(new_config.channel_layout(), config_.channel_layout());
   EXPECT_NE(new_config.samples_per_second(), config_.samples_per_second());
diff --git a/media/filters/decrypting_demuxer_stream_unittest.cc b/media/filters/decrypting_demuxer_stream_unittest.cc
index b927aa1..2d4f6931 100644
--- a/media/filters/decrypting_demuxer_stream_unittest.cc
+++ b/media/filters/decrypting_demuxer_stream_unittest.cc
@@ -145,7 +145,7 @@
 
     AudioDecoderConfig input_config(kCodecVorbis, kSampleFormatPlanarF32,
                                     CHANNEL_LAYOUT_STEREO, 44100,
-                                    EmptyExtraData(), AesCtrEncryptionScheme());
+                                    EmptyExtraData(), EncryptionScheme::kCenc);
     InitializeAudioAndExpectStatus(input_config, PIPELINE_OK);
 
     const AudioDecoderConfig& output_config =
@@ -326,7 +326,7 @@
   SetCdmType(CDM_WITHOUT_DECRYPTOR);
   AudioDecoderConfig input_config(kCodecVorbis, kSampleFormatPlanarF32,
                                   CHANNEL_LAYOUT_STEREO, 44100,
-                                  EmptyExtraData(), AesCtrEncryptionScheme());
+                                  EmptyExtraData(), EncryptionScheme::kCenc);
   InitializeAudioAndExpectStatus(input_config, DECODER_ERROR_NOT_SUPPORTED);
 }
 
@@ -502,7 +502,7 @@
 
   AudioDecoderConfig new_config(kCodecVorbis, kSampleFormatPlanarF32,
                                 CHANNEL_LAYOUT_STEREO, 88200, EmptyExtraData(),
-                                AesCtrEncryptionScheme());
+                                EncryptionScheme::kCenc);
   input_audio_stream_->set_audio_decoder_config(new_config);
 
   EXPECT_CALL(*input_audio_stream_, OnRead(_))
diff --git a/media/filters/ffmpeg_video_decoder_unittest.cc b/media/filters/ffmpeg_video_decoder_unittest.cc
index 24fe18f..5fdf779ea 100644
--- a/media/filters/ffmpeg_video_decoder_unittest.cc
+++ b/media/filters/ffmpeg_video_decoder_unittest.cc
@@ -225,7 +225,7 @@
                             VideoDecoderConfig::AlphaMode::kIsOpaque,
                             VideoColorSpace(), kNoTransformation, kCodedSize,
                             kVisibleRect, kNaturalSize, EmptyExtraData(),
-                            Unencrypted());
+                            EncryptionScheme::kUnencrypted);
   InitializeWithConfigWithResult(config, false);
 }
 
diff --git a/media/filters/frame_processor_unittest.cc b/media/filters/frame_processor_unittest.cc
index a907c1da..ee4dec0 100644
--- a/media/filters/frame_processor_unittest.cc
+++ b/media/filters/frame_processor_unittest.cc
@@ -345,9 +345,9 @@
         ASSERT_FALSE(audio_);
         audio_.reset(
             new ChunkDemuxerStream(DemuxerStream::AUDIO, MediaTrack::Id("1")));
-        AudioDecoderConfig decoder_config(kCodecVorbis, kSampleFormatPlanarF32,
-                                          CHANNEL_LAYOUT_STEREO, 1000,
-                                          EmptyExtraData(), Unencrypted());
+        AudioDecoderConfig decoder_config(
+            kCodecVorbis, kSampleFormatPlanarF32, CHANNEL_LAYOUT_STEREO, 1000,
+            EmptyExtraData(), EncryptionScheme::kUnencrypted);
         frame_processor_->OnPossibleAudioConfigUpdate(decoder_config);
         ASSERT_TRUE(
             audio_->UpdateAudioConfig(decoder_config, false, &media_log_));
diff --git a/media/filters/source_buffer_state_unittest.cc b/media/filters/source_buffer_state_unittest.cc
index c0b0dba..9221b928 100644
--- a/media/filters/source_buffer_state_unittest.cc
+++ b/media/filters/source_buffer_state_unittest.cc
@@ -29,7 +29,7 @@
 AudioDecoderConfig CreateAudioConfig(AudioCodec codec) {
   return AudioDecoderConfig(codec, kSampleFormatPlanarF32,
                             CHANNEL_LAYOUT_STEREO, 1000, EmptyExtraData(),
-                            Unencrypted());
+                            EncryptionScheme::kUnencrypted);
 }
 
 VideoDecoderConfig CreateVideoConfig(VideoCodec codec, int w, int h) {
@@ -39,7 +39,7 @@
                             VideoDecoderConfig::AlphaMode::kIsOpaque,
                             VideoColorSpace::REC709(), kNoTransformation, size,
                             visible_rect, size, EmptyExtraData(),
-                            Unencrypted());
+                            EncryptionScheme::kUnencrypted);
 }
 
 void AddAudioTrack(std::unique_ptr<MediaTracks>& t, AudioCodec codec, int id) {
diff --git a/media/filters/source_buffer_stream_unittest.cc b/media/filters/source_buffer_stream_unittest.cc
index 8364ab7d..0fd3b26 100644
--- a/media/filters/source_buffer_stream_unittest.cc
+++ b/media/filters/source_buffer_stream_unittest.cc
@@ -96,9 +96,9 @@
 
   void SetAudioStream() {
     video_config_ = TestVideoConfig::Invalid();
-    audio_config_.Initialize(kCodecVorbis, kSampleFormatPlanarF32,
-                             CHANNEL_LAYOUT_STEREO, 1000, EmptyExtraData(),
-                             Unencrypted(), base::TimeDelta(), 0);
+    audio_config_.Initialize(
+        kCodecVorbis, kSampleFormatPlanarF32, CHANNEL_LAYOUT_STEREO, 1000,
+        EmptyExtraData(), EncryptionScheme::kUnencrypted, base::TimeDelta(), 0);
     ResetStream<>(audio_config_);
 
     // Equivalent to 2ms per frame.
@@ -3892,7 +3892,8 @@
 // Test all the valid same timestamp cases for audio.
 TEST_F(SourceBufferStreamTest, SameTimestamp_Audio) {
   AudioDecoderConfig config(kCodecMP3, kSampleFormatF32, CHANNEL_LAYOUT_STEREO,
-                            44100, EmptyExtraData(), Unencrypted());
+                            44100, EmptyExtraData(),
+                            EncryptionScheme::kUnencrypted);
   ResetStream<>(config);
   Seek(0);
   NewCodedFrameGroupAppend("0K 0K 30K 30K");
@@ -4518,9 +4519,9 @@
   EXPECT_MEDIA_LOG(SkippingSpliceTooLittleOverlap(1250, 250));
 
   video_config_ = TestVideoConfig::Invalid();
-  audio_config_.Initialize(kCodecVorbis, kSampleFormatPlanarF32,
-                           CHANNEL_LAYOUT_STEREO, 4000, EmptyExtraData(),
-                           Unencrypted(), base::TimeDelta(), 0);
+  audio_config_.Initialize(
+      kCodecVorbis, kSampleFormatPlanarF32, CHANNEL_LAYOUT_STEREO, 4000,
+      EmptyExtraData(), EncryptionScheme::kUnencrypted, base::TimeDelta(), 0);
   ResetStream<>(audio_config_);
   // Equivalent to 0.5ms per frame.
   SetStreamInfo(2000, 2000);
@@ -4553,7 +4554,7 @@
 TEST_F(SourceBufferStreamTest, Audio_ConfigChangeWithPreroll) {
   AudioDecoderConfig new_config(kCodecVorbis, kSampleFormatPlanarF32,
                                 CHANNEL_LAYOUT_MONO, 2000, EmptyExtraData(),
-                                Unencrypted());
+                                EncryptionScheme::kUnencrypted);
   SetAudioStream();
   Seek(0);
 
@@ -4598,8 +4599,8 @@
   video_config_ = TestVideoConfig::Invalid();
   audio_config_.Initialize(kCodecOpus, kSampleFormatPlanarF32,
                            CHANNEL_LAYOUT_STEREO, 1000, EmptyExtraData(),
-                           Unencrypted(), base::TimeDelta::FromMilliseconds(10),
-                           0);
+                           EncryptionScheme::kUnencrypted,
+                           base::TimeDelta::FromMilliseconds(10), 0);
   ResetStream<>(audio_config_);
 
   // Equivalent to 1s per frame.
diff --git a/media/filters/vpx_video_decoder_fuzzertest.cc b/media/filters/vpx_video_decoder_fuzzertest.cc
index b501176..d7e6b835 100644
--- a/media/filters/vpx_video_decoder_fuzzertest.cc
+++ b/media/filters/vpx_video_decoder_fuzzertest.cc
@@ -92,7 +92,7 @@
                 : media::VideoDecoderConfig::AlphaMode::kIsOpaque,
       color_space, media::VideoTransformation(rotation, reflection), coded_size,
       visible_rect, natural_size, media::EmptyExtraData(),
-      media::Unencrypted());
+      media::EncryptionScheme::kUnencrypted);
 
   if (!config.IsValidConfig())
     return 0;
diff --git a/media/formats/mp2t/descriptors.cc b/media/formats/mp2t/descriptors.cc
index a0a771e..7d06dc97 100644
--- a/media/formats/mp2t/descriptors.cc
+++ b/media/formats/mp2t/descriptors.cc
@@ -114,7 +114,7 @@
 
 bool Descriptors::HasCADescriptorCenc(int* ca_pid,
                                       int* pssh_pid,
-                                      EncryptionMode* mode) const {
+                                      EncryptionScheme* scheme) const {
   DCHECK(ca_pid);
   DCHECK(pssh_pid);
   int system_id;
@@ -145,7 +145,7 @@
   RCHECK(reader.ReadBits(13, pssh_pid));
   // The pattern is actually set differently for audio and video, so OK not to
   // set it here. Important thing is to set the cipher mode.
-  *mode = EncryptionMode::kCbcs;
+  *scheme = EncryptionScheme::kCbcs;
 
   return true;
 }
diff --git a/media/formats/mp2t/descriptors.h b/media/formats/mp2t/descriptors.h
index 8e444f8..ad55322 100644
--- a/media/formats/mp2t/descriptors.h
+++ b/media/formats/mp2t/descriptors.h
@@ -52,7 +52,7 @@
   // |pssh_pid| and |mode| are populated with the contents of the descriptor.
   bool HasCADescriptorCenc(int* ca_pid,
                            int* pssh_pid,
-                           EncryptionMode* mode) const;
+                           EncryptionScheme* scheme) const;
 
   // Indicates whether a Private Data Indicator descriptor is present with a
   // particular |value|.
diff --git a/media/formats/mp2t/es_adapter_video_unittest.cc b/media/formats/mp2t/es_adapter_video_unittest.cc
index 228a969..00cacb21 100644
--- a/media/formats/mp2t/es_adapter_video_unittest.cc
+++ b/media/formats/mp2t/es_adapter_video_unittest.cc
@@ -35,7 +35,7 @@
   return VideoDecoderConfig(
       kCodecH264, H264PROFILE_MAIN, VideoDecoderConfig::AlphaMode::kIsOpaque,
       VideoColorSpace(), kNoTransformation, coded_size, visible_rect,
-      natural_size, EmptyExtraData(), Unencrypted());
+      natural_size, EmptyExtraData(), EncryptionScheme::kUnencrypted);
 }
 
 BufferQueue GenerateFakeBuffers(const int* frame_pts_ms,
diff --git a/media/formats/mp2t/es_parser_adts.cc b/media/formats/mp2t/es_parser_adts.cc
index 1834049..63aa3acd 100644
--- a/media/formats/mp2t/es_parser_adts.cc
+++ b/media/formats/mp2t/es_parser_adts.cc
@@ -217,7 +217,7 @@
         CalculateSubsamplesForAdtsFrame(adts_frame, &subsamples);
         stream_parser_buffer->set_decrypt_config(
             std::make_unique<DecryptConfig>(
-                base_decrypt_config->encryption_mode(),
+                base_decrypt_config->encryption_scheme(),
                 base_decrypt_config->key_id(), base_decrypt_config->iv(),
                 subsamples, EncryptionPattern()));
       }
@@ -260,11 +260,10 @@
   const int extended_samples_per_second =
       sbr_in_mimetype_ ? std::min(2 * orig_sample_rate, 48000)
                        : orig_sample_rate;
-  EncryptionScheme scheme = Unencrypted();
+  EncryptionScheme scheme = EncryptionScheme::kUnencrypted;
 #if BUILDFLAG(ENABLE_HLS_SAMPLE_AES)
   if (use_hls_sample_aes_) {
-    scheme = EncryptionScheme(EncryptionScheme::CIPHER_MODE_AES_CBC,
-                              EncryptionPattern());
+    scheme = EncryptionScheme::kCbcs;
   }
 #endif
   AudioDecoderConfig audio_decoder_config(
diff --git a/media/formats/mp2t/es_parser_h264.cc b/media/formats/mp2t/es_parser_h264.cc
index f48337d..fe03ac6 100644
--- a/media/formats/mp2t/es_parser_h264.cc
+++ b/media/formats/mp2t/es_parser_h264.cc
@@ -418,13 +418,11 @@
     const H264SPS* sps = h264_parser_->GetSPS(pps->seq_parameter_set_id);
     if (!sps)
       return false;
-    EncryptionScheme scheme = Unencrypted();
+    EncryptionScheme scheme = EncryptionScheme::kUnencrypted;
 #if BUILDFLAG(ENABLE_HLS_SAMPLE_AES)
     if (use_hls_sample_aes_) {
       // Note that for SampleAES the (encrypt,skip) pattern is constant.
-      scheme = EncryptionScheme(
-          EncryptionScheme::CIPHER_MODE_AES_CBC,
-          EncryptionPattern(kSampleAESEncryptBlocks, kSampleAESSkipBlocks));
+      scheme = EncryptionScheme::kCbcs;
     }
 #endif
     RCHECK(UpdateVideoDecoderConfig(sps, scheme));
@@ -465,19 +463,19 @@
   stream_parser_buffer->set_timestamp(current_timing_desc.pts);
 #if BUILDFLAG(ENABLE_HLS_SAMPLE_AES)
   if (use_hls_sample_aes_ && base_decrypt_config) {
-    switch (base_decrypt_config->encryption_mode()) {
-      case EncryptionMode::kUnencrypted:
+    switch (base_decrypt_config->encryption_scheme()) {
+      case EncryptionScheme::kUnencrypted:
         // As |base_decrypt_config| is specified, the stream is encrypted,
         // so this shouldn't happen.
         NOTREACHED();
         break;
-      case EncryptionMode::kCenc:
+      case EncryptionScheme::kCenc:
         stream_parser_buffer->set_decrypt_config(
             DecryptConfig::CreateCencConfig(base_decrypt_config->key_id(),
                                             base_decrypt_config->iv(),
                                             subsamples));
         break;
-      case EncryptionMode::kCbcs:
+      case EncryptionScheme::kCbcs:
         // Note that for SampleAES the (encrypt,skip) pattern is constant.
         // If not specified in |base_decrypt_config|, use default values.
         stream_parser_buffer->set_decrypt_config(
@@ -494,7 +492,7 @@
 }
 
 bool EsParserH264::UpdateVideoDecoderConfig(const H264SPS* sps,
-                                            const EncryptionScheme& scheme) {
+                                            EncryptionScheme scheme) {
   // Set the SAR to 1 when not specified in the H264 stream.
   int sar_width = (sps->sar_width == 0) ? 1 : sps->sar_width;
   int sar_height = (sps->sar_height == 0) ? 1 : sps->sar_height;
diff --git a/media/formats/mp2t/es_parser_h264.h b/media/formats/mp2t/es_parser_h264.h
index 5e22635..98e6ff0 100644
--- a/media/formats/mp2t/es_parser_h264.h
+++ b/media/formats/mp2t/es_parser_h264.h
@@ -22,7 +22,6 @@
 #include "media/media_buildflags.h"
 
 namespace media {
-class EncryptionScheme;
 class H264Parser;
 struct H264SPS;
 }
@@ -77,8 +76,7 @@
 
   // Update the video decoder config based on an H264 SPS.
   // Return true if successful.
-  bool UpdateVideoDecoderConfig(const H264SPS* sps,
-                                const EncryptionScheme& scheme);
+  bool UpdateVideoDecoderConfig(const H264SPS* sps, EncryptionScheme scheme);
 
   EsAdapterVideo es_adapter_;
 
diff --git a/media/formats/mp2t/es_parser_mpeg1audio.cc b/media/formats/mp2t/es_parser_mpeg1audio.cc
index 1e1cb06..efcd7a1 100644
--- a/media/formats/mp2t/es_parser_mpeg1audio.cc
+++ b/media/formats/mp2t/es_parser_mpeg1audio.cc
@@ -169,7 +169,7 @@
   // field for Mpeg1 audio. If yes, we should generate this field.
   AudioDecoderConfig audio_decoder_config(
       kCodecMP3, kSampleFormatS16, header.channel_layout, header.sample_rate,
-      EmptyExtraData(), Unencrypted());
+      EmptyExtraData(), EncryptionScheme::kUnencrypted);
 
   if (!audio_decoder_config.IsValidConfig()) {
     DVLOG(1) << "Invalid config: "
diff --git a/media/formats/mp2t/mp2t_stream_parser.cc b/media/formats/mp2t/mp2t_stream_parser.cc
index 41621a21..1d55087 100644
--- a/media/formats/mp2t/mp2t_stream_parser.cc
+++ b/media/formats/mp2t/mp2t_stream_parser.cc
@@ -429,7 +429,7 @@
   // use of the encrypted parser variant so that the initial configuration
   // reflects the intended encryption mode (even if the initial segment itself
   // is not encrypted).
-  return initial_encryption_mode_ != EncryptionMode::kUnencrypted;
+  return initial_encryption_scheme_ != EncryptionScheme::kUnencrypted;
 }
 
 std::unique_ptr<EsParser> Mp2tStreamParser::CreateEncryptedH264Parser(
@@ -833,7 +833,7 @@
   std::unique_ptr<TsSection> cat_section_parser(new TsSectionCat(
       base::BindRepeating(&Mp2tStreamParser::RegisterCencPids,
                           base::Unretained(this)),
-      base::BindRepeating(&Mp2tStreamParser::RegisterEncryptionMode,
+      base::BindRepeating(&Mp2tStreamParser::RegisterEncryptionScheme,
                           base::Unretained(this))));
   std::unique_ptr<PidState> cat_pid_state(new PidState(
       TsSection::kPidCat, PidState::kPidCat, std::move(cat_section_parser)));
@@ -883,10 +883,10 @@
   }
 }
 
-void Mp2tStreamParser::RegisterEncryptionMode(EncryptionMode mode) {
+void Mp2tStreamParser::RegisterEncryptionScheme(EncryptionScheme scheme) {
   // We only need to record this for the initial decoder config.
   if (!is_initialized_) {
-    initial_encryption_mode_ = mode;
+    initial_encryption_scheme_ = scheme;
   }
   // Reset the DecryptConfig, so that unless and until a CENC-ECM (containing
   // key id and IV) is seen, media data will be considered unencrypted. This is
@@ -897,14 +897,14 @@
 void Mp2tStreamParser::RegisterNewKeyIdAndIv(const std::string& key_id,
                                              const std::string& iv) {
   if (!iv.empty()) {
-    switch (initial_encryption_mode_) {
-      case EncryptionMode::kUnencrypted:
+    switch (initial_encryption_scheme_) {
+      case EncryptionScheme::kUnencrypted:
         decrypt_config_.reset();
         break;
-      case EncryptionMode::kCenc:
+      case EncryptionScheme::kCenc:
         decrypt_config_ = DecryptConfig::CreateCencConfig(key_id, iv, {});
         break;
-      case EncryptionMode::kCbcs:
+      case EncryptionScheme::kCbcs:
         decrypt_config_ =
             DecryptConfig::CreateCbcsConfig(key_id, iv, {}, base::nullopt);
         break;
diff --git a/media/formats/mp2t/mp2t_stream_parser.h b/media/formats/mp2t/mp2t_stream_parser.h
index ffa5ab16a..81f127f 100644
--- a/media/formats/mp2t/mp2t_stream_parser.h
+++ b/media/formats/mp2t/mp2t_stream_parser.h
@@ -122,7 +122,7 @@
   // Register a default encryption mode to be used for decoder configs. This
   // value is only used in the absence of explicit encryption metadata, as might
   // be the case during an unencrypted portion of a live stream.
-  void RegisterEncryptionMode(EncryptionMode mode);
+  void RegisterEncryptionScheme(EncryptionScheme scheme);
 
   // Register the new KeyID and IV (parsed from CENC-ECM).
   void RegisterNewKeyIdAndIv(const std::string& key_id, const std::string& iv);
@@ -171,7 +171,7 @@
   TimestampUnroller timestamp_unroller_;
 
 #if BUILDFLAG(ENABLE_HLS_SAMPLE_AES)
-  EncryptionMode initial_encryption_mode_ = EncryptionMode::kUnencrypted;
+  EncryptionScheme initial_encryption_scheme_ = EncryptionScheme::kUnencrypted;
 
   // TODO(jrummell): Rather than store the key_id and iv in a DecryptConfig,
   // provide a better way to access the last values seen in a ECM packet.
diff --git a/media/formats/mp2t/mp2t_stream_parser_unittest.cc b/media/formats/mp2t/mp2t_stream_parser_unittest.cc
index bd1590a..3119e4f 100644
--- a/media/formats/mp2t/mp2t_stream_parser_unittest.cc
+++ b/media/formats/mp2t/mp2t_stream_parser_unittest.cc
@@ -124,15 +124,15 @@
 // We only support AES-CBC at this time.
 // For the purpose of these tests, the key id is also used as the actual key.
 std::string DecryptBuffer(const StreamParserBuffer& buffer,
-                          const EncryptionScheme& scheme) {
-  EXPECT_TRUE(scheme.is_encrypted());
-  EXPECT_TRUE(scheme.mode() == EncryptionScheme::CIPHER_MODE_AES_CBC);
+                          EncryptionScheme scheme) {
+  EXPECT_EQ(scheme, EncryptionScheme::kCbcs);
 
   // Audio streams use whole block full sample encryption (so pattern = {0,0}),
   // so only the video stream uses pattern decryption. |has_pattern| is only
   // used by DecryptSampleAES(), which assumes a {1,9} pattern if
   // |has_pattern| = true.
-  bool has_pattern = scheme.pattern() == EncryptionPattern(1, 9);
+  bool has_pattern =
+      buffer.decrypt_config()->encryption_pattern() == EncryptionPattern(1, 9);
 
   std::string key;
   EXPECT_TRUE(
@@ -472,7 +472,7 @@
   parser_->Flush();
   EncryptionScheme video_encryption_scheme =
       current_video_config_.encryption_scheme();
-  EXPECT_TRUE(video_encryption_scheme.is_encrypted());
+  EXPECT_NE(video_encryption_scheme, EncryptionScheme::kUnencrypted);
   for (const auto& buffer : video_buffer_capture_) {
     std::string decrypted_video_buffer =
         DecryptBuffer(*buffer.get(), video_encryption_scheme);
@@ -480,7 +480,7 @@
   }
   EncryptionScheme audio_encryption_scheme =
       current_audio_config_.encryption_scheme();
-  EXPECT_TRUE(audio_encryption_scheme.is_encrypted());
+  EXPECT_NE(audio_encryption_scheme, EncryptionScheme::kUnencrypted);
   for (const auto& buffer : audio_buffer_capture_) {
     std::string decrypted_audio_buffer =
         DecryptBuffer(*buffer.get(), audio_encryption_scheme);
@@ -495,7 +495,7 @@
   ParseMpeg2TsFile("bear-1280x720-hls.ts", 2048);
   parser_->Flush();
   video_encryption_scheme = current_video_config_.encryption_scheme();
-  EXPECT_FALSE(video_encryption_scheme.is_encrypted());
+  EXPECT_EQ(video_encryption_scheme, EncryptionScheme::kUnencrypted);
   // Skip the last buffer, which may be truncated.
   for (size_t i = 0; i + 1 < video_buffer_capture_.size(); i++) {
     const auto& buffer = video_buffer_capture_[i];
@@ -504,7 +504,7 @@
     EXPECT_EQ(decrypted_video_buffers[i], unencrypted_video_buffer);
   }
   audio_encryption_scheme = current_audio_config_.encryption_scheme();
-  EXPECT_FALSE(audio_encryption_scheme.is_encrypted());
+  EXPECT_EQ(audio_encryption_scheme, EncryptionScheme::kUnencrypted);
   for (size_t i = 0; i + 1 < audio_buffer_capture_.size(); i++) {
     const auto& buffer = audio_buffer_capture_[i];
     std::string unencrypted_audio_buffer(
@@ -519,10 +519,10 @@
   parser_->Flush();
   EncryptionScheme video_encryption_scheme =
       current_video_config_.encryption_scheme();
-  EXPECT_TRUE(video_encryption_scheme.is_encrypted());
+  EXPECT_NE(video_encryption_scheme, EncryptionScheme::kUnencrypted);
   EncryptionScheme audio_encryption_scheme =
       current_audio_config_.encryption_scheme();
-  EXPECT_TRUE(audio_encryption_scheme.is_encrypted());
+  EXPECT_NE(audio_encryption_scheme, EncryptionScheme::kUnencrypted);
 }
 
 #endif
diff --git a/media/formats/mp2t/ts_section_cat.cc b/media/formats/mp2t/ts_section_cat.cc
index a9239fbd..507a789 100644
--- a/media/formats/mp2t/ts_section_cat.cc
+++ b/media/formats/mp2t/ts_section_cat.cc
@@ -16,9 +16,9 @@
 
 TsSectionCat::TsSectionCat(
     const RegisterCencPidsCb& register_cenc_ids_cb,
-    const RegisterEncryptionModeCb& register_encryption_mode_cb)
+    const RegisterEncryptionSchemeCb& register_encryption_scheme_cb)
     : register_cenc_ids_cb_(register_cenc_ids_cb),
-      register_encryption_mode_cb_(register_encryption_mode_cb),
+      register_encryption_scheme_cb_(register_encryption_scheme_cb),
       version_number_(-1) {}
 
 TsSectionCat::~TsSectionCat() {}
@@ -58,9 +58,9 @@
 
   Descriptors descriptors;
   int ca_pid, pssh_pid;
-  EncryptionMode mode;
+  EncryptionScheme scheme;
   RCHECK(descriptors.Read(bit_reader, section_length - 4));
-  RCHECK(descriptors.HasCADescriptorCenc(&ca_pid, &pssh_pid, &mode));
+  RCHECK(descriptors.HasCADescriptorCenc(&ca_pid, &pssh_pid, &scheme));
   int crc32;
   RCHECK(bit_reader->ReadBits(32, &crc32));
 
@@ -76,7 +76,7 @@
 
   // Can now register the PIDs and scheme.
   register_cenc_ids_cb_.Run(ca_pid, pssh_pid);
-  register_encryption_mode_cb_.Run(mode);
+  register_encryption_scheme_cb_.Run(scheme);
 
   version_number_ = version_number;
 
diff --git a/media/formats/mp2t/ts_section_cat.h b/media/formats/mp2t/ts_section_cat.h
index 637aec8..c929fad 100644
--- a/media/formats/mp2t/ts_section_cat.h
+++ b/media/formats/mp2t/ts_section_cat.h
@@ -7,7 +7,7 @@
 
 #include "base/callback.h"
 #include "base/macros.h"
-#include "media/base/decrypt_config.h"
+#include "media/base/encryption_scheme.h"
 #include "media/formats/mp2t/ts_section_psi.h"
 
 namespace media {
@@ -17,11 +17,11 @@
  public:
   // RegisterCencPidsCb::Run(int ca_pid, int pssh_pid);
   using RegisterCencPidsCb = base::RepeatingCallback<void(int, int)>;
-  // RegisterEncryptionMode::Run(EncryptionMode mode);
-  using RegisterEncryptionModeCb =
-      base::RepeatingCallback<void(EncryptionMode)>;
+  // RegisterEncryptionScheme::Run(EncryptionScheme scheme);
+  using RegisterEncryptionSchemeCb =
+      base::RepeatingCallback<void(EncryptionScheme)>;
   TsSectionCat(const RegisterCencPidsCb& register_cenc_ids_cb,
-               const RegisterEncryptionModeCb& register_encryption_mode_cb);
+               const RegisterEncryptionSchemeCb& register_encryption_scheme_cb);
   ~TsSectionCat() override;
 
   // TsSectionPsi implementation.
@@ -30,7 +30,7 @@
 
  private:
   RegisterCencPidsCb register_cenc_ids_cb_;
-  RegisterEncryptionModeCb register_encryption_mode_cb_;
+  RegisterEncryptionSchemeCb register_encryption_scheme_cb_;
 
   // Parameters from the CAT.
   int version_number_;
diff --git a/media/formats/mp4/mp4_stream_parser.cc b/media/formats/mp4/mp4_stream_parser.cc
index 60fc9932..550b2e9 100644
--- a/media/formats/mp4/mp4_stream_parser.cc
+++ b/media/formats/mp4/mp4_stream_parser.cc
@@ -42,33 +42,22 @@
 const int kMaxInvalidConversionLogs = 20;
 const int kMaxVideoKeyframeMismatchLogs = 10;
 
-// Caller should be prepared to handle return of Unencrypted() in case of
-// unsupported scheme.
-// TODO(crbug.com/825041): Remove pattern from this function.
+// Caller should be prepared to handle return of EncryptionScheme::kUnencrypted
+// in case of unsupported scheme.
 EncryptionScheme GetEncryptionScheme(const ProtectionSchemeInfo& sinf) {
   if (!sinf.HasSupportedScheme())
-    return Unencrypted();
+    return EncryptionScheme::kUnencrypted;
   FourCC fourcc = sinf.type.type;
-  EncryptionScheme::CipherMode mode = EncryptionScheme::CIPHER_MODE_UNENCRYPTED;
-  EncryptionPattern pattern;
-  bool uses_pattern_encryption = false;
   switch (fourcc) {
     case FOURCC_CENC:
-      mode = EncryptionScheme::CIPHER_MODE_AES_CTR;
-      break;
+      return EncryptionScheme::kCenc;
     case FOURCC_CBCS:
-      mode = EncryptionScheme::CIPHER_MODE_AES_CBC;
-      uses_pattern_encryption = true;
-      break;
+      return EncryptionScheme::kCbcs;
     default:
       NOTREACHED();
       break;
   }
-  if (uses_pattern_encryption) {
-    pattern = {sinf.info.track_encryption.default_crypt_byte_block,
-               sinf.info.track_encryption.default_skip_byte_block};
-  }
-  return EncryptionScheme(mode, pattern);
+  return EncryptionScheme::kUnencrypted;
 }
 }  // namespace
 
@@ -442,10 +431,10 @@
         return false;
       }
       bool is_track_encrypted = entry.sinf.info.track_encryption.is_encrypted;
-      EncryptionScheme scheme = Unencrypted();
+      EncryptionScheme scheme = EncryptionScheme::kUnencrypted;
       if (is_track_encrypted) {
         scheme = GetEncryptionScheme(entry.sinf);
-        if (!scheme.is_encrypted())
+        if (scheme == EncryptionScheme::kUnencrypted)
           return false;
       }
       audio_config.Initialize(codec, sample_format, channel_layout,
@@ -511,10 +500,10 @@
         return false;
       }
       bool is_track_encrypted = entry.sinf.info.track_encryption.is_encrypted;
-      EncryptionScheme scheme = Unencrypted();
+      EncryptionScheme scheme = EncryptionScheme::kUnencrypted;
       if (is_track_encrypted) {
         scheme = GetEncryptionScheme(entry.sinf);
-        if (!scheme.is_encrypted())
+        if (scheme == EncryptionScheme::kUnencrypted)
           return false;
       }
       video_config.Initialize(entry.video_codec, entry.video_codec_profile,
@@ -838,7 +827,7 @@
     if (!subsamples.empty()) {
       // Create a new config with the updated subsamples.
       decrypt_config.reset(
-          new DecryptConfig(decrypt_config->encryption_mode(),
+          new DecryptConfig(decrypt_config->encryption_scheme(),
                             decrypt_config->key_id(), decrypt_config->iv(),
                             subsamples, decrypt_config->encryption_pattern()));
     }
diff --git a/media/formats/mp4/track_run_iterator.cc b/media/formats/mp4/track_run_iterator.cc
index 91e0f822d..cda6de3 100644
--- a/media/formats/mp4/track_run_iterator.cc
+++ b/media/formats/mp4/track_run_iterator.cc
@@ -16,6 +16,7 @@
 #include "media/base/decrypt_config.h"
 #include "media/base/demuxer_memory_limit.h"
 #include "media/base/encryption_pattern.h"
+#include "media/base/encryption_scheme.h"
 #include "media/base/media_util.h"
 #include "media/base/timestamp_constants.h"
 #include "media/formats/mp4/rcheck.h"
@@ -56,7 +57,7 @@
   std::vector<uint8_t> aux_info_sizes;  // Populated if default_size == 0.
   int aux_info_total_size;
 
-  EncryptionMode encryption_mode;
+  EncryptionScheme encryption_scheme = EncryptionScheme::kUnencrypted;
   EncryptionPattern encryption_pattern;
 
   std::vector<CencSampleEncryptionInfoEntry> fragment_sample_encryption_info;
@@ -372,11 +373,11 @@
       }
 
       if (!sinf->HasSupportedScheme()) {
-        tri.encryption_mode = EncryptionMode::kUnencrypted;
+        tri.encryption_scheme = EncryptionScheme::kUnencrypted;
       } else {
-        tri.encryption_mode = sinf->IsCbcsEncryptionScheme()
-                                  ? EncryptionMode::kCbcs
-                                  : EncryptionMode::kCenc;
+        tri.encryption_scheme = sinf->IsCbcsEncryptionScheme()
+                                    ? EncryptionScheme::kCbcs
+                                    : EncryptionScheme::kCenc;
         tri.encryption_pattern =
             EncryptionPattern(track_encryption->default_crypt_byte_block,
                               track_encryption->default_skip_byte_block);
@@ -747,13 +748,13 @@
       std::string iv(reinterpret_cast<const char*>(
                          sample_encryption_entry.initialization_vector),
                      base::size(sample_encryption_entry.initialization_vector));
-      switch (run_itr_->encryption_mode) {
-        case EncryptionMode::kUnencrypted:
+      switch (run_itr_->encryption_scheme) {
+        case EncryptionScheme::kUnencrypted:
           return nullptr;
-        case EncryptionMode::kCenc:
+        case EncryptionScheme::kCenc:
           return DecryptConfig::CreateCencConfig(
               key_id, iv, sample_encryption_entry.subsamples);
-        case EncryptionMode::kCbcs:
+        case EncryptionScheme::kCbcs:
           return DecryptConfig::CreateCbcsConfig(
               key_id, iv, sample_encryption_entry.subsamples,
               run_itr_->encryption_pattern);
diff --git a/media/formats/mpeg/mpeg_audio_stream_parser_base.cc b/media/formats/mpeg/mpeg_audio_stream_parser_base.cc
index 137bfa4..b5fed3d 100644
--- a/media/formats/mpeg/mpeg_audio_stream_parser_base.cc
+++ b/media/formats/mpeg/mpeg_audio_stream_parser_base.cc
@@ -211,7 +211,7 @@
 
   if (!config_.IsValidConfig()) {
     config_.Initialize(audio_codec_, kSampleFormatF32, channel_layout,
-                       sample_rate, extra_data, Unencrypted(),
+                       sample_rate, extra_data, EncryptionScheme::kUnencrypted,
                        base::TimeDelta(), codec_delay_);
     if (audio_codec_ == kCodecAAC)
       config_.disable_discard_decoder_delay();
diff --git a/media/formats/webm/webm_audio_client.cc b/media/formats/webm/webm_audio_client.cc
index 6eff0dd..5ac98e8e 100644
--- a/media/formats/webm/webm_audio_client.cc
+++ b/media/formats/webm/webm_audio_client.cc
@@ -27,7 +27,7 @@
     const std::vector<uint8_t>& codec_private,
     int64_t seek_preroll,
     int64_t codec_delay,
-    const EncryptionScheme& encryption_scheme,
+    EncryptionScheme encryption_scheme,
     AudioDecoderConfig* config) {
   DCHECK(config);
   SampleFormat sample_format = kSampleFormatPlanarF32;
diff --git a/media/formats/webm/webm_audio_client.h b/media/formats/webm/webm_audio_client.h
index 5b3357da..ab65867 100644
--- a/media/formats/webm/webm_audio_client.h
+++ b/media/formats/webm/webm_audio_client.h
@@ -11,12 +11,12 @@
 #include <vector>
 
 #include "base/macros.h"
+#include "media/base/encryption_scheme.h"
 #include "media/base/media_log.h"
 #include "media/formats/webm/webm_parser.h"
 
 namespace media {
 class AudioDecoderConfig;
-class EncryptionScheme;
 
 // Helper class used to parse an Audio element inside a TrackEntry element.
 class WebMAudioClient : public WebMParserClient {
@@ -37,7 +37,7 @@
                         const std::vector<uint8_t>& codec_private,
                         const int64_t seek_preroll,
                         const int64_t codec_delay,
-                        const EncryptionScheme& encryption_scheme,
+                        EncryptionScheme encryption_scheme,
                         AudioDecoderConfig* config);
 
  private:
diff --git a/media/formats/webm/webm_tracks_parser.cc b/media/formats/webm/webm_tracks_parser.cc
index 6ae7ce8..b9a1179 100644
--- a/media/formats/webm/webm_tracks_parser.cc
+++ b/media/formats/webm/webm_tracks_parser.cc
@@ -205,8 +205,9 @@
           content_encodings()[0]->encryption_key_id();
     }
 
-    EncryptionScheme encryption_scheme =
-        encryption_key_id.empty() ? Unencrypted() : AesCtrEncryptionScheme();
+    EncryptionScheme encryption_scheme = encryption_key_id.empty()
+                                             ? EncryptionScheme::kUnencrypted
+                                             : EncryptionScheme::kCenc;
 
     if (track_type_ == kWebMTrackTypeAudio) {
       detected_audio_track_count_++;
diff --git a/media/formats/webm/webm_video_client.cc b/media/formats/webm/webm_video_client.cc
index 1500ff5..9eba79d 100644
--- a/media/formats/webm/webm_video_client.cc
+++ b/media/formats/webm/webm_video_client.cc
@@ -53,7 +53,7 @@
 bool WebMVideoClient::InitializeConfig(
     const std::string& codec_id,
     const std::vector<uint8_t>& codec_private,
-    const EncryptionScheme& encryption_scheme,
+    EncryptionScheme encryption_scheme,
     VideoDecoderConfig* config) {
   DCHECK(config);
 
diff --git a/media/formats/webm/webm_video_client.h b/media/formats/webm/webm_video_client.h
index f245ff9..3c15c463 100644
--- a/media/formats/webm/webm_video_client.h
+++ b/media/formats/webm/webm_video_client.h
@@ -11,13 +11,13 @@
 #include <vector>
 
 #include "base/macros.h"
+#include "media/base/encryption_scheme.h"
 #include "media/base/media_export.h"
 #include "media/base/media_log.h"
 #include "media/formats/webm/webm_colour_parser.h"
 #include "media/formats/webm/webm_parser.h"
 
 namespace media {
-class EncryptionScheme;
 class VideoDecoderConfig;
 
 // Helper class used to parse a Video element inside a TrackEntry element.
@@ -38,7 +38,7 @@
   // case and should not be relied upon.
   bool InitializeConfig(const std::string& codec_id,
                         const std::vector<uint8_t>& codec_private,
-                        const EncryptionScheme& encryption_scheme,
+                        EncryptionScheme encryption_scheme,
                         VideoDecoderConfig* config);
 
  private:
diff --git a/media/formats/webm/webm_video_client_unittest.cc b/media/formats/webm/webm_video_client_unittest.cc
index 4cc41190..cfc73ccc 100644
--- a/media/formats/webm/webm_video_client_unittest.cc
+++ b/media/formats/webm/webm_video_client_unittest.cc
@@ -140,7 +140,8 @@
   VideoDecoderConfig expected_config(
       kCodecVP9, profile, VideoDecoderConfig::AlphaMode::kIsOpaque,
       VideoColorSpace::REC709(), kNoTransformation, kCodedSize,
-      gfx::Rect(kCodedSize), kCodedSize, codec_private, Unencrypted());
+      gfx::Rect(kCodedSize), kCodedSize, codec_private,
+      EncryptionScheme::kUnencrypted);
 
   EXPECT_TRUE(config.Matches(expected_config))
       << "Config (" << config.AsHumanReadableString()
diff --git a/media/fuchsia/cdm/fuchsia_stream_decryptor.cc b/media/fuchsia/cdm/fuchsia_stream_decryptor.cc
index 1281e589..9c3ebb1 100644
--- a/media/fuchsia/cdm/fuchsia_stream_decryptor.cc
+++ b/media/fuchsia/cdm/fuchsia_stream_decryptor.cc
@@ -25,11 +25,11 @@
 // available, so it doesn't need more than one output buffer.
 const size_t kMinClearStreamOutputFrames = 1;
 
-std::string GetEncryptionScheme(EncryptionMode mode) {
+std::string GetEncryptionScheme(EncryptionScheme mode) {
   switch (mode) {
-    case EncryptionMode::kCenc:
+    case EncryptionScheme::kCenc:
       return fuchsia::media::ENCRYPTION_SCHEME_CENC;
-    case EncryptionMode::kCbcs:
+    case EncryptionScheme::kCbcs:
       return fuchsia::media::ENCRYPTION_SCHEME_CBCS;
     default:
       NOTREACHED() << "unknown encryption mode " << static_cast<int>(mode);
@@ -89,13 +89,13 @@
   DCHECK(config);
 
   fuchsia::media::EncryptedFormat encrypted_format;
-  encrypted_format.set_scheme(GetEncryptionScheme(config->encryption_mode()))
+  encrypted_format.set_scheme(GetEncryptionScheme(config->encryption_scheme()))
       .set_key_id(std::vector<uint8_t>(config->key_id().begin(),
                                        config->key_id().end()))
       .set_init_vector(
           std::vector<uint8_t>(config->iv().begin(), config->iv().end()))
       .set_subsamples(GetSubsamples(config->subsamples()));
-  if (config->encryption_mode() == EncryptionMode::kCbcs) {
+  if (config->encryption_scheme() == EncryptionScheme::kCbcs) {
     DCHECK(config->encryption_pattern().has_value());
     encrypted_format.set_pattern(
         GetEncryptionPattern(config->encryption_pattern().value()));
diff --git a/media/gpu/android/codec_wrapper.cc b/media/gpu/android/codec_wrapper.cc
index dc0037ff..a89cc16 100644
--- a/media/gpu/android/codec_wrapper.cc
+++ b/media/gpu/android/codec_wrapper.cc
@@ -262,7 +262,7 @@
     status = codec_->QueueSecureInputBuffer(
         input_buffer, buffer.data(), buffer.data_size(),
         decrypt_config->key_id(), decrypt_config->iv(),
-        decrypt_config->subsamples(), decrypt_config->encryption_mode(),
+        decrypt_config->subsamples(), decrypt_config->encryption_scheme(),
         decrypt_config->encryption_pattern(), buffer.timestamp());
   } else {
     status = codec_->QueueInputBuffer(input_buffer, buffer.data(),
diff --git a/media/gpu/ipc/service/vda_video_decoder_unittest.cc b/media/gpu/ipc/service/vda_video_decoder_unittest.cc
index b81c1c35..335f0653 100644
--- a/media/gpu/ipc/service/vda_video_decoder_unittest.cc
+++ b/media/gpu/ipc/service/vda_video_decoder_unittest.cc
@@ -144,7 +144,8 @@
         kCodecVP9, VP9PROFILE_PROFILE0,
         VideoDecoderConfig::AlphaMode::kIsOpaque, VideoColorSpace::REC709(),
         kNoTransformation, gfx::Size(1920, 1088), gfx::Rect(1920, 1080),
-        gfx::Size(1920, 1080), EmptyExtraData(), Unencrypted()));
+        gfx::Size(1920, 1080), EmptyExtraData(),
+        EncryptionScheme::kUnencrypted));
     RunUntilIdle();
   }
 
@@ -321,7 +322,7 @@
       kCodecVP9, VP9PROFILE_PROFILE0, VideoDecoderConfig::AlphaMode::kIsOpaque,
       VideoColorSpace::REC601(), kNoTransformation, gfx::Size(320, 240),
       gfx::Rect(320, 240), gfx::Size(320, 240), EmptyExtraData(),
-      Unencrypted()));
+      EncryptionScheme::kUnencrypted));
   EXPECT_CALL(init_cb_, Run(false));
   RunUntilIdle();
 }
@@ -331,7 +332,7 @@
       kCodecH264, H264PROFILE_BASELINE,
       VideoDecoderConfig::AlphaMode::kIsOpaque, VideoColorSpace::REC709(),
       kNoTransformation, gfx::Size(1920, 1088), gfx::Rect(1920, 1080),
-      gfx::Size(1920, 1080), EmptyExtraData(), Unencrypted()));
+      gfx::Size(1920, 1080), EmptyExtraData(), EncryptionScheme::kUnencrypted));
   EXPECT_CALL(init_cb_, Run(false));
   RunUntilIdle();
 }
@@ -342,7 +343,7 @@
       kCodecVP9, VP9PROFILE_PROFILE0, VideoDecoderConfig::AlphaMode::kIsOpaque,
       VideoColorSpace::REC709(), kNoTransformation, gfx::Size(1920, 1088),
       gfx::Rect(1920, 1080), gfx::Size(1920, 1080), EmptyExtraData(),
-      Unencrypted()));
+      EncryptionScheme::kUnencrypted));
   EXPECT_CALL(init_cb_, Run(false));
   RunUntilIdle();
 }
@@ -425,7 +426,7 @@
       kCodecVP9, VP9PROFILE_PROFILE0, VideoDecoderConfig::AlphaMode::kIsOpaque,
       VideoColorSpace::REC709(), kNoTransformation, gfx::Size(640, 480),
       gfx::Rect(640, 480), gfx::Size(1280, 480), EmptyExtraData(),
-      Unencrypted()));
+      EncryptionScheme::kUnencrypted));
   EXPECT_CALL(init_cb_, Run(true));
   RunUntilIdle();
 
diff --git a/media/gpu/video_encode_accelerator_unittest.cc b/media/gpu/video_encode_accelerator_unittest.cc
index b69c904..7834f03 100644
--- a/media/gpu/video_encode_accelerator_unittest.cc
+++ b/media/gpu/video_encode_accelerator_unittest.cc
@@ -1170,17 +1170,17 @@
   if (IsVP8(profile_)) {
     config.Initialize(kCodecVP8, VP8PROFILE_ANY, alpha_mode, VideoColorSpace(),
                       kNoTransformation, coded_size, visible_size, natural_size,
-                      EmptyExtraData(), Unencrypted());
+                      EmptyExtraData(), EncryptionScheme::kUnencrypted);
   } else if (IsVP9(profile_)) {
     config.Initialize(kCodecVP9, VP9PROFILE_PROFILE0, alpha_mode,
                       VideoColorSpace(), kNoTransformation, coded_size,
                       visible_size, natural_size, EmptyExtraData(),
-                      Unencrypted());
+                      EncryptionScheme::kUnencrypted);
   } else if (IsH264(profile_)) {
     config.Initialize(kCodecH264, H264PROFILE_MAIN, alpha_mode,
                       VideoColorSpace(), kNoTransformation, coded_size,
                       visible_size, natural_size, EmptyExtraData(),
-                      Unencrypted());
+                      EncryptionScheme::kUnencrypted);
   } else {
     LOG_ASSERT(0) << "Invalid profile " << GetProfileName(profile_);
   }
diff --git a/media/gpu/windows/d3d11_texture_selector_unittest.cc b/media/gpu/windows/d3d11_texture_selector_unittest.cc
index b24a372..84748886 100644
--- a/media/gpu/windows/d3d11_texture_selector_unittest.cc
+++ b/media/gpu/windows/d3d11_texture_selector_unittest.cc
@@ -25,14 +25,11 @@
                                          gfx::Size size,
                                          bool encrypted) {
     VideoDecoderConfig result;
-    EncryptionPattern pattern;
     result.Initialize(
         kUnknownVideoCodec,  // It doesn't matter because it won't be used.
         profile, VideoDecoderConfig::AlphaMode::kIsOpaque, VideoColorSpace(),
         kNoTransformation, size, {}, {}, {},
-        EncryptionScheme(encrypted ? EncryptionScheme::CIPHER_MODE_AES_CTR
-                                   : EncryptionScheme::CIPHER_MODE_UNENCRYPTED,
-                         pattern));
+        encrypted ? EncryptionScheme::kCenc : EncryptionScheme::kUnencrypted);
     return result;
   }
 
diff --git a/media/mojo/clients/mojo_audio_decoder_unittest.cc b/media/mojo/clients/mojo_audio_decoder_unittest.cc
index 22efe050..6946df7 100644
--- a/media/mojo/clients/mojo_audio_decoder_unittest.cc
+++ b/media/mojo/clients/mojo_audio_decoder_unittest.cc
@@ -142,7 +142,7 @@
 
     AudioDecoderConfig audio_config(kCodecVorbis, kSampleFormat, kChannelLayout,
                                     kDefaultSampleRate, EmptyExtraData(),
-                                    Unencrypted());
+                                    EncryptionScheme::kUnencrypted);
 
     mojo_audio_decoder_->Initialize(
         audio_config, nullptr,
diff --git a/media/mojo/common/media_type_converters.cc b/media/mojo/common/media_type_converters.cc
index 012d818..9337748 100644
--- a/media/mojo/common/media_type_converters.cc
+++ b/media/mojo/common/media_type_converters.cc
@@ -29,7 +29,7 @@
   mojo_decrypt_config->key_id = input.key_id();
   mojo_decrypt_config->iv = input.iv();
   mojo_decrypt_config->subsamples = input.subsamples();
-  mojo_decrypt_config->encryption_mode = input.encryption_mode();
+  mojo_decrypt_config->encryption_scheme = input.encryption_scheme();
   mojo_decrypt_config->encryption_pattern = input.encryption_pattern();
 
   return mojo_decrypt_config;
@@ -41,7 +41,7 @@
               media::mojom::DecryptConfigPtr>::
     Convert(const media::mojom::DecryptConfigPtr& input) {
   return std::make_unique<media::DecryptConfig>(
-      input->encryption_mode, input->key_id, input->iv, input->subsamples,
+      input->encryption_scheme, input->key_id, input->iv, input->subsamples,
       input->encryption_pattern);
 }
 
diff --git a/media/mojo/mojom/BUILD.gn b/media/mojo/mojom/BUILD.gn
index 6ff84fb..bf42227 100644
--- a/media/mojo/mojom/BUILD.gn
+++ b/media/mojo/mojom/BUILD.gn
@@ -131,7 +131,6 @@
   sources = [
     "audio_decoder_config_mojom_traits_unittest.cc",
     "cdm_key_information_mojom_traits_unittest.cc",
-    "encryption_scheme_mojom_traits_unittest.cc",
     "video_decoder_config_mojom_traits_unittest.cc",
     "video_frame_mojom_traits_unittest.cc",
   ]
diff --git a/media/mojo/mojom/audio_decoder_config_mojom_traits.h b/media/mojo/mojom/audio_decoder_config_mojom_traits.h
index a786d24..c95d33a 100644
--- a/media/mojo/mojom/audio_decoder_config_mojom_traits.h
+++ b/media/mojo/mojom/audio_decoder_config_mojom_traits.h
@@ -7,7 +7,6 @@
 
 #include "media/base/audio_decoder_config.h"
 #include "media/base/ipc/media_param_traits.h"
-#include "media/mojo/mojom/encryption_scheme_mojom_traits.h"
 #include "media/mojo/mojom/media_types.mojom.h"
 
 namespace mojo {
@@ -46,7 +45,7 @@
     return input.codec_delay();
   }
 
-  static const media::EncryptionScheme& encryption_scheme(
+  static media::EncryptionScheme encryption_scheme(
       const media::AudioDecoderConfig& input) {
     return input.encryption_scheme();
   }
diff --git a/media/mojo/mojom/audio_decoder_config_mojom_traits_unittest.cc b/media/mojo/mojom/audio_decoder_config_mojom_traits_unittest.cc
index f04e0cb3..16ea462 100644
--- a/media/mojo/mojom/audio_decoder_config_mojom_traits_unittest.cc
+++ b/media/mojo/mojom/audio_decoder_config_mojom_traits_unittest.cc
@@ -21,7 +21,8 @@
 
   AudioDecoderConfig input;
   input.Initialize(kCodecAAC, kSampleFormatU8, CHANNEL_LAYOUT_SURROUND, 48000,
-                   kExtraDataVector, Unencrypted(), base::TimeDelta(), 0);
+                   kExtraDataVector, EncryptionScheme::kUnencrypted,
+                   base::TimeDelta(), 0);
   std::vector<uint8_t> data =
       media::mojom::AudioDecoderConfig::Serialize(&input);
   AudioDecoderConfig output;
@@ -34,7 +35,8 @@
      ConvertAudioDecoderConfig_EmptyExtraData) {
   AudioDecoderConfig input;
   input.Initialize(kCodecAAC, kSampleFormatU8, CHANNEL_LAYOUT_SURROUND, 48000,
-                   EmptyExtraData(), Unencrypted(), base::TimeDelta(), 0);
+                   EmptyExtraData(), EncryptionScheme::kUnencrypted,
+                   base::TimeDelta(), 0);
   std::vector<uint8_t> data =
       media::mojom::AudioDecoderConfig::Serialize(&input);
   AudioDecoderConfig output;
@@ -46,8 +48,8 @@
 TEST(AudioDecoderConfigStructTraitsTest, ConvertAudioDecoderConfig_Encrypted) {
   AudioDecoderConfig input;
   input.Initialize(kCodecAAC, kSampleFormatU8, CHANNEL_LAYOUT_SURROUND, 48000,
-                   EmptyExtraData(), AesCtrEncryptionScheme(),
-                   base::TimeDelta(), 0);
+                   EmptyExtraData(), EncryptionScheme::kCenc, base::TimeDelta(),
+                   0);
   std::vector<uint8_t> data =
       media::mojom::AudioDecoderConfig::Serialize(&input);
   AudioDecoderConfig output;
diff --git a/media/mojo/mojom/encryption_pattern.typemap b/media/mojo/mojom/encryption_pattern.typemap
new file mode 100644
index 0000000..06e07e9
--- /dev/null
+++ b/media/mojo/mojom/encryption_pattern.typemap
@@ -0,0 +1,24 @@
+# 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.
+
+mojom = "//media/mojo/mojom/media_types.mojom"
+
+public_headers = [ "//media/base/encryption_pattern.h" ]
+
+traits_headers = [ "//media/mojo/mojom/encryption_pattern_mojom_traits.h" ]
+
+sources = [
+  "//media/mojo/mojom/encryption_pattern_mojom_traits.cc",
+]
+
+public_deps = [
+  "//media",
+]
+
+deps = [
+  "//media/base/ipc",
+]
+
+# See media_types.typemap for enum mappings.
+type_mappings = [ "media.mojom.EncryptionPattern=::media::EncryptionPattern" ]
diff --git a/media/mojo/mojom/encryption_pattern_mojom_traits.cc b/media/mojo/mojom/encryption_pattern_mojom_traits.cc
new file mode 100644
index 0000000..d463132
--- /dev/null
+++ b/media/mojo/mojom/encryption_pattern_mojom_traits.cc
@@ -0,0 +1,19 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/mojo/mojom/encryption_pattern_mojom_traits.h"
+
+namespace mojo {
+
+// static
+bool StructTraits<media::mojom::EncryptionPatternDataView,
+                  media::EncryptionPattern>::
+    Read(media::mojom::EncryptionPatternDataView input,
+         media::EncryptionPattern* output) {
+  *output = media::EncryptionPattern(input.crypt_byte_block(),
+                                     input.skip_byte_block());
+  return true;
+}
+
+}  // namespace mojo
diff --git a/media/mojo/mojom/encryption_pattern_mojom_traits.h b/media/mojo/mojom/encryption_pattern_mojom_traits.h
new file mode 100644
index 0000000..8694a4d
--- /dev/null
+++ b/media/mojo/mojom/encryption_pattern_mojom_traits.h
@@ -0,0 +1,31 @@
+// 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 MEDIA_MOJO_MOJOM_ENCRYPTION_PATTERN_MOJOM_TRAITS_H_
+#define MEDIA_MOJO_MOJOM_ENCRYPTION_PATTERN_MOJOM_TRAITS_H_
+
+#include "media/base/encryption_pattern.h"
+#include "media/base/ipc/media_param_traits.h"
+#include "media/mojo/mojom/media_types.mojom.h"
+
+namespace mojo {
+
+template <>
+struct StructTraits<media::mojom::EncryptionPatternDataView,
+                    media::EncryptionPattern> {
+  static uint32_t crypt_byte_block(const media::EncryptionPattern& input) {
+    return input.crypt_byte_block();
+  }
+
+  static uint32_t skip_byte_block(const media::EncryptionPattern& input) {
+    return input.skip_byte_block();
+  }
+
+  static bool Read(media::mojom::EncryptionPatternDataView input,
+                   media::EncryptionPattern* output);
+};
+
+}  // namespace mojo
+
+#endif  // MEDIA_MOJO_MOJOM_ENCRYPTION_PATTERN_MOJOM_TRAITS_H_
diff --git a/media/mojo/mojom/encryption_scheme.typemap b/media/mojo/mojom/encryption_scheme.typemap
deleted file mode 100644
index f942f83..0000000
--- a/media/mojo/mojom/encryption_scheme.typemap
+++ /dev/null
@@ -1,30 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//media/mojo/mojom/media_types.mojom"
-
-public_headers = [
-  "//media/base/encryption_scheme.h",
-  "//media/base/encryption_pattern.h",
-]
-
-traits_headers = [ "//media/mojo/mojom/encryption_scheme_mojom_traits.h" ]
-
-sources = [
-  "//media/mojo/mojom/encryption_scheme_mojom_traits.cc",
-]
-
-public_deps = [
-  "//media",
-]
-
-deps = [
-  "//media/base/ipc",
-]
-
-# See media_types.typemap for enum mappings.
-type_mappings = [
-  "media.mojom.EncryptionPattern=::media::EncryptionPattern",
-  "media.mojom.EncryptionScheme=::media::EncryptionScheme",
-]
diff --git a/media/mojo/mojom/encryption_scheme_mojom_traits.cc b/media/mojo/mojom/encryption_scheme_mojom_traits.cc
deleted file mode 100644
index a379269..0000000
--- a/media/mojo/mojom/encryption_scheme_mojom_traits.cc
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "media/mojo/mojom/encryption_scheme_mojom_traits.h"
-
-namespace mojo {
-
-// static
-bool StructTraits<media::mojom::EncryptionPatternDataView,
-                  media::EncryptionPattern>::
-    Read(media::mojom::EncryptionPatternDataView input,
-         media::EncryptionPattern* output) {
-  *output = media::EncryptionPattern(input.crypt_byte_block(),
-                                     input.skip_byte_block());
-  return true;
-}
-
-// static
-bool StructTraits<
-    media::mojom::EncryptionSchemeDataView,
-    media::EncryptionScheme>::Read(media::mojom::EncryptionSchemeDataView input,
-                                   media::EncryptionScheme* output) {
-  media::EncryptionScheme::CipherMode mode;
-  if (!input.ReadMode(&mode))
-    return false;
-
-  media::EncryptionPattern pattern;
-  if (!input.ReadPattern(&pattern))
-    return false;
-
-  *output = media::EncryptionScheme(mode, pattern);
-
-  return true;
-}
-
-}  // namespace mojo
\ No newline at end of file
diff --git a/media/mojo/mojom/encryption_scheme_mojom_traits.h b/media/mojo/mojom/encryption_scheme_mojom_traits.h
deleted file mode 100644
index c5166e5..0000000
--- a/media/mojo/mojom/encryption_scheme_mojom_traits.h
+++ /dev/null
@@ -1,49 +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 MEDIA_MOJO_MOJOM_ENCRYPTION_SCHEME_MOJOM_TRAITS_H_
-#define MEDIA_MOJO_MOJOM_ENCRYPTION_SCHEME_MOJOM_TRAITS_H_
-
-#include "media/base/encryption_pattern.h"
-#include "media/base/encryption_scheme.h"
-#include "media/base/ipc/media_param_traits.h"
-#include "media/mojo/mojom/media_types.mojom.h"
-
-namespace mojo {
-
-template <>
-struct StructTraits<media::mojom::EncryptionPatternDataView,
-                    media::EncryptionPattern> {
-  static uint32_t crypt_byte_block(const media::EncryptionPattern& input) {
-    return input.crypt_byte_block();
-  }
-
-  static uint32_t skip_byte_block(const media::EncryptionPattern& input) {
-    return input.skip_byte_block();
-  }
-
-  static bool Read(media::mojom::EncryptionPatternDataView input,
-                   media::EncryptionPattern* output);
-};
-
-template <>
-struct StructTraits<media::mojom::EncryptionSchemeDataView,
-                    media::EncryptionScheme> {
-  static media::EncryptionScheme::CipherMode mode(
-      const media::EncryptionScheme& input) {
-    return input.mode();
-  }
-
-  static media::EncryptionPattern pattern(
-      const media::EncryptionScheme& input) {
-    return input.pattern();
-  }
-
-  static bool Read(media::mojom::EncryptionSchemeDataView input,
-                   media::EncryptionScheme* output);
-};
-
-}  // namespace mojo
-
-#endif  // MEDIA_MOJO_MOJOM_ENCRYPTION_SCHEME_MOJOM_TRAITS_H_
diff --git a/media/mojo/mojom/encryption_scheme_mojom_traits_unittest.cc b/media/mojo/mojom/encryption_scheme_mojom_traits_unittest.cc
deleted file mode 100644
index 99cce07..0000000
--- a/media/mojo/mojom/encryption_scheme_mojom_traits_unittest.cc
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "media/mojo/mojom/encryption_scheme_mojom_traits.h"
-
-#include <utility>
-
-#include "media/base/encryption_scheme.h"
-#include "media/base/media_util.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-TEST(EncryptionSchemeStructTraitsTest,
-     ConvertEncryptionSchemeAesCbcWithPattern) {
-  EncryptionScheme input(EncryptionScheme::CIPHER_MODE_AES_CBC,
-                         EncryptionPattern(1, 9));
-  std::vector<uint8_t> data = media::mojom::EncryptionScheme::Serialize(&input);
-
-  EncryptionScheme output;
-  EXPECT_TRUE(
-      media::mojom::EncryptionScheme::Deserialize(std::move(data), &output));
-  EXPECT_TRUE(output.Matches(input));
-
-  // Verify a couple of negative cases.
-  EXPECT_FALSE(output.Matches(Unencrypted()));
-  EXPECT_FALSE(output.Matches(AesCtrEncryptionScheme()));
-}
-
-}  // namespace media
diff --git a/media/mojo/mojom/key_system_support.mojom b/media/mojo/mojom/key_system_support.mojom
index 99a807d..06ede1b 100644
--- a/media/mojo/mojom/key_system_support.mojom
+++ b/media/mojo/mojom/key_system_support.mojom
@@ -14,12 +14,12 @@
   // Software secure codecs and encryption schemes supported by the CDM.
   array<VideoCodec> video_codecs;
   bool supports_vp9_profile2;
-  array<EncryptionMode> encryption_schemes;
+  array<EncryptionScheme> encryption_schemes;
 
   // Hardware secure codecs and encryption schemes supported by the CDM,
   // directly or indirectly through CdmProxy.
   array<VideoCodec> hw_secure_video_codecs;
-  array<EncryptionMode> hw_secure_encryption_schemes;
+  array<EncryptionScheme> hw_secure_encryption_schemes;
 
   // Session types supported in software secure mode if no
   // |hw_secure_video_codecs| is supported, or in both modes otherwise.
diff --git a/media/mojo/mojom/media_types.mojom b/media/mojo/mojom/media_types.mojom
index 1068f46..93fb25c 100644
--- a/media/mojo/mojom/media_types.mojom
+++ b/media/mojo/mojom/media_types.mojom
@@ -80,10 +80,6 @@
 [Native]
 enum WatchTimeKey;
 
-// See media/base/decrypt_config.h for descriptions.
-[Native]
-enum EncryptionMode;
-
 // See media/base/container_names.h for descriptions.
 [Native]
 enum MediaContainerName;
@@ -99,15 +95,9 @@
   uint32 skip_byte_block;
 };
 
-// This defines a mojo transport format for media::EncryptionScheme.
 // See media/base/encryption_scheme.h for description.
-struct EncryptionScheme {
-  [Native]
-  enum CipherMode;
-
-  CipherMode mode;
-  EncryptionPattern pattern;
-};
+[Native]
+enum EncryptionScheme;
 
 // This defines a mojo transport format for media::VideoColorSpace.
 // See media/base/video_color_space.h for description.
@@ -183,7 +173,7 @@
 // This defines a mojo transport format for media::DecryptConfig.
 // See media/base/decrypt_config.h for descriptions.
 struct DecryptConfig {
-  EncryptionMode encryption_mode;
+  EncryptionScheme encryption_scheme;
   string key_id;
   string iv;
   array<SubsampleEntry> subsamples;
diff --git a/media/mojo/mojom/media_types.typemap b/media/mojo/mojom/media_types.typemap
index 02aaa323..a24f592 100644
--- a/media/mojo/mojom/media_types.typemap
+++ b/media/mojo/mojom/media_types.typemap
@@ -49,8 +49,7 @@
   "media.mojom.ChannelLayout=::media::ChannelLayout",
   "media.mojom.ColorSpace=::media::ColorSpace",
   "media.mojom.DecodeStatus=::media::DecodeStatus",
-  "media.mojom.EncryptionMode=::media::EncryptionMode",
-  "media.mojom.EncryptionScheme.CipherMode=::media::EncryptionScheme::CipherMode",
+  "media.mojom.EncryptionScheme=::media::EncryptionScheme",
   "media.mojom.MediaContainerName=::media::container_names::MediaContainerName",
   "media.mojom.MediaLogEvent=::media::MediaLogEvent",
   "media.mojom.OutputDeviceStatus=::media::OutputDeviceStatus",
@@ -64,6 +63,5 @@
   "media.mojom.VideoTransformation=::media::VideoTransformation",
   "media.mojom.WaitingReason=::media::WaitingReason",
   "media.mojom.WatchTimeKey=::media::WatchTimeKey",
-  "media.mojom.EncryptionPattern=::media::EncryptionPattern",
   "media.mojom.MediaStatusState=::media::MediaStatus::State",
 ]
diff --git a/media/mojo/mojom/typemaps.gni b/media/mojo/mojom/typemaps.gni
index 8dc9420..987ebfc 100644
--- a/media/mojo/mojom/typemaps.gni
+++ b/media/mojo/mojom/typemaps.gni
@@ -11,7 +11,7 @@
   "//media/mojo/mojom/content_decryption_module.typemap",
   "//media/mojo/mojom/decryptor.typemap",
   "//media/mojo/mojom/demuxer_stream.typemap",
-  "//media/mojo/mojom/encryption_scheme.typemap",
+  "//media/mojo/mojom/encryption_pattern.typemap",
   "//media/mojo/mojom/hdr_metadata.typemap",
   "//media/mojo/mojom/media_drm_storage.typemap",
   "//media/mojo/mojom/media_types.typemap",
diff --git a/media/mojo/mojom/video_decoder_config_mojom_traits.h b/media/mojo/mojom/video_decoder_config_mojom_traits.h
index 05fffca..0d3f4e1 100644
--- a/media/mojo/mojom/video_decoder_config_mojom_traits.h
+++ b/media/mojo/mojom/video_decoder_config_mojom_traits.h
@@ -7,7 +7,6 @@
 
 #include "media/base/ipc/media_param_traits.h"
 #include "media/base/video_decoder_config.h"
-#include "media/mojo/mojom/encryption_scheme_mojom_traits.h"
 #include "media/mojo/mojom/hdr_metadata_mojom_traits.h"
 #include "media/mojo/mojom/media_types.mojom.h"
 #include "media/mojo/mojom/video_color_space_mojom_traits.h"
@@ -50,7 +49,7 @@
     return input.extra_data();
   }
 
-  static const media::EncryptionScheme& encryption_scheme(
+  static media::EncryptionScheme encryption_scheme(
       const media::VideoDecoderConfig& input) {
     return input.encryption_scheme();
   }
diff --git a/media/mojo/mojom/video_decoder_config_mojom_traits_unittest.cc b/media/mojo/mojom/video_decoder_config_mojom_traits_unittest.cc
index 002306a8e..2be014f 100644
--- a/media/mojo/mojom/video_decoder_config_mojom_traits_unittest.cc
+++ b/media/mojo/mojom/video_decoder_config_mojom_traits_unittest.cc
@@ -28,7 +28,7 @@
   VideoDecoderConfig input(
       kCodecVP8, VP8PROFILE_ANY, VideoDecoderConfig::AlphaMode::kIsOpaque,
       VideoColorSpace(), kNoTransformation, kCodedSize, kVisibleRect,
-      kNaturalSize, kExtraDataVector, Unencrypted());
+      kNaturalSize, kExtraDataVector, EncryptionScheme::kUnencrypted);
   std::vector<uint8_t> data =
       media::mojom::VideoDecoderConfig::Serialize(&input);
   VideoDecoderConfig output;
@@ -42,7 +42,7 @@
   VideoDecoderConfig input(
       kCodecVP8, VP8PROFILE_ANY, VideoDecoderConfig::AlphaMode::kIsOpaque,
       VideoColorSpace(), kNoTransformation, kCodedSize, kVisibleRect,
-      kNaturalSize, EmptyExtraData(), Unencrypted());
+      kNaturalSize, EmptyExtraData(), EncryptionScheme::kUnencrypted);
   std::vector<uint8_t> data =
       media::mojom::VideoDecoderConfig::Serialize(&input);
   VideoDecoderConfig output;
@@ -55,7 +55,7 @@
   VideoDecoderConfig input(
       kCodecVP8, VP8PROFILE_ANY, VideoDecoderConfig::AlphaMode::kIsOpaque,
       VideoColorSpace(), kNoTransformation, kCodedSize, kVisibleRect,
-      kNaturalSize, EmptyExtraData(), AesCtrEncryptionScheme());
+      kNaturalSize, EmptyExtraData(), EncryptionScheme::kCenc);
   std::vector<uint8_t> data =
       media::mojom::VideoDecoderConfig::Serialize(&input);
   VideoDecoderConfig output;
@@ -73,7 +73,7 @@
                       VideoColorSpace::MatrixID::BT2020_CL,
                       gfx::ColorSpace::RangeID::LIMITED),
       kNoTransformation, kCodedSize, kVisibleRect, kNaturalSize,
-      EmptyExtraData(), Unencrypted());
+      EmptyExtraData(), EncryptionScheme::kUnencrypted);
   std::vector<uint8_t> data =
       media::mojom::VideoDecoderConfig::Serialize(&input);
   VideoDecoderConfig output;
@@ -87,7 +87,7 @@
   VideoDecoderConfig input(
       kCodecVP8, VP8PROFILE_ANY, VideoDecoderConfig::AlphaMode::kIsOpaque,
       VideoColorSpace(), kNoTransformation, kCodedSize, kVisibleRect,
-      kNaturalSize, EmptyExtraData(), Unencrypted());
+      kNaturalSize, EmptyExtraData(), EncryptionScheme::kUnencrypted);
   HDRMetadata hdr_metadata;
   hdr_metadata.max_frame_average_light_level = 123;
   hdr_metadata.max_content_light_level = 456;
@@ -126,10 +126,10 @@
 
   // Next try an non-empty invalid config. Natural size must not be zero.
   const gfx::Size kInvalidNaturalSize(0, 0);
-  input.Initialize(kCodecVP8, VP8PROFILE_ANY,
-                   VideoDecoderConfig::AlphaMode::kIsOpaque, VideoColorSpace(),
-                   kNoTransformation, kCodedSize, kVisibleRect,
-                   kInvalidNaturalSize, EmptyExtraData(), Unencrypted());
+  input.Initialize(
+      kCodecVP8, VP8PROFILE_ANY, VideoDecoderConfig::AlphaMode::kIsOpaque,
+      VideoColorSpace(), kNoTransformation, kCodedSize, kVisibleRect,
+      kInvalidNaturalSize, EmptyExtraData(), EncryptionScheme::kUnencrypted);
   EXPECT_FALSE(input.IsValidConfig());
 
   // Deserialize should again fail due to invalid config.
diff --git a/media/mojo/mojom/watch_time_recorder.mojom b/media/mojo/mojom/watch_time_recorder.mojom
index 8374b82..ff6737ee 100644
--- a/media/mojo/mojom/watch_time_recorder.mojom
+++ b/media/mojo/mojom/watch_time_recorder.mojom
@@ -30,8 +30,8 @@
   VideoCodecProfile video_codec_profile;
   string audio_decoder_name;
   string video_decoder_name;
-  EncryptionMode audio_encryption_scheme;
-  EncryptionMode video_encryption_scheme;
+  EncryptionScheme audio_encryption_scheme;
+  EncryptionScheme video_encryption_scheme;
   gfx.mojom.Size natural_size;  // Size of video frame; (0, 0) if audio only.
 };
 
diff --git a/media/mojo/services/watch_time_recorder_unittest.cc b/media/mojo/services/watch_time_recorder_unittest.cc
index 0fcdea77..1d60279 100644
--- a/media/mojo/services/watch_time_recorder_unittest.cc
+++ b/media/mojo/services/watch_time_recorder_unittest.cc
@@ -170,7 +170,7 @@
   mojom::SecondaryPlaybackPropertiesPtr CreateSecondaryProperties() {
     return mojom::SecondaryPlaybackProperties::New(
         kCodecAAC, kCodecH264, H264PROFILE_MAIN, "", "",
-        EncryptionMode::kUnencrypted, EncryptionMode::kUnencrypted,
+        EncryptionScheme::kUnencrypted, EncryptionScheme::kUnencrypted,
         gfx::Size(800, 600));
   }
 
@@ -590,7 +590,8 @@
   mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
       mojom::SecondaryPlaybackProperties::New(
           kCodecAAC, kCodecH264, H264PROFILE_MAIN, "", "",
-          EncryptionMode::kCenc, EncryptionMode::kCbcs, gfx::Size(800, 600));
+          EncryptionScheme::kCenc, EncryptionScheme::kCbcs,
+          gfx::Size(800, 600));
   Initialize(properties.Clone());
   wtr_->UpdateSecondaryProperties(secondary_properties.Clone());
 
@@ -651,7 +652,7 @@
   mojom::SecondaryPlaybackPropertiesPtr secondary_properties =
       mojom::SecondaryPlaybackProperties::New(
           kCodecOpus, kCodecVP9, VP9PROFILE_PROFILE0, "", "",
-          EncryptionMode::kUnencrypted, EncryptionMode::kUnencrypted,
+          EncryptionScheme::kUnencrypted, EncryptionScheme::kUnencrypted,
           gfx::Size(800, 600));
   Initialize(properties.Clone());
   wtr_->UpdateSecondaryProperties(secondary_properties.Clone());
@@ -952,8 +953,8 @@
   mojom::SecondaryPlaybackPropertiesPtr secondary_properties1 =
       mojom::SecondaryPlaybackProperties::New(
           kUnknownAudioCodec, kUnknownVideoCodec, VIDEO_CODEC_PROFILE_UNKNOWN,
-          "", "", EncryptionMode::kUnencrypted, EncryptionMode::kUnencrypted,
-          gfx::Size(800, 600));
+          "", "", EncryptionScheme::kUnencrypted,
+          EncryptionScheme::kUnencrypted, gfx::Size(800, 600));
   Initialize(properties.Clone());
   wtr_->UpdateSecondaryProperties(secondary_properties1.Clone());
 
@@ -963,8 +964,8 @@
   mojom::SecondaryPlaybackPropertiesPtr secondary_properties2 =
       mojom::SecondaryPlaybackProperties::New(
           kCodecAAC, kCodecH264, H264PROFILE_MAIN, "FFmpegAudioDecoder",
-          "FFmpegVideoDecoder", EncryptionMode::kUnencrypted,
-          EncryptionMode::kUnencrypted, gfx::Size(800, 600));
+          "FFmpegVideoDecoder", EncryptionScheme::kUnencrypted,
+          EncryptionScheme::kUnencrypted, gfx::Size(800, 600));
   wtr_->UpdateSecondaryProperties(secondary_properties2.Clone());
 
   wtr_.reset();
@@ -1015,8 +1016,8 @@
   mojom::SecondaryPlaybackPropertiesPtr secondary_properties1 =
       mojom::SecondaryPlaybackProperties::New(
           kCodecOpus, kCodecVP9, VP9PROFILE_PROFILE0, "MojoAudioDecoder",
-          "MojoVideoDecoder", EncryptionMode::kUnencrypted,
-          EncryptionMode::kUnencrypted, gfx::Size(400, 300));
+          "MojoVideoDecoder", EncryptionScheme::kUnencrypted,
+          EncryptionScheme::kUnencrypted, gfx::Size(400, 300));
   Initialize(properties.Clone());
   wtr_->UpdateSecondaryProperties(secondary_properties1.Clone());
 
@@ -1035,8 +1036,8 @@
   mojom::SecondaryPlaybackPropertiesPtr secondary_properties2 =
       mojom::SecondaryPlaybackProperties::New(
           kCodecAAC, kCodecH264, H264PROFILE_MAIN, "FFmpegAudioDecoder",
-          "FFmpegVideoDecoder", EncryptionMode::kCenc, EncryptionMode::kCenc,
-          gfx::Size(800, 600));
+          "FFmpegVideoDecoder", EncryptionScheme::kCenc,
+          EncryptionScheme::kCenc, gfx::Size(800, 600));
   wtr_->UpdateSecondaryProperties(secondary_properties2.Clone());
 
   constexpr base::TimeDelta kWatchTime2 = base::TimeDelta::FromSeconds(25);
@@ -1140,8 +1141,8 @@
   mojom::SecondaryPlaybackPropertiesPtr secondary_properties1 =
       mojom::SecondaryPlaybackProperties::New(
           kCodecOpus, kCodecVP9, VP9PROFILE_PROFILE0, "MojoAudioDecoder",
-          "MojoVideoDecoder", EncryptionMode::kUnencrypted,
-          EncryptionMode::kUnencrypted, gfx::Size(400, 300));
+          "MojoVideoDecoder", EncryptionScheme::kUnencrypted,
+          EncryptionScheme::kUnencrypted, gfx::Size(400, 300));
   Initialize(properties.Clone());
   wtr_->UpdateSecondaryProperties(secondary_properties1.Clone());
 
@@ -1160,8 +1161,8 @@
   mojom::SecondaryPlaybackPropertiesPtr secondary_properties2 =
       mojom::SecondaryPlaybackProperties::New(
           kCodecAAC, kCodecH264, H264PROFILE_MAIN, "FFmpegAudioDecoder",
-          "FFmpegVideoDecoder", EncryptionMode::kUnencrypted,
-          EncryptionMode::kUnencrypted, gfx::Size(800, 600));
+          "FFmpegVideoDecoder", EncryptionScheme::kUnencrypted,
+          EncryptionScheme::kUnencrypted, gfx::Size(800, 600));
   wtr_->UpdateSecondaryProperties(secondary_properties2.Clone());
 
   // Don't record any watch time to the new record, it should report zero watch
@@ -1247,7 +1248,7 @@
   mojom::SecondaryPlaybackPropertiesPtr secondary_properties1 =
       mojom::SecondaryPlaybackProperties::New(
           kCodecOpus, kCodecVP9, VP9PROFILE_PROFILE0, "MojoAudioDecoder",
-          "MojoVideoDecoder", EncryptionMode::kCbcs, EncryptionMode::kCbcs,
+          "MojoVideoDecoder", EncryptionScheme::kCbcs, EncryptionScheme::kCbcs,
           gfx::Size(400, 300));
   Initialize(properties.Clone());
   wtr_->UpdateSecondaryProperties(secondary_properties1.Clone());
@@ -1271,8 +1272,8 @@
   mojom::SecondaryPlaybackPropertiesPtr secondary_properties2 =
       mojom::SecondaryPlaybackProperties::New(
           kCodecAAC, kCodecH264, H264PROFILE_MAIN, "FFmpegAudioDecoder",
-          "FFmpegVideoDecoder", EncryptionMode::kUnencrypted,
-          EncryptionMode::kUnencrypted, gfx::Size(800, 600));
+          "FFmpegVideoDecoder", EncryptionScheme::kUnencrypted,
+          EncryptionScheme::kUnencrypted, gfx::Size(800, 600));
   wtr_->UpdateSecondaryProperties(secondary_properties2.Clone());
 
   constexpr base::TimeDelta kWatchTime2 = base::TimeDelta::FromSeconds(25);
@@ -1366,7 +1367,7 @@
   mojom::SecondaryPlaybackPropertiesPtr secondary_properties1 =
       mojom::SecondaryPlaybackProperties::New(
           kCodecOpus, kCodecVP9, VP9PROFILE_PROFILE0, "MojoAudioDecoder",
-          "MojoVideoDecoder", EncryptionMode::kCbcs, EncryptionMode::kCbcs,
+          "MojoVideoDecoder", EncryptionScheme::kCbcs, EncryptionScheme::kCbcs,
           gfx::Size(400, 300));
   Initialize(properties.Clone());
   wtr_->UpdateSecondaryProperties(secondary_properties1.Clone());
@@ -1384,8 +1385,8 @@
   mojom::SecondaryPlaybackPropertiesPtr secondary_properties2 =
       mojom::SecondaryPlaybackProperties::New(
           kCodecAAC, kCodecH264, H264PROFILE_MAIN, "FFmpegAudioDecoder",
-          "FFmpegVideoDecoder", EncryptionMode::kUnencrypted,
-          EncryptionMode::kUnencrypted, gfx::Size(800, 600));
+          "FFmpegVideoDecoder", EncryptionScheme::kUnencrypted,
+          EncryptionScheme::kUnencrypted, gfx::Size(800, 600));
   wtr_->UpdateSecondaryProperties(secondary_properties2.Clone());
 
   constexpr base::TimeDelta kWatchTime2 = base::TimeDelta::FromSeconds(25);
diff --git a/media/remoting/courier_renderer_unittest.cc b/media/remoting/courier_renderer_unittest.cc
index de6ed68..9f637bbb4 100644
--- a/media/remoting/courier_renderer_unittest.cc
+++ b/media/remoting/courier_renderer_unittest.cc
@@ -558,9 +558,9 @@
 }
 
 TEST_F(CourierRendererTest, OnAudioConfigChange) {
-  const AudioDecoderConfig kNewAudioConfig(kCodecVorbis, kSampleFormatPlanarF32,
-                                           CHANNEL_LAYOUT_STEREO, 44100,
-                                           EmptyExtraData(), Unencrypted());
+  const AudioDecoderConfig kNewAudioConfig(
+      kCodecVorbis, kSampleFormatPlanarF32, CHANNEL_LAYOUT_STEREO, 44100,
+      EmptyExtraData(), EncryptionScheme::kUnencrypted);
   InitializeRenderer();
   // Make sure initial audio config does not match the one we intend to send.
   ASSERT_FALSE(render_client_->audio_decoder_config().Matches(kNewAudioConfig));
diff --git a/media/remoting/fake_media_resource.cc b/media/remoting/fake_media_resource.cc
index 7381d167..e77cf87 100644
--- a/media/remoting/fake_media_resource.cc
+++ b/media/remoting/fake_media_resource.cc
@@ -22,15 +22,17 @@
   type_ = is_audio ? DemuxerStream::AUDIO : DemuxerStream::VIDEO;
   if (is_audio) {
     audio_config_.Initialize(kCodecAAC, kSampleFormatS16, CHANNEL_LAYOUT_STEREO,
-                             38400, std::vector<uint8_t>(), Unencrypted(),
-                             base::TimeDelta(), 0);
+                             38400, std::vector<uint8_t>(),
+                             EncryptionScheme::kUnencrypted, base::TimeDelta(),
+                             0);
   } else {
     gfx::Size size(640, 480);
     gfx::Rect rect(0, 0, 640, 480);
     video_config_.Initialize(kCodecH264, H264PROFILE_BASELINE,
                              VideoDecoderConfig::AlphaMode::kIsOpaque,
                              VideoColorSpace::REC601(), kNoTransformation, size,
-                             rect, size, std::vector<uint8_t>(), Unencrypted());
+                             rect, size, std::vector<uint8_t>(),
+                             EncryptionScheme::kUnencrypted);
   }
   ON_CALL(*this, Read(_))
       .WillByDefault(Invoke(this, &FakeDemuxerStream::FakeRead));
diff --git a/media/remoting/proto_enum_utils.cc b/media/remoting/proto_enum_utils.cc
index 807a48cf..a2313f6 100644
--- a/media/remoting/proto_enum_utils.cc
+++ b/media/remoting/proto_enum_utils.cc
@@ -11,26 +11,28 @@
   case OriginType::x:        \
     return OtherType::x
 
-base::Optional<EncryptionScheme::CipherMode> ToMediaEncryptionSchemeCipherMode(
+base::Optional<EncryptionScheme> ToMediaEncryptionScheme(
     pb::EncryptionScheme::CipherMode value) {
-  using OriginType = pb::EncryptionScheme;
-  using OtherType = EncryptionScheme;
   switch (value) {
-    CASE_RETURN_OTHER(CIPHER_MODE_UNENCRYPTED);
-    CASE_RETURN_OTHER(CIPHER_MODE_AES_CTR);
-    CASE_RETURN_OTHER(CIPHER_MODE_AES_CBC);
+    case pb::EncryptionScheme::CIPHER_MODE_UNENCRYPTED:
+      return EncryptionScheme::kUnencrypted;
+    case pb::EncryptionScheme::CIPHER_MODE_AES_CTR:
+      return EncryptionScheme::kCenc;
+    case pb::EncryptionScheme::CIPHER_MODE_AES_CBC:
+      return EncryptionScheme::kCbcs;
   }
   return base::nullopt;  // Not a 'default' to ensure compile-time checks.
 }
 
 base::Optional<pb::EncryptionScheme::CipherMode>
-ToProtoEncryptionSchemeCipherMode(EncryptionScheme::CipherMode value) {
-  using OriginType = EncryptionScheme;
-  using OtherType = pb::EncryptionScheme;
+ToProtoEncryptionSchemeCipherMode(EncryptionScheme value) {
   switch (value) {
-    CASE_RETURN_OTHER(CIPHER_MODE_UNENCRYPTED);
-    CASE_RETURN_OTHER(CIPHER_MODE_AES_CTR);
-    CASE_RETURN_OTHER(CIPHER_MODE_AES_CBC);
+    case EncryptionScheme::kUnencrypted:
+      return pb::EncryptionScheme::CIPHER_MODE_UNENCRYPTED;
+    case EncryptionScheme::kCenc:
+      return pb::EncryptionScheme::CIPHER_MODE_AES_CTR;
+    case EncryptionScheme::kCbcs:
+      return pb::EncryptionScheme::CIPHER_MODE_AES_CBC;
   }
   return base::nullopt;  // Not a 'default' to ensure compile-time checks.
 }
@@ -561,9 +563,10 @@
   return base::nullopt;  // Not a 'default' to ensure compile-time checks.
 }
 
-base::Optional<EncryptionMode> ToMediaEncryptionMode(pb::EncryptionMode value) {
+base::Optional<EncryptionScheme> ToMediaEncryptionScheme(
+    pb::EncryptionMode value) {
   using OriginType = pb::EncryptionMode;
-  using OtherType = EncryptionMode;
+  using OtherType = EncryptionScheme;
   switch (value) {
     CASE_RETURN_OTHER(kUnencrypted);
     CASE_RETURN_OTHER(kCenc);
@@ -572,8 +575,9 @@
   return base::nullopt;  // Not a 'default' to ensure compile-time checks.
 }
 
-base::Optional<pb::EncryptionMode> ToProtoEncryptionMode(EncryptionMode value) {
-  using OriginType = EncryptionMode;
+base::Optional<pb::EncryptionMode> ToProtoEncryptionMode(
+    EncryptionScheme value) {
+  using OriginType = EncryptionScheme;
   using OtherType = pb::EncryptionMode;
   switch (value) {
     CASE_RETURN_OTHER(kUnencrypted);
diff --git a/media/remoting/proto_enum_utils.h b/media/remoting/proto_enum_utils.h
index 113cf9f..6a92c4b 100644
--- a/media/remoting/proto_enum_utils.h
+++ b/media/remoting/proto_enum_utils.h
@@ -32,10 +32,10 @@
 // Each returns a base::Optional value. If it is not set, that indicates the
 // conversion failed.
 
-base::Optional<EncryptionScheme::CipherMode> ToMediaEncryptionSchemeCipherMode(
+base::Optional<EncryptionScheme> ToMediaEncryptionScheme(
     pb::EncryptionScheme::CipherMode value);
 base::Optional<pb::EncryptionScheme::CipherMode>
-ToProtoEncryptionSchemeCipherMode(EncryptionScheme::CipherMode value);
+ToProtoEncryptionSchemeCipherMode(EncryptionScheme value);
 
 base::Optional<AudioCodec> ToMediaAudioCodec(
     pb::AudioDecoderConfig::Codec value);
@@ -96,8 +96,10 @@
 base::Optional<pb::DemuxerStreamReadUntilCallback::Status>
 ToProtoDemuxerStreamStatus(DemuxerStream::Status value);
 
-base::Optional<EncryptionMode> ToMediaEncryptionMode(pb::EncryptionMode value);
-base::Optional<pb::EncryptionMode> ToProtoEncryptionMode(EncryptionMode value);
+base::Optional<EncryptionScheme> ToMediaEncryptionScheme(
+    pb::EncryptionMode value);
+base::Optional<pb::EncryptionMode> ToProtoEncryptionMode(
+    EncryptionScheme value);
 
 }  // namespace remoting
 }  // namespace media
diff --git a/media/remoting/proto_utils.cc b/media/remoting/proto_utils.cc
index 6d34554c..50374662 100644
--- a/media/remoting/proto_utils.cc
+++ b/media/remoting/proto_utils.cc
@@ -135,7 +135,7 @@
   }
 
   config_message->set_mode(
-      ToProtoEncryptionMode(decrypt_config.encryption_mode()).value());
+      ToProtoEncryptionMode(decrypt_config.encryption_scheme()).value());
   if (decrypt_config.HasPattern()) {
     config_message->set_crypt_byte_block(
         decrypt_config.encryption_pattern()->crypt_byte_block());
@@ -235,20 +235,23 @@
   return buffer;
 }
 
-void ConvertEncryptionSchemeToProto(const EncryptionScheme& encryption_scheme,
+void ConvertEncryptionSchemeToProto(EncryptionScheme encryption_scheme,
                                     pb::EncryptionScheme* message) {
   DCHECK(message);
+
+  // The remote side only cares about the cipher mode. Setting EncryptionPattern
+  // to (0, 0) is fine.
+  // TODO(crbug.com/1018923): Upgrade proto to remove EncryptionPattern from
+  // Audio/VideoDecoderConfig.
   message->set_mode(
-      ToProtoEncryptionSchemeCipherMode(encryption_scheme.mode()).value());
-  message->set_encrypt_blocks(encryption_scheme.pattern().crypt_byte_block());
-  message->set_skip_blocks(encryption_scheme.pattern().skip_byte_block());
+      ToProtoEncryptionSchemeCipherMode(encryption_scheme).value());
+  message->set_encrypt_blocks(0);
+  message->set_skip_blocks(0);
 }
 
 EncryptionScheme ConvertProtoToEncryptionScheme(
     const pb::EncryptionScheme& message) {
-  return EncryptionScheme(
-      ToMediaEncryptionSchemeCipherMode(message.mode()).value(),
-      EncryptionPattern(message.encrypt_blocks(), message.skip_blocks()));
+  return ToMediaEncryptionScheme(message.mode()).value();
 }
 
 void ConvertAudioDecoderConfigToProto(const AudioDecoderConfig& audio_config,
@@ -359,7 +362,6 @@
     const pb::VideoDecoderConfig& video_message,
     VideoDecoderConfig* video_config) {
   DCHECK(video_config);
-  EncryptionScheme encryption_scheme;
 
   // TODO(hubbe): Update pb to use VideoColorSpace
   VideoColorSpace color_space;
diff --git a/media/remoting/proto_utils_unittest.cc b/media/remoting/proto_utils_unittest.cc
index b20c4bb..4e1748a 100644
--- a/media/remoting/proto_utils_unittest.cc
+++ b/media/remoting/proto_utils_unittest.cc
@@ -114,8 +114,7 @@
 
 TEST_F(ProtoUtilsTest, AudioDecoderConfigConversionTest) {
   const std::string extra_data = "ACEG";
-  const EncryptionScheme encryption_scheme(
-      EncryptionScheme::CIPHER_MODE_AES_CTR, EncryptionPattern(20, 40));
+  const EncryptionScheme encryption_scheme = EncryptionScheme::kCenc;
   AudioDecoderConfig audio_config(
       kCodecAAC, kSampleFormatF32, CHANNEL_LAYOUT_MONO, 48000,
       std::vector<uint8_t>(extra_data.begin(), extra_data.end()),
diff --git a/media/remoting/renderer_controller_unittest.cc b/media/remoting/renderer_controller_unittest.cc
index 9507195..3caf8a17 100644
--- a/media/remoting/renderer_controller_unittest.cc
+++ b/media/remoting/renderer_controller_unittest.cc
@@ -288,7 +288,7 @@
 TEST_F(RendererControllerTest, WithAACAudioCodec) {
   const AudioDecoderConfig audio_config = AudioDecoderConfig(
       AudioCodec::kCodecAAC, kSampleFormatPlanarF32, CHANNEL_LAYOUT_STEREO,
-      44100, EmptyExtraData(), Unencrypted());
+      44100, EmptyExtraData(), EncryptionScheme::kUnencrypted);
   PipelineMetadata pipeline_metadata = DefaultMetadata(VideoCodec::kCodecVP8);
   pipeline_metadata.audio_decoder_config = audio_config;
   InitializeControllerAndBecomeDominant(pipeline_metadata,
@@ -316,7 +316,7 @@
 TEST_F(RendererControllerTest, WithOpusAudioCodec) {
   const AudioDecoderConfig audio_config = AudioDecoderConfig(
       AudioCodec::kCodecOpus, kSampleFormatPlanarF32, CHANNEL_LAYOUT_STEREO,
-      44100, EmptyExtraData(), Unencrypted());
+      44100, EmptyExtraData(), EncryptionScheme::kUnencrypted);
   PipelineMetadata pipeline_metadata = DefaultMetadata(VideoCodec::kCodecVP8);
   pipeline_metadata.audio_decoder_config = audio_config;
   InitializeControllerAndBecomeDominant(pipeline_metadata,
diff --git a/media/renderers/audio_renderer_impl_unittest.cc b/media/renderers/audio_renderer_impl_unittest.cc
index 6ed5f91..c9ec1283 100644
--- a/media/renderers/audio_renderer_impl_unittest.cc
+++ b/media/renderers/audio_renderer_impl_unittest.cc
@@ -106,7 +106,7 @@
         ended_(false) {
     AudioDecoderConfig audio_config(kCodec, kSampleFormat, kChannelLayout,
                                     kInputSamplesPerSecond, EmptyExtraData(),
-                                    Unencrypted());
+                                    EncryptionScheme::kUnencrypted);
     demuxer_stream_.set_audio_decoder_config(audio_config);
 
     ConfigureDemuxerStream(true);
@@ -218,9 +218,9 @@
     hardware_params_.Reset(AudioParameters::AUDIO_BITSTREAM_EAC3,
                            kChannelLayout, kOutputSamplesPerSecond, 512);
     sink_ = new FakeAudioRendererSink(hardware_params_);
-    AudioDecoderConfig audio_config(kCodecAC3, kSampleFormatEac3,
-                                    kChannelLayout, kInputSamplesPerSecond,
-                                    EmptyExtraData(), Unencrypted());
+    AudioDecoderConfig audio_config(
+        kCodecAC3, kSampleFormatEac3, kChannelLayout, kInputSamplesPerSecond,
+        EmptyExtraData(), EncryptionScheme::kUnencrypted);
     demuxer_stream_.set_audio_decoder_config(audio_config);
 
     ConfigureDemuxerStream(true);
@@ -565,7 +565,7 @@
   EXPECT_CALL(new_stream, SupportsConfigChanges()).WillOnce(Return(false));
   AudioDecoderConfig audio_config(kCodec, kSampleFormat, kChannelLayout,
                                   kInputSamplesPerSecond, EmptyExtraData(),
-                                  Unencrypted());
+                                  EncryptionScheme::kUnencrypted);
   new_stream.set_audio_decoder_config(audio_config);
 
   // The renderer is now in the flushed state and can be reinitialized.
@@ -586,7 +586,7 @@
   // that RendererClient to be signaled with the new config.
   const AudioDecoderConfig kValidAudioConfig(
       kCodecVorbis, kSampleFormatPlanarF32, CHANNEL_LAYOUT_STEREO, 44100,
-      EmptyExtraData(), Unencrypted());
+      EmptyExtraData(), EncryptionScheme::kUnencrypted);
   EXPECT_TRUE(kValidAudioConfig.IsValidConfig());
   EXPECT_CALL(*this, OnAudioConfigChange(DecoderConfigEq(kValidAudioConfig)));
   force_config_change(kValidAudioConfig);
@@ -850,7 +850,7 @@
 
   AudioDecoderConfig audio_config(
       kCodecOpus, kSampleFormat, CHANNEL_LAYOUT_DISCRETE,
-      kInputSamplesPerSecond, EmptyExtraData(), Unencrypted());
+      kInputSamplesPerSecond, EmptyExtraData(), EncryptionScheme::kUnencrypted);
   audio_config.SetChannelsForDiscrete(audio_channels);
   demuxer_stream_.set_audio_decoder_config(audio_config);
   ConfigureDemuxerStream(true);
diff --git a/media/video/video_decode_accelerator.h b/media/video/video_decode_accelerator.h
index b0a5448..262600a 100644
--- a/media/video/video_decode_accelerator.h
+++ b/media/video/video_decode_accelerator.h
@@ -137,13 +137,15 @@
     ~Config();
 
     std::string AsHumanReadableString() const;
-    bool is_encrypted() const { return encryption_scheme.is_encrypted(); }
+    bool is_encrypted() const {
+      return encryption_scheme != EncryptionScheme::kUnencrypted;
+    }
 
     // The video codec and profile.
     VideoCodecProfile profile = VIDEO_CODEC_PROFILE_UNKNOWN;
 
     // Whether the stream is encrypted, and, if so, the scheme used.
-    EncryptionScheme encryption_scheme;
+    EncryptionScheme encryption_scheme = EncryptionScheme::kUnencrypted;
 
     // The CDM that the VDA should use to decode encrypted streams. Must be
     // set to a valid ID if |is_encrypted|.
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 016a2ab..c4bed84 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -3566,6 +3566,8 @@
     "tools/quic/quic_simple_server_packet_writer.h",
     "tools/quic/quic_simple_server_session_helper.cc",
     "tools/quic/quic_simple_server_session_helper.h",
+    "tools/quic/quic_simple_server_socket.cc",
+    "tools/quic/quic_simple_server_socket.h",
     "tools/quic/synchronous_host_resolver.cc",
     "tools/quic/synchronous_host_resolver.h",
   ]
diff --git a/net/tools/quic/quic_simple_server.cc b/net/tools/quic/quic_simple_server.cc
index da14e4f..9d8f58bf 100644
--- a/net/tools/quic/quic_simple_server.cc
+++ b/net/tools/quic/quic_simple_server.cc
@@ -24,6 +24,7 @@
 #include "net/third_party/quiche/src/quic/tools/quic_simple_dispatcher.h"
 #include "net/tools/quic/quic_simple_server_packet_writer.h"
 #include "net/tools/quic/quic_simple_server_session_helper.h"
+#include "net/tools/quic/quic_simple_server_socket.h"
 
 namespace net {
 
@@ -95,50 +96,17 @@
 
 bool QuicSimpleServer::CreateUDPSocketAndListen(
     const quic::QuicSocketAddress& address) {
-  return Listen(ToIPEndPoint(address)) == 0;
+  return Listen(ToIPEndPoint(address));
 }
 
 void QuicSimpleServer::HandleEventsForever() {
   base::RunLoop().Run();
 }
 
-int QuicSimpleServer::Listen(const IPEndPoint& address) {
-  std::unique_ptr<UDPServerSocket> socket(
-      new UDPServerSocket(nullptr, NetLogSource()));
-
-  socket->AllowAddressReuse();
-
-  int rc = socket->Listen(address);
-  if (rc < 0) {
-    LOG(ERROR) << "Listen() failed: " << ErrorToString(rc);
-    return rc;
-  }
-
-  // These send and receive buffer sizes are sized for a single connection,
-  // because the default usage of QuicSimpleServer is as a test server with
-  // one or two clients.  Adjust higher for use with many clients.
-  rc = socket->SetReceiveBufferSize(
-      static_cast<int32_t>(quic::kDefaultSocketReceiveBuffer));
-  if (rc < 0) {
-    LOG(ERROR) << "SetReceiveBufferSize() failed: " << ErrorToString(rc);
-    return rc;
-  }
-
-  rc = socket->SetSendBufferSize(20 * quic::kMaxOutgoingPacketSize);
-  if (rc < 0) {
-    LOG(ERROR) << "SetSendBufferSize() failed: " << ErrorToString(rc);
-    return rc;
-  }
-
-  rc = socket->GetLocalAddress(&server_address_);
-  if (rc < 0) {
-    LOG(ERROR) << "GetLocalAddress() failed: " << ErrorToString(rc);
-    return rc;
-  }
-
-  DVLOG(1) << "Listening on " << server_address_.ToString();
-
-  socket_.swap(socket);
+bool QuicSimpleServer::Listen(const IPEndPoint& address) {
+  socket_ = CreateQuicSimpleServerSocket(address, &server_address_);
+  if (socket_ == nullptr)
+    return false;
 
   dispatcher_.reset(new quic::QuicSimpleDispatcher(
       &config_, &crypto_config_, &version_manager_,
@@ -153,7 +121,7 @@
 
   StartReading();
 
-  return OK;
+  return true;
 }
 
 void QuicSimpleServer::Shutdown() {
diff --git a/net/tools/quic/quic_simple_server.h b/net/tools/quic/quic_simple_server.h
index 8b512ff..ae8b0f7 100644
--- a/net/tools/quic/quic_simple_server.h
+++ b/net/tools/quic/quic_simple_server.h
@@ -52,8 +52,8 @@
       const quic::QuicSocketAddress& address) override;
   void HandleEventsForever() override;
 
-  // Start listening on the specified address. Returns an error code.
-  int Listen(const IPEndPoint& address);
+  // Start listening on the specified address. Returns true on success.
+  bool Listen(const IPEndPoint& address);
 
   // Server deletion is imminent. Start cleaning up.
   void Shutdown();
diff --git a/net/tools/quic/quic_simple_server_socket.cc b/net/tools/quic/quic_simple_server_socket.cc
new file mode 100644
index 0000000..5680aaa3
--- /dev/null
+++ b/net/tools/quic/quic_simple_server_socket.cc
@@ -0,0 +1,53 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/tools/quic/quic_simple_server_socket.h"
+
+#include "net/base/net_errors.h"
+#include "net/log/net_log_source.h"
+#include "net/third_party/quiche/src/quic/core/quic_constants.h"
+
+namespace net {
+
+std::unique_ptr<UDPServerSocket> CreateQuicSimpleServerSocket(
+    const IPEndPoint& address,
+    IPEndPoint* server_address) {
+  auto socket =
+      std::make_unique<UDPServerSocket>(/*net_log=*/nullptr, NetLogSource());
+
+  socket->AllowAddressReuse();
+
+  int rc = socket->Listen(address);
+  if (rc < 0) {
+    LOG(ERROR) << "Listen() failed: " << ErrorToString(rc);
+    return nullptr;
+  }
+
+  // These send and receive buffer sizes are sized for a single connection,
+  // because the default usage of QuicSimpleServer is as a test server with
+  // one or two clients.  Adjust higher for use with many clients.
+  rc = socket->SetReceiveBufferSize(
+      static_cast<int32_t>(quic::kDefaultSocketReceiveBuffer));
+  if (rc < 0) {
+    LOG(ERROR) << "SetReceiveBufferSize() failed: " << ErrorToString(rc);
+    return nullptr;
+  }
+
+  rc = socket->SetSendBufferSize(20 * quic::kMaxOutgoingPacketSize);
+  if (rc < 0) {
+    LOG(ERROR) << "SetSendBufferSize() failed: " << ErrorToString(rc);
+    return nullptr;
+  }
+
+  rc = socket->GetLocalAddress(server_address);
+  if (rc < 0) {
+    LOG(ERROR) << "GetLocalAddress() failed: " << ErrorToString(rc);
+    return nullptr;
+  }
+
+  VLOG(1) << "Listening on " << server_address->ToString();
+  return socket;
+}
+
+}  // namespace net
diff --git a/net/tools/quic/quic_simple_server_socket.h b/net/tools/quic/quic_simple_server_socket.h
new file mode 100644
index 0000000..56cf9d9
--- /dev/null
+++ b/net/tools/quic/quic_simple_server_socket.h
@@ -0,0 +1,20 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_TOOLS_QUIC_QUIC_SIMPLE_SERVER_SOCKET_H_
+#define NET_TOOLS_QUIC_QUIC_SIMPLE_SERVER_SOCKET_H_
+
+#include "net/base/ip_endpoint.h"
+#include "net/socket/udp_server_socket.h"
+
+namespace net {
+
+// Creates a UDP server socket tuned for use in a QUIC server.
+std::unique_ptr<UDPServerSocket> CreateQuicSimpleServerSocket(
+    const IPEndPoint& address,
+    IPEndPoint* server_address);
+
+}  // namespace net
+
+#endif  // NET_TOOLS_QUIC_QUIC_SIMPLE_SERVER_SOCKET_H_
diff --git a/services/service_manager/sandbox/mac/network.sb b/services/service_manager/sandbox/mac/network.sb
index ff7a3c6..e1c1d1d 100644
--- a/services/service_manager/sandbox/mac/network.sb
+++ b/services/service_manager/sandbox/mac/network.sb
@@ -108,3 +108,13 @@
 (allow sysctl-read
   (sysctl-name-regex #"^net.routetable")
 )
+
+; Kerberos support. This should be removed after GSS is moved out of the
+; network service. https://crbug.com/1017830
+(allow mach-lookup
+  (global-name "org.h5l.kcm")
+)
+(allow file-read*
+  (path "/private/etc/krb5.conf")
+  (subpath "/System/Library/KerberosPlugins/KerberosFrameworkPlugins")
+)
diff --git a/services/tracing/BUILD.gn b/services/tracing/BUILD.gn
index 72ad733..389a7cb 100644
--- a/services/tracing/BUILD.gn
+++ b/services/tracing/BUILD.gn
@@ -16,10 +16,14 @@
   sources = [
     "perfetto/consumer_host.cc",
     "perfetto/consumer_host.h",
+    "perfetto/json_trace_exporter.cc",
+    "perfetto/json_trace_exporter.h",
     "perfetto/perfetto_service.cc",
     "perfetto/perfetto_service.h",
     "perfetto/producer_host.cc",
     "perfetto/producer_host.h",
+    "perfetto/track_event_json_exporter.cc",
+    "perfetto/track_event_json_exporter.h",
     "tracing_service.cc",
     "tracing_service.h",
   ]
@@ -36,40 +40,31 @@
   ]
 
   deps = [
-    "//third_party/perfetto:libproto_to_json",
-  ]
-}
-
-source_set("json_trace_exporter") {
-  sources = [
-    "perfetto/json_trace_exporter.cc",
-    "perfetto/json_trace_exporter.h",
-    "perfetto/track_event_json_exporter.cc",
-    "perfetto/track_event_json_exporter.h",
-  ]
-  deps = [
-    "//base",
-    "//third_party/perfetto:libperfetto",
-    "//third_party/perfetto/protos/perfetto/common:lite",
-    "//third_party/perfetto/protos/perfetto/trace:lite",
-    "//third_party/perfetto/protos/perfetto/trace/chrome:lite",
     "//third_party/perfetto/protos/perfetto/trace/chrome:minimal_complete_lite",
-    "//third_party/perfetto/protos/perfetto/trace/interned_data:lite",
-    "//third_party/perfetto/protos/perfetto/trace/track_event:lite",
   ]
 }
 
 executable("trace_json_exporter") {
   sources = [
     "perfetto/json_exporter_main.cc",
+    "perfetto/json_trace_exporter.cc",
+    "perfetto/json_trace_exporter.h",
+    "perfetto/track_event_json_exporter.cc",
+    "perfetto/track_event_json_exporter.h",
   ]
 
   configs += [ "//build/config/compiler:rtti" ]
 
   deps = [
-    ":json_trace_exporter",
     "//base",
     "//third_party/perfetto:libperfetto",
+    "//third_party/perfetto/include/perfetto/protozero:protozero",
+    "//third_party/perfetto/protos/perfetto/common:lite",
+    "//third_party/perfetto/protos/perfetto/trace:lite",
+    "//third_party/perfetto/protos/perfetto/trace/chrome:lite",
+    "//third_party/perfetto/protos/perfetto/trace/chrome:minimal_complete_lite",
+    "//third_party/perfetto/protos/perfetto/trace/interned_data:lite",
+    "//third_party/perfetto/protos/perfetto/trace/track_event:lite",
     "//third_party/perfetto/src/protozero:protozero",
   ]
 }
@@ -150,7 +145,6 @@
   }
 
   deps = [
-    ":json_trace_exporter",
     ":lib",
     ":test_utils",
     "//base",
diff --git a/services/tracing/perfetto/consumer_host.cc b/services/tracing/perfetto/consumer_host.cc
index ee60eaab..aa254286 100644
--- a/services/tracing/perfetto/consumer_host.cc
+++ b/services/tracing/perfetto/consumer_host.cc
@@ -24,15 +24,13 @@
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "mojo/public/cpp/system/wait.h"
+#include "services/tracing/perfetto/json_trace_exporter.h"
 #include "services/tracing/perfetto/perfetto_service.h"
+#include "services/tracing/perfetto/track_event_json_exporter.h"
 #include "services/tracing/public/cpp/trace_event_args_whitelist.h"
-#include "third_party/perfetto/include/perfetto/ext/trace_processor/export_json.h"
 #include "third_party/perfetto/include/perfetto/ext/tracing/core/observable_events.h"
-#include "third_party/perfetto/include/perfetto/ext/tracing/core/slice.h"
 #include "third_party/perfetto/include/perfetto/ext/tracing/core/trace_packet.h"
 #include "third_party/perfetto/include/perfetto/ext/tracing/core/trace_stats.h"
-#include "third_party/perfetto/include/perfetto/trace_processor/basic_types.h"
-#include "third_party/perfetto/include/perfetto/trace_processor/trace_processor_storage.h"
 #include "third_party/perfetto/include/perfetto/tracing/core/trace_config.h"
 #include "third_party/perfetto/protos/perfetto/config/trace_config.pb.h"
 
@@ -42,44 +40,6 @@
 
 const int32_t kEnableTracingTimeoutSeconds = 10;
 
-class JsonStringOutputWriter
-    : public perfetto::trace_processor::json::OutputWriter {
- public:
-  using FlushCallback =
-      base::RepeatingCallback<void(std::string json, bool has_more)>;
-
-  JsonStringOutputWriter(FlushCallback flush_callback)
-      : flush_callback_(std::move(flush_callback)) {
-    buffer_.reserve(kBufferReserveCapacity);
-  }
-
-  ~JsonStringOutputWriter() override {
-    flush_callback_.Run(std::move(buffer_), false);
-  }
-
-  perfetto::trace_processor::util::Status AppendString(
-      const std::string& string) override {
-    buffer_ += string;
-    if (buffer_.size() > kBufferLimitInBytes) {
-      flush_callback_.Run(std::move(buffer_), true);
-      // Reset the buffer_ after moving it above.
-      buffer_.clear();
-      buffer_.reserve(kBufferReserveCapacity);
-    }
-    return perfetto::trace_processor::util::OkStatus();
-  }
-
- private:
-  static constexpr size_t kBufferLimitInBytes = 100 * 1024;
-  // Since we write each string before checking the limit, we'll always go
-  // slightly over and hence we reserve some extra space to avoid most
-  // reallocs.
-  static constexpr size_t kBufferReserveCapacity = kBufferLimitInBytes * 5 / 4;
-
-  FlushCallback flush_callback_;
-  std::string buffer_;
-};
-
 }  // namespace
 
 class ConsumerHost::StreamWriter {
@@ -331,7 +291,7 @@
 
   tracing_session_client_->OnTracingDisabled();
 
-  if (trace_processor_) {
+  if (json_trace_exporter_) {
     host_->consumer_endpoint()->ReadBuffers();
   }
 
@@ -383,24 +343,6 @@
                      weak_factory_.GetWeakPtr()),
       base::SequencedTaskRunnerHandle::Get());
 
-  if (privacy_filtering_enabled) {
-    // For filtering/whitelisting to be possible at JSON export time,
-    // filtering must not have been enabled during proto emission time
-    // (or there's nothing to pass through the whitelist).
-    DCHECK(!privacy_filtering_enabled_);
-    privacy_filtering_enabled_ = true;
-  }
-
-  json_agent_label_filter_ = agent_label_filter;
-
-  trace_processor_ =
-      perfetto::trace_processor::TraceProcessorStorage::CreateInstance(
-          perfetto::trace_processor::Config());
-
-  DisableTracing();
-}
-
-void ConsumerHost::TracingSession::ExportJson() {
   // In legacy backend, the trace event agent sets the predicate used by
   // TraceLog. For perfetto backend, ensure that predicate is always set
   // before creating the exporter. The agent can be created later than this
@@ -414,55 +356,34 @@
         base::BindRepeating(&IsMetadataWhitelisted));
   }
 
-  perfetto::trace_processor::json::ArgumentFilterPredicate argument_filter;
-  perfetto::trace_processor::json::MetadataFilterPredicate metadata_filter;
-  perfetto::trace_processor::json::LabelFilterPredicate label_filter;
-
-  if (privacy_filtering_enabled_) {
+  JSONTraceExporter::ArgumentFilterPredicate arg_filter_predicate;
+  JSONTraceExporter::MetadataFilterPredicate metadata_filter_predicate;
+  if (privacy_filtering_enabled) {
+    // For filtering/whitelisting to be possible at JSON export time,
+    // filtering must not have been enabled during proto emission time
+    // (or there's nothing to pass through the whitelist).
+    DCHECK(!privacy_filtering_enabled_);
     auto* trace_log = base::trace_event::TraceLog::GetInstance();
-    base::trace_event::ArgumentFilterPredicate argument_filter_predicate =
-        trace_log->GetArgumentFilterPredicate();
-    argument_filter =
-        [argument_filter_predicate](
-            const char* category_group_name, const char* event_name,
-            perfetto::trace_processor::json::ArgumentNameFilterPredicate*
-                name_filter) {
-          base::trace_event::ArgumentNameFilterPredicate name_filter_predicate;
-          bool result = argument_filter_predicate.Run(
-              category_group_name, event_name, &name_filter_predicate);
-          if (name_filter_predicate) {
-            *name_filter = [name_filter_predicate](const char* arg_name) {
-              return name_filter_predicate.Run(arg_name);
-            };
-          }
-          return result;
-        };
-    base::trace_event::MetadataFilterPredicate metadata_filter_predicate =
-        trace_log->GetMetadataFilterPredicate();
-    metadata_filter = [metadata_filter_predicate](const char* metadata_name) {
-      return metadata_filter_predicate.Run(metadata_name);
-    };
+    arg_filter_predicate = trace_log->GetArgumentFilterPredicate();
+    metadata_filter_predicate = trace_log->GetMetadataFilterPredicate();
   }
+  json_trace_exporter_ = std::make_unique<TrackEventJSONExporter>(
+      std::move(arg_filter_predicate), std::move(metadata_filter_predicate),
+      base::BindRepeating(&ConsumerHost::TracingSession::OnJSONTraceData,
+                          base::Unretained(this)));
 
-  if (!json_agent_label_filter_.empty()) {
-    label_filter = [this](const char* label) {
-      return strcmp(label, json_agent_label_filter_.c_str()) == 0;
-    };
-  }
+  json_trace_exporter_->set_label_filter(agent_label_filter);
 
-  JsonStringOutputWriter output_writer(base::BindRepeating(
-      &ConsumerHost::TracingSession::OnJSONTraceData, base::Unretained(this)));
-  auto status = perfetto::trace_processor::json::ExportJson(
-      trace_processor_.get(), &output_writer, argument_filter, metadata_filter,
-      label_filter);
-  DCHECK(status.ok()) << status.message();
+  DisableTracing();
 }
 
-void ConsumerHost::TracingSession::OnJSONTraceData(std::string json,
-                                                   bool has_more) {
+void ConsumerHost::TracingSession::OnJSONTraceData(
+    std::string* json,
+    base::DictionaryValue* metadata,
+    bool has_more) {
   auto slices = std::make_unique<StreamWriter::Slices>();
   slices->push_back(std::string());
-  slices->back().swap(json);
+  slices->back().swap(*json);
   read_buffers_stream_writer_.Post(FROM_HERE, &StreamWriter::WriteToStream,
                                    std::move(slices), has_more);
 
@@ -475,40 +396,10 @@
     std::vector<perfetto::TracePacket> packets,
     bool has_more) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (trace_processor_) {
-    // Calculate space needed for trace chunk. Each packet has a preamble and
-    // payload size.
-    size_t max_size = packets.size() * perfetto::TracePacket::kMaxPreambleBytes;
-    for (const auto& packet : packets) {
-      max_size += packet.size();
-    }
-
-    // Copy packets into a trace file chunk.
-    size_t position = 0;
-    std::unique_ptr<uint8_t[]> data(new uint8_t[max_size]);
-    for (perfetto::TracePacket& packet : packets) {
-      char* preamble;
-      size_t preamble_size;
-      std::tie(preamble, preamble_size) = packet.GetProtoPreamble();
-      DCHECK_LT(position + preamble_size, max_size);
-      memcpy(&data[position], preamble, preamble_size);
-      position += preamble_size;
-      for (const perfetto::Slice& slice : packet.slices()) {
-        DCHECK_LT(position + slice.size, max_size);
-        memcpy(&data[position], slice.start, slice.size);
-        position += slice.size;
-      }
-    }
-
-    auto status = trace_processor_->Parse(std::move(data), position);
-    // TODO(eseckler): There's no way to propagate this error at the moment - If
-    // one occurs on production builds, we silently ignore it and will end up
-    // producing an empty JSON result.
-    DCHECK(status.ok()) << status.message();
+  if (json_trace_exporter_) {
+    json_trace_exporter_->OnTraceData(std::move(packets), has_more);
     if (!has_more) {
-      trace_processor_->NotifyEndOfFile();
-      ExportJson();
-      trace_processor_.reset();
+      json_trace_exporter_.reset();
     }
     return;
   }
diff --git a/services/tracing/perfetto/consumer_host.h b/services/tracing/perfetto/consumer_host.h
index 64031683..63a400ca 100644
--- a/services/tracing/perfetto/consumer_host.h
+++ b/services/tracing/perfetto/consumer_host.h
@@ -23,18 +23,17 @@
 #include "third_party/perfetto/include/perfetto/ext/tracing/core/consumer.h"
 #include "third_party/perfetto/include/perfetto/ext/tracing/core/tracing_service.h"
 
+namespace base {
+class DictionaryValue;
+}
+
 namespace service_manager {
 struct BindSourceInfo;
 }  // namespace service_manager
 
-namespace perfetto {
-namespace trace_processor {
-class TraceProcessorStorage;
-}  // namespace trace_processor
-}  // namespace perfetto
-
 namespace tracing {
 
+class JSONTraceExporter;
 class PerfettoService;
 
 // This is a Mojo interface which enables any client
@@ -90,8 +89,9 @@
         DisableTracingAndEmitJsonCallback callback) override;
 
    private:
-    void ExportJson();
-    void OnJSONTraceData(std::string json, bool has_more);
+    void OnJSONTraceData(std::string* json,
+                         base::DictionaryValue* metadata,
+                         bool has_more);
     void OnEnableTracingTimeout();
     void MaybeSendEnableTracingAck();
     bool IsExpectedPid(base::ProcessId pid) const;
@@ -102,9 +102,7 @@
     bool privacy_filtering_enabled_ = false;
     base::SequenceBound<StreamWriter> read_buffers_stream_writer_;
     RequestBufferUsageCallback request_buffer_usage_callback_;
-    std::unique_ptr<perfetto::trace_processor::TraceProcessorStorage>
-        trace_processor_;
-    std::string json_agent_label_filter_;
+    std::unique_ptr<JSONTraceExporter> json_trace_exporter_;
     base::OnceCallback<void(bool)> flush_callback_;
     const mojom::TracingClientPriority tracing_priority_;
     base::OnceClosure on_disabled_callback_;
diff --git a/services/video_capture/broadcasting_receiver.cc b/services/video_capture/broadcasting_receiver.cc
index 7ca5b12..6dea411e 100644
--- a/services/video_capture/broadcasting_receiver.cc
+++ b/services/video_capture/broadcasting_receiver.cc
@@ -65,7 +65,7 @@
 }  // anonymous namespace
 
 BroadcastingReceiver::ClientContext::ClientContext(
-    mojo::PendingRemote<mojom::Receiver> client,
+    mojo::PendingRemote<mojom::VideoFrameHandler> client,
     media::VideoCaptureBufferType target_buffer_type)
     : client_(std::move(client)),
       target_buffer_type_(target_buffer_type),
@@ -242,7 +242,7 @@
 }
 
 int32_t BroadcastingReceiver::AddClient(
-    mojo::PendingRemote<mojom::Receiver> client,
+    mojo::PendingRemote<mojom::VideoFrameHandler> client,
     media::VideoCaptureBufferType target_buffer_type) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   auto client_id = next_client_id_++;
@@ -284,7 +284,7 @@
   clients_.at(client_id).set_is_suspended(false);
 }
 
-mojo::Remote<mojom::Receiver> BroadcastingReceiver::RemoveClient(
+mojo::Remote<mojom::VideoFrameHandler> BroadcastingReceiver::RemoveClient(
     int32_t client_id) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   auto client = std::move(clients_.at(client_id));
diff --git a/services/video_capture/broadcasting_receiver.h b/services/video_capture/broadcasting_receiver.h
index 32f6d6ba0..99f09c2 100644
--- a/services/video_capture/broadcasting_receiver.h
+++ b/services/video_capture/broadcasting_receiver.h
@@ -15,14 +15,14 @@
 #include "media/capture/video_capture_types.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
-#include "services/video_capture/public/mojom/receiver.mojom.h"
 #include "services/video_capture/public/mojom/scoped_access_permission.mojom.h"
+#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
 
 namespace video_capture {
 
-// Implementation of mojom::VideoFrameReceiver that distributes frames to
+// Implementation of mojom::VideoFrameHandler that distributes frames to
 // potentially multiple clients.
-class BroadcastingReceiver : public mojom::Receiver {
+class BroadcastingReceiver : public mojom::VideoFrameHandler {
  public:
   class BufferContext {
    public:
@@ -77,14 +77,14 @@
   void SetOnStoppedHandler(base::OnceClosure on_stopped_handler);
 
   // Returns a client_id that can be used for a call to Suspend/Resume/Remove.
-  int32_t AddClient(mojo::PendingRemote<mojom::Receiver> client,
+  int32_t AddClient(mojo::PendingRemote<mojom::VideoFrameHandler> client,
                     media::VideoCaptureBufferType target_buffer_type);
   void SuspendClient(int32_t client_id);
   void ResumeClient(int32_t client_id);
   // Returns ownership of the client back to the caller.
-  mojo::Remote<mojom::Receiver> RemoveClient(int32_t client_id);
+  mojo::Remote<mojom::VideoFrameHandler> RemoveClient(int32_t client_id);
 
-  // video_capture::mojom::Receiver:
+  // video_capture::mojom::VideoFrameHandler:
   void OnNewBuffer(int32_t buffer_id,
                    media::mojom::VideoBufferHandlePtr buffer_handle) override;
   void OnFrameReadyInBuffer(
@@ -115,7 +115,7 @@
   // a client is suspended.
   class ClientContext {
    public:
-    ClientContext(mojo::PendingRemote<mojom::Receiver> client,
+    ClientContext(mojo::PendingRemote<mojom::VideoFrameHandler> client,
                   media::VideoCaptureBufferType target_buffer_type);
     ~ClientContext();
     ClientContext(ClientContext&& other);
@@ -123,7 +123,7 @@
     void OnStarted();
     void OnStartedUsingGpuDecode();
 
-    mojo::Remote<mojom::Receiver>& client() { return client_; }
+    mojo::Remote<mojom::VideoFrameHandler>& client() { return client_; }
     media::VideoCaptureBufferType target_buffer_type() {
       return target_buffer_type_;
     }
@@ -131,7 +131,7 @@
     bool is_suspended() const { return is_suspended_; }
 
    private:
-    mojo::Remote<mojom::Receiver> client_;
+    mojo::Remote<mojom::VideoFrameHandler> client_;
     media::VideoCaptureBufferType target_buffer_type_;
     bool is_suspended_;
     bool on_started_has_been_called_;
diff --git a/services/video_capture/broadcasting_receiver_unittest.cc b/services/video_capture/broadcasting_receiver_unittest.cc
index 65033ca..a0caa62 100644
--- a/services/video_capture/broadcasting_receiver_unittest.cc
+++ b/services/video_capture/broadcasting_receiver_unittest.cc
@@ -10,7 +10,8 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "mojo/public/cpp/system/platform_handle.h"
-#include "services/video_capture/public/cpp/mock_receiver.h"
+#include "services/video_capture/public/cpp/mock_video_frame_handler.h"
+#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -36,16 +37,18 @@
 class BroadcastingReceiverTest : public ::testing::Test {
  public:
   void SetUp() override {
-    mojo::PendingRemote<mojom::Receiver> receiver_1;
-    mojo::PendingRemote<mojom::Receiver> receiver_2;
-    mock_receiver_1_ = std::make_unique<MockReceiver>(
-        receiver_1.InitWithNewPipeAndPassReceiver());
-    mock_receiver_2_ = std::make_unique<MockReceiver>(
-        receiver_2.InitWithNewPipeAndPassReceiver());
-    client_id_1_ = broadcaster_.AddClient(
-        std::move(receiver_1), media::VideoCaptureBufferType::kSharedMemory);
-    client_id_2_ = broadcaster_.AddClient(
-        std::move(receiver_2), media::VideoCaptureBufferType::kSharedMemory);
+    mojo::PendingRemote<mojom::VideoFrameHandler> video_frame_handler_1;
+    mojo::PendingRemote<mojom::VideoFrameHandler> video_frame_handler_2;
+    mock_video_frame_handler_1_ = std::make_unique<MockVideoFrameHandler>(
+        video_frame_handler_1.InitWithNewPipeAndPassReceiver());
+    mock_video_frame_handler_2_ = std::make_unique<MockVideoFrameHandler>(
+        video_frame_handler_2.InitWithNewPipeAndPassReceiver());
+    client_id_1_ =
+        broadcaster_.AddClient(std::move(video_frame_handler_1),
+                               media::VideoCaptureBufferType::kSharedMemory);
+    client_id_2_ =
+        broadcaster_.AddClient(std::move(video_frame_handler_2),
+                               media::VideoCaptureBufferType::kSharedMemory);
 
     shm_region_ =
         base::UnsafeSharedMemoryRegion::Create(kArbitraryDummyBufferSize);
@@ -59,8 +62,8 @@
 
  protected:
   BroadcastingReceiver broadcaster_;
-  std::unique_ptr<MockReceiver> mock_receiver_1_;
-  std::unique_ptr<MockReceiver> mock_receiver_2_;
+  std::unique_ptr<MockVideoFrameHandler> mock_video_frame_handler_1_;
+  std::unique_ptr<MockVideoFrameHandler> mock_video_frame_handler_2_;
   int32_t client_id_1_;
   int32_t client_id_2_;
   base::UnsafeSharedMemoryRegion shm_region_;
@@ -70,17 +73,17 @@
 TEST_F(
     BroadcastingReceiverTest,
     HoldsOnToAccessPermissionForRetiredBufferUntilLastClientFinishedConsuming) {
-  base::RunLoop frame_arrived_at_receiver_1;
-  base::RunLoop frame_arrived_at_receiver_2;
-  EXPECT_CALL(*mock_receiver_1_, DoOnFrameReadyInBuffer(_, _, _, _))
-      .WillOnce(InvokeWithoutArgs([&frame_arrived_at_receiver_1]() {
-        frame_arrived_at_receiver_1.Quit();
+  base::RunLoop frame_arrived_at_video_frame_handler_1;
+  base::RunLoop frame_arrived_at_video_frame_handler_2;
+  EXPECT_CALL(*mock_video_frame_handler_1_, DoOnFrameReadyInBuffer(_, _, _, _))
+      .WillOnce(InvokeWithoutArgs([&frame_arrived_at_video_frame_handler_1]() {
+        frame_arrived_at_video_frame_handler_1.Quit();
       }));
-  EXPECT_CALL(*mock_receiver_2_, DoOnFrameReadyInBuffer(_, _, _, _))
-      .WillOnce(InvokeWithoutArgs([&frame_arrived_at_receiver_2]() {
-        frame_arrived_at_receiver_2.Quit();
+  EXPECT_CALL(*mock_video_frame_handler_2_, DoOnFrameReadyInBuffer(_, _, _, _))
+      .WillOnce(InvokeWithoutArgs([&frame_arrived_at_video_frame_handler_2]() {
+        frame_arrived_at_video_frame_handler_2.Quit();
       }));
-  mock_receiver_2_->HoldAccessPermissions();
+  mock_video_frame_handler_2_->HoldAccessPermissions();
 
   mojo::PendingRemote<mojom::ScopedAccessPermission> access_permission;
   bool access_permission_has_been_released = false;
@@ -99,35 +102,37 @@
                                     std::move(access_permission),
                                     std::move(frame_info));
 
-  // mock_receiver_1_ finishes consuming immediately.
-  // mock_receiver_2_ continues consuming.
-  frame_arrived_at_receiver_1.Run();
-  frame_arrived_at_receiver_2.Run();
+  // mock_video_frame_handler_1_ finishes consuming immediately.
+  // mock_video_frame_handler_2_ continues consuming.
+  frame_arrived_at_video_frame_handler_1.Run();
+  frame_arrived_at_video_frame_handler_2.Run();
 
-  base::RunLoop buffer_retired_arrived_at_receiver_1;
-  base::RunLoop buffer_retired_arrived_at_receiver_2;
-  EXPECT_CALL(*mock_receiver_1_, DoOnBufferRetired(_))
-      .WillOnce(InvokeWithoutArgs([&buffer_retired_arrived_at_receiver_1]() {
-        buffer_retired_arrived_at_receiver_1.Quit();
-      }));
-  EXPECT_CALL(*mock_receiver_2_, DoOnBufferRetired(_))
-      .WillOnce(InvokeWithoutArgs([&buffer_retired_arrived_at_receiver_2]() {
-        buffer_retired_arrived_at_receiver_2.Quit();
-      }));
+  base::RunLoop buffer_retired_arrived_at_video_frame_handler_1;
+  base::RunLoop buffer_retired_arrived_at_video_frame_handler_2;
+  EXPECT_CALL(*mock_video_frame_handler_1_, DoOnBufferRetired(_))
+      .WillOnce(InvokeWithoutArgs(
+          [&buffer_retired_arrived_at_video_frame_handler_1]() {
+            buffer_retired_arrived_at_video_frame_handler_1.Quit();
+          }));
+  EXPECT_CALL(*mock_video_frame_handler_2_, DoOnBufferRetired(_))
+      .WillOnce(InvokeWithoutArgs(
+          [&buffer_retired_arrived_at_video_frame_handler_2]() {
+            buffer_retired_arrived_at_video_frame_handler_2.Quit();
+          }));
 
   // retire the buffer
   broadcaster_.OnBufferRetired(kArbiraryBufferId);
 
   // expect that both receivers get the retired event
-  buffer_retired_arrived_at_receiver_1.Run();
-  buffer_retired_arrived_at_receiver_2.Run();
+  buffer_retired_arrived_at_video_frame_handler_1.Run();
+  buffer_retired_arrived_at_video_frame_handler_2.Run();
 
   // expect that |access_permission| is still being held
   base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(access_permission_has_been_released);
 
-  // mock_receiver_2_ finishes consuming
-  mock_receiver_2_->ReleaseAccessPermissions();
+  // mock_video_frame_handler_2_ finishes consuming
+  mock_video_frame_handler_2_->ReleaseAccessPermissions();
 
   // expect that |access_permission| is released
   base::RunLoop().RunUntilIdle();
@@ -136,8 +141,10 @@
 
 TEST_F(BroadcastingReceiverTest,
        DoesNotHoldOnToAccessPermissionWhenAllClientsAreSuspended) {
-  EXPECT_CALL(*mock_receiver_1_, DoOnFrameReadyInBuffer(_, _, _, _)).Times(0);
-  EXPECT_CALL(*mock_receiver_2_, DoOnFrameReadyInBuffer(_, _, _, _)).Times(0);
+  EXPECT_CALL(*mock_video_frame_handler_1_, DoOnFrameReadyInBuffer(_, _, _, _))
+      .Times(0);
+  EXPECT_CALL(*mock_video_frame_handler_2_, DoOnFrameReadyInBuffer(_, _, _, _))
+      .Times(0);
 
   broadcaster_.SuspendClient(client_id_1_);
   broadcaster_.SuspendClient(client_id_2_);
diff --git a/services/video_capture/device_media_to_mojo_adapter.cc b/services/video_capture/device_media_to_mojo_adapter.cc
index 6f14e0b..b56cc785 100644
--- a/services/video_capture/device_media_to_mojo_adapter.cc
+++ b/services/video_capture/device_media_to_mojo_adapter.cc
@@ -63,14 +63,17 @@
 
 void DeviceMediaToMojoAdapter::Start(
     const media::VideoCaptureParams& requested_settings,
-    mojo::PendingRemote<mojom::Receiver> receiver_pending_remote) {
+    mojo::PendingRemote<mojom::VideoFrameHandler>
+        video_frame_handler_pending_remote) {
   DCHECK(thread_checker_.CalledOnValidThread());
-  mojo::Remote<mojom::Receiver> receiver(std::move(receiver_pending_remote));
-  receiver.set_disconnect_handler(
+  mojo::Remote<mojom::VideoFrameHandler> handler_remote(
+      std::move(video_frame_handler_pending_remote));
+  handler_remote.set_disconnect_handler(
       base::BindOnce(&DeviceMediaToMojoAdapter::OnClientConnectionErrorOrClose,
                      weak_factory_.GetWeakPtr()));
 
-  receiver_ = std::make_unique<ReceiverMojoToMediaAdapter>(std::move(receiver));
+  receiver_ =
+      std::make_unique<ReceiverMojoToMediaAdapter>(std::move(handler_remote));
   auto media_receiver = std::make_unique<media::VideoFrameReceiverOnTaskRunner>(
       receiver_->GetWeakPtr(), base::ThreadTaskRunnerHandle::Get());
 
@@ -158,8 +161,8 @@
   device_->StopAndDeAllocate();
   // We need to post the deletion of receiver to the end of the message queue,
   // because |device_->StopAndDeAllocate()| may post messages (e.g.
-  // OnBufferRetired()) to a WeakPtr to |receiver_| to this queue, and we need
-  // those messages to be sent before we invalidate the WeakPtr.
+  // OnBufferRetired()) to a WeakPtr to |receiver_| to this queue,
+  // and we need those messages to be sent before we invalidate the WeakPtr.
   base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE,
                                                   std::move(receiver_));
 }
diff --git a/services/video_capture/device_media_to_mojo_adapter.h b/services/video_capture/device_media_to_mojo_adapter.h
index b54baf74..b78c4037 100644
--- a/services/video_capture/device_media_to_mojo_adapter.h
+++ b/services/video_capture/device_media_to_mojo_adapter.h
@@ -12,6 +12,7 @@
 #include "media/capture/video_capture_types.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "services/video_capture/public/mojom/device.mojom.h"
+#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
 
 #if defined(OS_CHROMEOS)
 #include "media/capture/video/chromeos/video_capture_device_factory_chromeos.h"
@@ -38,9 +39,9 @@
   ~DeviceMediaToMojoAdapter() override;
 
   // mojom::Device implementation.
-  void Start(
-      const media::VideoCaptureParams& requested_settings,
-      mojo::PendingRemote<mojom::Receiver> receiver_pending_remote) override;
+  void Start(const media::VideoCaptureParams& requested_settings,
+             mojo::PendingRemote<mojom::VideoFrameHandler>
+                 handler_pending_remote) override;
   void MaybeSuspend() override;
   void Resume() override;
   void GetPhotoState(GetPhotoStateCallback callback) override;
diff --git a/services/video_capture/device_media_to_mojo_adapter_unittest.cc b/services/video_capture/device_media_to_mojo_adapter_unittest.cc
index c8a8738..a4d469150 100644
--- a/services/video_capture/device_media_to_mojo_adapter_unittest.cc
+++ b/services/video_capture/device_media_to_mojo_adapter_unittest.cc
@@ -9,7 +9,8 @@
 #include "base/test/task_environment.h"
 #include "media/capture/video/mock_device.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
-#include "services/video_capture/public/cpp/mock_receiver.h"
+#include "services/video_capture/public/cpp/mock_video_frame_handler.h"
+#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -24,8 +25,8 @@
   ~DeviceMediaToMojoAdapterTest() override = default;
 
   void SetUp() override {
-    mock_receiver_ = std::make_unique<MockReceiver>(
-        receiver_.InitWithNewPipeAndPassReceiver());
+    mock_video_frame_handler_ = std::make_unique<MockVideoFrameHandler>(
+        video_frame_handler_.InitWithNewPipeAndPassReceiver());
     auto mock_device = std::make_unique<media::MockDevice>();
     mock_device_ptr_ = mock_device.get();
 #if defined(OS_CHROMEOS)
@@ -48,8 +49,8 @@
  protected:
   media::MockDevice* mock_device_ptr_;
   std::unique_ptr<DeviceMediaToMojoAdapter> adapter_;
-  std::unique_ptr<MockReceiver> mock_receiver_;
-  mojo::PendingRemote<mojom::Receiver> receiver_;
+  std::unique_ptr<MockVideoFrameHandler> mock_video_frame_handler_;
+  mojo::PendingRemote<mojom::VideoFrameHandler> video_frame_handler_;
   base::test::TaskEnvironment task_environment_;
 };
 
@@ -63,19 +64,18 @@
                std::unique_ptr<media::VideoCaptureDevice::Client>* client) {
               (*client)->OnStarted();
             }));
-    EXPECT_CALL(*mock_receiver_, OnStarted()).WillOnce(Invoke([&run_loop]() {
-      run_loop.Quit();
-    }));
+    EXPECT_CALL(*mock_video_frame_handler_, OnStarted())
+        .WillOnce(Invoke([&run_loop]() { run_loop.Quit(); }));
 
     const media::VideoCaptureParams kArbitrarySettings;
-    adapter_->Start(kArbitrarySettings, std::move(receiver_));
+    adapter_->Start(kArbitrarySettings, std::move(video_frame_handler_));
     run_loop.Run();
   }
   {
     base::RunLoop run_loop;
     EXPECT_CALL(*mock_device_ptr_, DoStopAndDeAllocate())
         .WillOnce(Invoke([&run_loop]() { run_loop.Quit(); }));
-    mock_receiver_.reset();
+    mock_video_frame_handler_.reset();
     run_loop.Run();
   }
 }
@@ -94,12 +94,11 @@
                std::unique_ptr<media::VideoCaptureDevice::Client>* client) {
               (*client)->OnStarted();
             }));
-    EXPECT_CALL(*mock_receiver_, OnStarted()).WillOnce(Invoke([&run_loop]() {
-      run_loop.Quit();
-    }));
+    EXPECT_CALL(*mock_video_frame_handler_, OnStarted())
+        .WillOnce(Invoke([&run_loop]() { run_loop.Quit(); }));
 
     const media::VideoCaptureParams kArbitrarySettings;
-    adapter_->Start(kArbitrarySettings, std::move(receiver_));
+    adapter_->Start(kArbitrarySettings, std::move(video_frame_handler_));
     run_loop.Run();
   }
 
@@ -108,7 +107,7 @@
 
     // This posts invocation of the error event handler to the end of the
     // current sequence.
-    mock_receiver_.reset();
+    mock_video_frame_handler_.reset();
 
     // This destroys the DeviceMediaToMojoAdapter, which in turn posts a
     // DeleteSoon in ~ReceiverOnTaskRunner() to the end of the current sequence.
diff --git a/services/video_capture/public/cpp/BUILD.gn b/services/video_capture/public/cpp/BUILD.gn
index 98462de..83f37e5 100644
--- a/services/video_capture/public/cpp/BUILD.gn
+++ b/services/video_capture/public/cpp/BUILD.gn
@@ -32,10 +32,10 @@
     "mock_producer.h",
     "mock_push_subscription.cc",
     "mock_push_subscription.h",
-    "mock_receiver.cc",
-    "mock_receiver.h",
     "mock_video_capture_service.cc",
     "mock_video_capture_service.h",
+    "mock_video_frame_handler.cc",
+    "mock_video_frame_handler.h",
     "mock_video_source.cc",
     "mock_video_source.h",
     "mock_video_source_provider.cc",
diff --git a/services/video_capture/public/cpp/mock_receiver.cc b/services/video_capture/public/cpp/mock_video_frame_handler.cc
similarity index 66%
rename from services/video_capture/public/cpp/mock_receiver.cc
rename to services/video_capture/public/cpp/mock_video_frame_handler.cc
index 386903e..faafe864 100644
--- a/services/video_capture/public/cpp/mock_receiver.cc
+++ b/services/video_capture/public/cpp/mock_video_frame_handler.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 "services/video_capture/public/cpp/mock_receiver.h"
+#include "services/video_capture/public/cpp/mock_video_frame_handler.h"
 
 #include "base/stl_util.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
@@ -10,25 +10,26 @@
 
 namespace video_capture {
 
-MockReceiver::MockReceiver()
-    : receiver_(this), should_store_access_permissions_(false) {}
+MockVideoFrameHandler::MockVideoFrameHandler()
+    : video_frame_handler_(this), should_store_access_permissions_(false) {}
 
-MockReceiver::MockReceiver(mojo::PendingReceiver<mojom::Receiver> receiver)
-    : receiver_(this, std::move(receiver)),
+MockVideoFrameHandler::MockVideoFrameHandler(
+    mojo::PendingReceiver<mojom::VideoFrameHandler> handler)
+    : video_frame_handler_(this, std::move(handler)),
       should_store_access_permissions_(false) {}
 
-MockReceiver::~MockReceiver() = default;
+MockVideoFrameHandler::~MockVideoFrameHandler() = default;
 
-void MockReceiver::HoldAccessPermissions() {
+void MockVideoFrameHandler::HoldAccessPermissions() {
   should_store_access_permissions_ = true;
 }
 
-void MockReceiver::ReleaseAccessPermissions() {
+void MockVideoFrameHandler::ReleaseAccessPermissions() {
   should_store_access_permissions_ = false;
   access_permissions_.clear();
 }
 
-void MockReceiver::OnNewBuffer(
+void MockVideoFrameHandler::OnNewBuffer(
     int32_t buffer_id,
     media::mojom::VideoBufferHandlePtr buffer_handle) {
   CHECK(!base::Contains(known_buffer_ids_, buffer_id));
@@ -36,7 +37,7 @@
   DoOnNewBuffer(buffer_id, &buffer_handle);
 }
 
-void MockReceiver::OnFrameReadyInBuffer(
+void MockVideoFrameHandler::OnFrameReadyInBuffer(
     int32_t buffer_id,
     int32_t frame_feedback_id,
     mojo::PendingRemote<mojom::ScopedAccessPermission> access_permission,
@@ -47,7 +48,7 @@
     access_permissions_.emplace_back(std::move(access_permission));
 }
 
-void MockReceiver::OnBufferRetired(int32_t buffer_id) {
+void MockVideoFrameHandler::OnBufferRetired(int32_t buffer_id) {
   auto iter =
       std::find(known_buffer_ids_.begin(), known_buffer_ids_.end(), buffer_id);
   CHECK(iter != known_buffer_ids_.end());
diff --git a/services/video_capture/public/cpp/mock_receiver.h b/services/video_capture/public/cpp/mock_video_frame_handler.h
similarity index 77%
rename from services/video_capture/public/cpp/mock_receiver.h
rename to services/video_capture/public/cpp/mock_video_frame_handler.h
index 7761106..a38f5b6 100644
--- a/services/video_capture/public/cpp/mock_receiver.h
+++ b/services/video_capture/public/cpp/mock_video_frame_handler.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 SERVICES_VIDEO_CAPTURE_PUBLIC_CPP_MOCK_RECEIVER_H_
-#define SERVICES_VIDEO_CAPTURE_PUBLIC_CPP_MOCK_RECEIVER_H_
+#ifndef SERVICES_VIDEO_CAPTURE_PUBLIC_CPP_MOCK_VIDEO_FRAME_HANDLER_H_
+#define SERVICES_VIDEO_CAPTURE_PUBLIC_CPP_MOCK_VIDEO_FRAME_HANDLER_H_
 
 #include <vector>
 
@@ -11,17 +11,18 @@
 #include "media/mojo/mojom/media_types.mojom.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
-#include "services/video_capture/public/mojom/receiver.mojom.h"
 #include "services/video_capture/public/mojom/scoped_access_permission.mojom.h"
+#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 namespace video_capture {
 
-class MockReceiver : public mojom::Receiver {
+class MockVideoFrameHandler : public mojom::VideoFrameHandler {
  public:
-  MockReceiver();
-  explicit MockReceiver(mojo::PendingReceiver<mojom::Receiver> receiver);
-  ~MockReceiver() override;
+  MockVideoFrameHandler();
+  explicit MockVideoFrameHandler(
+      mojo::PendingReceiver<mojom::VideoFrameHandler> handler);
+  ~MockVideoFrameHandler() override;
 
   void HoldAccessPermissions();
   void ReleaseAccessPermissions();
@@ -52,7 +53,7 @@
   MOCK_METHOD0(OnStopped, void());
 
  private:
-  const mojo::Receiver<mojom::Receiver> receiver_;
+  const mojo::Receiver<mojom::VideoFrameHandler> video_frame_handler_;
   std::vector<int32_t> known_buffer_ids_;
   bool should_store_access_permissions_;
   std::vector<mojom::ScopedAccessPermissionPtr> access_permissions_;
@@ -60,4 +61,4 @@
 
 }  // namespace video_capture
 
-#endif  // SERVICES_VIDEO_CAPTURE_PUBLIC_CPP_MOCK_RECEIVER_H_
+#endif  // SERVICES_VIDEO_CAPTURE_PUBLIC_CPP_MOCK_VIDEO_FRAME_HANDLER_H_
diff --git a/services/video_capture/public/cpp/mock_video_source.cc b/services/video_capture/public/cpp/mock_video_source.cc
index 33d49fc..2fcc498 100644
--- a/services/video_capture/public/cpp/mock_video_source.cc
+++ b/services/video_capture/public/cpp/mock_video_source.cc
@@ -11,7 +11,7 @@
 MockVideoSource::~MockVideoSource() = default;
 
 void MockVideoSource::CreatePushSubscription(
-    mojo::PendingRemote<video_capture::mojom::Receiver> subscriber,
+    mojo::PendingRemote<video_capture::mojom::VideoFrameHandler> subscriber,
     const media::VideoCaptureParams& requested_settings,
     bool force_reopen_with_new_settings,
     mojo::PendingReceiver<video_capture::mojom::PushVideoStreamSubscription>
diff --git a/services/video_capture/public/cpp/mock_video_source.h b/services/video_capture/public/cpp/mock_video_source.h
index de9c48bd..ea65c82f 100644
--- a/services/video_capture/public/cpp/mock_video_source.h
+++ b/services/video_capture/public/cpp/mock_video_source.h
@@ -6,7 +6,7 @@
 #define SERVICES_VIDEO_CAPTURE_PUBLIC_CPP_MOCK_VIDEO_SOURCE_H_
 
 #include "mojo/public/cpp/bindings/pending_remote.h"
-#include "services/video_capture/public/mojom/receiver.mojom.h"
+#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
 #include "services/video_capture/public/mojom/video_source.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
@@ -18,7 +18,7 @@
   ~MockVideoSource() override;
 
   void CreatePushSubscription(
-      mojo::PendingRemote<video_capture::mojom::Receiver> subscriber,
+      mojo::PendingRemote<video_capture::mojom::VideoFrameHandler> subscriber,
       const media::VideoCaptureParams& requested_settings,
       bool force_reopen_with_new_settings,
       mojo::PendingReceiver<video_capture::mojom::PushVideoStreamSubscription>
@@ -27,7 +27,8 @@
 
   MOCK_METHOD5(
       DoCreatePushSubscription,
-      void(mojo::PendingRemote<video_capture::mojom::Receiver> subscriber,
+      void(mojo::PendingRemote<video_capture::mojom::VideoFrameHandler>
+               subscriber,
            const media::VideoCaptureParams& requested_settings,
            bool force_reopen_with_new_settings,
            mojo::PendingReceiver<
diff --git a/services/video_capture/public/cpp/receiver_media_to_mojo_adapter.h b/services/video_capture/public/cpp/receiver_media_to_mojo_adapter.h
index 25afe1469..1c913be 100644
--- a/services/video_capture/public/cpp/receiver_media_to_mojo_adapter.h
+++ b/services/video_capture/public/cpp/receiver_media_to_mojo_adapter.h
@@ -7,19 +7,19 @@
 
 #include "media/capture/video/video_frame_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
-#include "services/video_capture/public/mojom/receiver.mojom.h"
+#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
 
 namespace video_capture {
 
 // Adapter that allows a media::VideoFrameReceiver to be used in place of
-// a mojom::VideoFrameReceiver.
-class ReceiverMediaToMojoAdapter : public mojom::Receiver {
+// a mojom::VideoFrameHandler.
+class ReceiverMediaToMojoAdapter : public mojom::VideoFrameHandler {
  public:
   ReceiverMediaToMojoAdapter(
       std::unique_ptr<media::VideoFrameReceiver> receiver);
   ~ReceiverMediaToMojoAdapter() override;
 
-  // video_capture::mojom::Receiver:
+  // video_capture::mojom::VideoFrameHandler:
   void OnNewBuffer(int32_t buffer_id,
                    media::mojom::VideoBufferHandlePtr buffer_handle) override;
   void OnFrameReadyInBuffer(
diff --git a/services/video_capture/public/mojom/BUILD.gn b/services/video_capture/public/mojom/BUILD.gn
index 74cabff..4807a00 100644
--- a/services/video_capture/public/mojom/BUILD.gn
+++ b/services/video_capture/public/mojom/BUILD.gn
@@ -10,10 +10,10 @@
     "device_factory.mojom",
     "devices_changed_observer.mojom",
     "producer.mojom",
-    "receiver.mojom",
     "scoped_access_permission.mojom",
     "testing_controls.mojom",
     "video_capture_service.mojom",
+    "video_frame_handler.mojom",
     "video_source.mojom",
     "video_source_provider.mojom",
     "virtual_device.mojom",
diff --git a/services/video_capture/public/mojom/device.mojom b/services/video_capture/public/mojom/device.mojom
index 9dae2f1..cc8c868 100644
--- a/services/video_capture/public/mojom/device.mojom
+++ b/services/video_capture/public/mojom/device.mojom
@@ -6,18 +6,18 @@
 
 import "media/capture/mojom/video_capture_types.mojom";
 import "media/capture/mojom/image_capture.mojom";
-import "services/video_capture/public/mojom/receiver.mojom";
+import "services/video_capture/public/mojom/video_frame_handler.mojom";
 
-// Represents access to a video capture device available on the machine.
-// The device is stopped automatically when the message pipe corresponding to
-// either the Device or the given |receiver| is closed. Note that as a response
-// to stopping the device, the service may still need to send out events such as
-// Receiver.OnBufferRetired() to |receiver|. The service will send a final event
-// Receiver.OnStopped() to indicate that stopping has completed and no further
-// events are going to be sent to |receiver|.
+// Represents access to a video capture device available on the machine. The
+// device is stopped automatically when the message pipe corresponding to either
+// the Device or the given |handler| is closed. Note that as a response to
+// stopping the device, the service may still need to send out events such as
+// VideoFrameHandler.OnBufferRetired() to |handler|. The service will send a
+// final event VideoFrameHandler.OnStopped() to indicate that stopping has
+// completed and no further events are going to be sent to |handler|.
 interface Device {
   Start(media.mojom.VideoCaptureParams requested_settings,
-        pending_remote<Receiver> receiver);
+        pending_remote<VideoFrameHandler> handler);
   MaybeSuspend();
   Resume();
   GetPhotoState()
diff --git a/services/video_capture/public/mojom/receiver.mojom b/services/video_capture/public/mojom/video_frame_handler.mojom
similarity index 75%
rename from services/video_capture/public/mojom/receiver.mojom
rename to services/video_capture/public/mojom/video_frame_handler.mojom
index b9f3274c..d9e6bd94 100644
--- a/services/video_capture/public/mojom/receiver.mojom
+++ b/services/video_capture/public/mojom/video_frame_handler.mojom
@@ -10,32 +10,31 @@
 // Callback interface for receiving data and messages from a
 // video_capture.mojom.Device or
 // video_capture.mojom.PushVideoStreamSubscription.
-// TODO: Rename to VideoFrameHandler to avoid confusion with the new Mojo types
-// e.g. when currently declaring a mojo::Remote<video_capture::mojom::Receiver>.
-interface Receiver {
+interface VideoFrameHandler {
   // Indicates that the producer is going to subsequently use the provided
   // buffer as one of possibly many for frame delivery via
   // OnFrameReadyInBuffer(). Note, that a call to this method does not mean that
-  // the caller allows the receiver to read from or write to the buffer just
-  // yet. Temporary permission to read will be given with subsequent calls to
+  // the caller allows the handler to read from or write to the buffer just yet.
+  // Temporary permission to read will be given with subsequent calls to
   // OnFrameReadyInBuffer().
   OnNewBuffer(int32 buffer_id, media.mojom.VideoBufferHandle buffer_handle);
 
   // Indicates that a new frame is ready for consumption in the buffer with id
   // |buffer_id| and allows it to read the data from the buffer. The producer
   // guarantees that the buffer and its contents stay alive and unchanged until
-  // VideoFrameReceiver releases the given |access_permission|.
+  // VideoFrameHandler releases the given |access_permission|.
   OnFrameReadyInBuffer(int32 buffer_id, int32 frame_feedback_id,
                        pending_remote<ScopedAccessPermission> access_permission,
                        media.mojom.VideoFrameInfo frame_info);
 
   // Indicates that the producer is no longer going to use the buffer with id
-  // |buffer_id| for frame delivery. This may be called even while the receiver
+  // |buffer_id| for frame delivery. This may be called even while the handler
   // is still holding |access_permission| from a call to OnFrameReadInBuffer()
   // for the same buffer. In that case, it means that the caller is asking the
-  // VideoFrameReceiver to release the read permission and buffer handle at its
-  //  earliest convenience. After this call, a producer may immediately reuse
-  // the retired |buffer_id| with a new buffer via a call to OnNewBuffer().
+  // VideoFrameHaVideoFrameReceiverndler to release the read permission and
+  // buffer handle at its earliest convenience. After this call, a producer may
+  // immediately reuse the retired |buffer_id| with a new buffer via a call to
+  // OnNewBuffer().
   OnBufferRetired(int32 buffer_id);
 
   // Indicates that an error occurred on the producer side.
@@ -51,6 +50,6 @@
   OnStartedUsingGpuDecode();
 
   // Indicates that the producer side has stopped and is not going to make any
-  // further calls to this Receiver.
+  // further calls to this VideoFrameHandler.
   OnStopped();
 };
diff --git a/services/video_capture/public/mojom/video_source.mojom b/services/video_capture/public/mojom/video_source.mojom
index 6a6cdc75..bbbdac7 100644
--- a/services/video_capture/public/mojom/video_source.mojom
+++ b/services/video_capture/public/mojom/video_source.mojom
@@ -6,7 +6,7 @@
 
 import "media/capture/mojom/image_capture.mojom";
 import "media/capture/mojom/video_capture_types.mojom";
-import "services/video_capture/public/mojom/receiver.mojom";
+import "services/video_capture/public/mojom/video_frame_handler.mojom";
 
 enum CreatePushSubscriptionResultCode {
   kCreatedWithRequestedSettings,
@@ -81,7 +81,7 @@
   // potentially briefly interrupt the video stream received by other
   // subscribers.
   CreatePushSubscription(
-      pending_remote<Receiver> subscriber,
+      pending_remote<VideoFrameHandler> subscriber,
       media.mojom.VideoCaptureParams requested_settings,
       bool force_reopen_with_new_settings,
       pending_receiver<PushVideoStreamSubscription> subscription)
diff --git a/services/video_capture/push_video_stream_subscription_impl.cc b/services/video_capture/push_video_stream_subscription_impl.cc
index c26a62b..ebdcb01 100644
--- a/services/video_capture/push_video_stream_subscription_impl.cc
+++ b/services/video_capture/push_video_stream_subscription_impl.cc
@@ -13,7 +13,7 @@
 PushVideoStreamSubscriptionImpl::PushVideoStreamSubscriptionImpl(
     mojo::PendingReceiver<mojom::PushVideoStreamSubscription>
         subscription_receiver,
-    mojo::PendingRemote<mojom::Receiver> subscriber,
+    mojo::PendingRemote<mojom::VideoFrameHandler> subscriber,
     const media::VideoCaptureParams& requested_settings,
     mojom::VideoSource::CreatePushSubscriptionCallback creation_callback,
     BroadcastingReceiver* broadcaster,
diff --git a/services/video_capture/push_video_stream_subscription_impl.h b/services/video_capture/push_video_stream_subscription_impl.h
index 8040bd43..febad473 100644
--- a/services/video_capture/push_video_stream_subscription_impl.h
+++ b/services/video_capture/push_video_stream_subscription_impl.h
@@ -9,7 +9,7 @@
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/video_capture/public/mojom/device.mojom.h"
-#include "services/video_capture/public/mojom/receiver.mojom.h"
+#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
 #include "services/video_capture/public/mojom/video_source.mojom.h"
 
 namespace video_capture {
@@ -22,7 +22,7 @@
   PushVideoStreamSubscriptionImpl(
       mojo::PendingReceiver<mojom::PushVideoStreamSubscription>
           subscription_receiver,
-      mojo::PendingRemote<mojom::Receiver> subscriber,
+      mojo::PendingRemote<mojom::VideoFrameHandler> subscriber,
       const media::VideoCaptureParams& requested_settings,
       mojom::VideoSource::CreatePushSubscriptionCallback creation_callback,
       BroadcastingReceiver* broadcaster,
@@ -58,7 +58,7 @@
   void OnConnectionLost();
 
   mojo::Receiver<mojom::PushVideoStreamSubscription> receiver_;
-  mojo::PendingRemote<mojom::Receiver> subscriber_;
+  mojo::PendingRemote<mojom::VideoFrameHandler> subscriber_;
   const media::VideoCaptureParams requested_settings_;
   mojom::VideoSource::CreatePushSubscriptionCallback creation_callback_;
   BroadcastingReceiver* const broadcaster_;
diff --git a/services/video_capture/receiver_mojo_to_media_adapter.cc b/services/video_capture/receiver_mojo_to_media_adapter.cc
index db9797e..4205ae6b 100644
--- a/services/video_capture/receiver_mojo_to_media_adapter.cc
+++ b/services/video_capture/receiver_mojo_to_media_adapter.cc
@@ -10,8 +10,8 @@
 namespace video_capture {
 
 ReceiverMojoToMediaAdapter::ReceiverMojoToMediaAdapter(
-    mojo::Remote<mojom::Receiver> receiver)
-    : receiver_(std::move(receiver)) {}
+    mojo::Remote<mojom::VideoFrameHandler> handler)
+    : video_frame_handler_(std::move(handler)) {}
 
 ReceiverMojoToMediaAdapter::~ReceiverMojoToMediaAdapter() = default;
 
@@ -23,7 +23,7 @@
 void ReceiverMojoToMediaAdapter::OnNewBuffer(
     int buffer_id,
     media::mojom::VideoBufferHandlePtr buffer_handle) {
-  receiver_->OnNewBuffer(buffer_id, std::move(buffer_handle));
+  video_frame_handler_->OnNewBuffer(buffer_id, std::move(buffer_handle));
 }
 
 void ReceiverMojoToMediaAdapter::OnFrameReadyInBuffer(
@@ -38,38 +38,38 @@
       std::make_unique<ScopedAccessPermissionMediaToMojoAdapter>(
           std::move(access_permission)),
       access_permission_proxy.InitWithNewPipeAndPassReceiver());
-  receiver_->OnFrameReadyInBuffer(buffer_id, frame_feedback_id,
-                                  std::move(access_permission_proxy),
-                                  std::move(frame_info));
+  video_frame_handler_->OnFrameReadyInBuffer(buffer_id, frame_feedback_id,
+                                             std::move(access_permission_proxy),
+                                             std::move(frame_info));
 }
 
 void ReceiverMojoToMediaAdapter::OnBufferRetired(int buffer_id) {
-  receiver_->OnBufferRetired(buffer_id);
+  video_frame_handler_->OnBufferRetired(buffer_id);
 }
 
 void ReceiverMojoToMediaAdapter::OnError(media::VideoCaptureError error) {
-  receiver_->OnError(error);
+  video_frame_handler_->OnError(error);
 }
 
 void ReceiverMojoToMediaAdapter::OnFrameDropped(
     media::VideoCaptureFrameDropReason reason) {
-  receiver_->OnFrameDropped(reason);
+  video_frame_handler_->OnFrameDropped(reason);
 }
 
 void ReceiverMojoToMediaAdapter::OnLog(const std::string& message) {
-  receiver_->OnLog(message);
+  video_frame_handler_->OnLog(message);
 }
 
 void ReceiverMojoToMediaAdapter::OnStarted() {
-  receiver_->OnStarted();
+  video_frame_handler_->OnStarted();
 }
 
 void ReceiverMojoToMediaAdapter::OnStartedUsingGpuDecode() {
-  receiver_->OnStartedUsingGpuDecode();
+  video_frame_handler_->OnStartedUsingGpuDecode();
 }
 
 void ReceiverMojoToMediaAdapter::OnStopped() {
-  receiver_->OnStopped();
+  video_frame_handler_->OnStopped();
 }
 
 }  // namespace video_capture
diff --git a/services/video_capture/receiver_mojo_to_media_adapter.h b/services/video_capture/receiver_mojo_to_media_adapter.h
index ee4f2a3..88c6a92 100644
--- a/services/video_capture/receiver_mojo_to_media_adapter.h
+++ b/services/video_capture/receiver_mojo_to_media_adapter.h
@@ -8,7 +8,7 @@
 #include "base/single_thread_task_runner.h"
 #include "media/capture/video/video_frame_receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
-#include "services/video_capture/public/mojom/receiver.mojom.h"
+#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
 
 namespace video_capture {
 
@@ -16,7 +16,7 @@
 // a media::VideoFrameReceiver.
 class ReceiverMojoToMediaAdapter : public media::VideoFrameReceiver {
  public:
-  ReceiverMojoToMediaAdapter(mojo::Remote<mojom::Receiver> receiver);
+  ReceiverMojoToMediaAdapter(mojo::Remote<mojom::VideoFrameHandler> handler);
   ~ReceiverMojoToMediaAdapter() override;
 
   base::WeakPtr<media::VideoFrameReceiver> GetWeakPtr();
@@ -40,7 +40,7 @@
   void OnStopped() override;
 
  private:
-  mojo::Remote<mojom::Receiver> receiver_;
+  mojo::Remote<mojom::VideoFrameHandler> video_frame_handler_;
   base::WeakPtrFactory<ReceiverMojoToMediaAdapter> weak_factory_{this};
 };
 
diff --git a/services/video_capture/scoped_access_permission_media_to_mojo_adapter.h b/services/video_capture/scoped_access_permission_media_to_mojo_adapter.h
index e0c4e33..10c39973 100644
--- a/services/video_capture/scoped_access_permission_media_to_mojo_adapter.h
+++ b/services/video_capture/scoped_access_permission_media_to_mojo_adapter.h
@@ -6,7 +6,6 @@
 #define SERVICES_VIDEO_CAPTURE_SCOPED_ACCESS_PERMISSION_MEDIA_TO_MOJO_ADAPTER_H_
 
 #include "media/capture/video/video_capture_device_client.h"
-#include "services/video_capture/public/mojom/receiver.mojom.h"
 #include "services/video_capture/public/mojom/scoped_access_permission.mojom.h"
 
 namespace video_capture {
diff --git a/services/video_capture/shared_memory_virtual_device_mojo_adapter.cc b/services/video_capture/shared_memory_virtual_device_mojo_adapter.cc
index a3f60d7..6067b0d 100644
--- a/services/video_capture/shared_memory_virtual_device_mojo_adapter.cc
+++ b/services/video_capture/shared_memory_virtual_device_mojo_adapter.cc
@@ -74,8 +74,8 @@
       known_buffer_ids_.erase(entry_iter);
       if (producer_.is_bound())
         producer_->OnBufferRetired(buffer_id_to_drop);
-      if (receiver_.is_bound()) {
-        receiver_->OnBufferRetired(buffer_id_to_drop);
+      if (video_frame_handler_.is_bound()) {
+        video_frame_handler_->OnBufferRetired(buffer_id_to_drop);
       }
     }
   }
@@ -87,12 +87,12 @@
   }
 
   if (!base::Contains(known_buffer_ids_, buffer_id)) {
-    if (receiver_.is_bound()) {
+    if (video_frame_handler_.is_bound()) {
       media::mojom::VideoBufferHandlePtr buffer_handle =
           media::mojom::VideoBufferHandle::New();
       buffer_handle->set_shared_buffer_handle(
           buffer_pool_->DuplicateAsMojoBuffer(buffer_id));
-      receiver_->OnNewBuffer(buffer_id, std::move(buffer_handle));
+      video_frame_handler_->OnNewBuffer(buffer_id, std::move(buffer_handle));
     }
     known_buffer_ids_.push_back(buffer_id);
 
@@ -132,7 +132,7 @@
   }
 
   // Notify receiver if there is one.
-  if (receiver_.is_bound()) {
+  if (video_frame_handler_.is_bound()) {
     buffer_pool_->HoldForConsumers(buffer_id, 1 /* num_clients */);
     auto access_permission = std::make_unique<
         media::ScopedBufferPoolReservation<media::ConsumerReleaseTraits>>(
@@ -142,22 +142,22 @@
         std::make_unique<ScopedAccessPermissionMediaToMojoAdapter>(
             std::move(access_permission)),
         access_permission_proxy.InitWithNewPipeAndPassReceiver());
-    receiver_->OnFrameReadyInBuffer(buffer_id, 0 /* frame_feedback_id */,
-                                    std::move(access_permission_proxy),
-                                    std::move(frame_info));
+    video_frame_handler_->OnFrameReadyInBuffer(
+        buffer_id, 0 /* frame_feedback_id */,
+        std::move(access_permission_proxy), std::move(frame_info));
   }
   buffer_pool_->RelinquishProducerReservation(buffer_id);
 }
 
 void SharedMemoryVirtualDeviceMojoAdapter::Start(
     const media::VideoCaptureParams& requested_settings,
-    mojo::PendingRemote<mojom::Receiver> receiver) {
+    mojo::PendingRemote<mojom::VideoFrameHandler> handler) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  receiver_.Bind(std::move(receiver));
-  receiver_.set_disconnect_handler(base::BindOnce(
+  video_frame_handler_.Bind(std::move(handler));
+  video_frame_handler_.set_disconnect_handler(base::BindOnce(
       &SharedMemoryVirtualDeviceMojoAdapter::OnReceiverConnectionErrorOrClose,
       base::Unretained(this)));
-  receiver_->OnStarted();
+  video_frame_handler_->OnStarted();
 
   // Notify receiver of known buffers */
   for (auto buffer_id : known_buffer_ids_) {
@@ -165,7 +165,7 @@
         media::mojom::VideoBufferHandle::New();
     buffer_handle->set_shared_buffer_handle(
         buffer_pool_->DuplicateAsMojoBuffer(buffer_id));
-    receiver_->OnNewBuffer(buffer_id, std::move(buffer_handle));
+    video_frame_handler_->OnNewBuffer(buffer_id, std::move(buffer_handle));
   }
 }
 
@@ -196,15 +196,15 @@
 
 void SharedMemoryVirtualDeviceMojoAdapter::Stop() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!receiver_.is_bound())
+  if (!video_frame_handler_.is_bound())
     return;
   // Unsubscribe from connection error callbacks.
-  receiver_.set_disconnect_handler(base::OnceClosure());
+  video_frame_handler_.set_disconnect_handler(base::OnceClosure());
   // Send out OnBufferRetired events and OnStopped.
   for (auto buffer_id : known_buffer_ids_)
-    receiver_->OnBufferRetired(buffer_id);
-  receiver_->OnStopped();
-  receiver_.reset();
+    video_frame_handler_->OnBufferRetired(buffer_id);
+  video_frame_handler_->OnStopped();
+  video_frame_handler_.reset();
 }
 
 void SharedMemoryVirtualDeviceMojoAdapter::OnReceiverConnectionErrorOrClose() {
diff --git a/services/video_capture/shared_memory_virtual_device_mojo_adapter.h b/services/video_capture/shared_memory_virtual_device_mojo_adapter.h
index 792a954e..c1f86f2 100644
--- a/services/video_capture/shared_memory_virtual_device_mojo_adapter.h
+++ b/services/video_capture/shared_memory_virtual_device_mojo_adapter.h
@@ -12,7 +12,7 @@
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/video_capture/public/mojom/device.mojom.h"
 #include "services/video_capture/public/mojom/producer.mojom.h"
-#include "services/video_capture/public/mojom/receiver.mojom.h"
+#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
 #include "services/video_capture/public/mojom/virtual_device.mojom.h"
 
 namespace video_capture {
@@ -37,7 +37,7 @@
 
   // mojom::Device implementation.
   void Start(const media::VideoCaptureParams& requested_settings,
-             mojo::PendingRemote<mojom::Receiver> receiver) override;
+             mojo::PendingRemote<mojom::VideoFrameHandler> receiver) override;
   void MaybeSuspend() override;
   void Resume() override;
   void GetPhotoState(GetPhotoStateCallback callback) override;
@@ -54,7 +54,7 @@
  private:
   void OnReceiverConnectionErrorOrClose();
 
-  mojo::Remote<mojom::Receiver> receiver_;
+  mojo::Remote<mojom::VideoFrameHandler> video_frame_handler_;
   mojo::Remote<mojom::Producer> producer_;
   const bool send_buffer_handles_to_producer_as_raw_file_descriptors_;
   scoped_refptr<media::VideoCaptureBufferPool> buffer_pool_;
diff --git a/services/video_capture/test/fake_device_descriptor_unittest.cc b/services/video_capture/test/fake_device_descriptor_unittest.cc
index 5fe588cc..5cb1ee29 100644
--- a/services/video_capture/test/fake_device_descriptor_unittest.cc
+++ b/services/video_capture/test/fake_device_descriptor_unittest.cc
@@ -7,8 +7,9 @@
 #include "base/run_loop.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
-#include "services/video_capture/public/cpp/mock_receiver.h"
+#include "services/video_capture/public/cpp/mock_video_frame_handler.h"
 #include "services/video_capture/public/mojom/device.mojom.h"
+#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
 #include "services/video_capture/test/fake_device_descriptor_test.h"
 
 using testing::_;
@@ -120,10 +121,11 @@
       media::PowerLineFrequency::FREQUENCY_DEFAULT;
 
   base::RunLoop wait_loop_2;
-  mojo::PendingRemote<mojom::Receiver> subscriber;
-  MockReceiver receiver(subscriber.InitWithNewPipeAndPassReceiver());
-  EXPECT_CALL(receiver, DoOnNewBuffer(_, _)).Times(AtLeast(1));
-  EXPECT_CALL(receiver, DoOnFrameReadyInBuffer(_, _, _, _))
+  mojo::PendingRemote<mojom::VideoFrameHandler> subscriber;
+  MockVideoFrameHandler video_frame_handler(
+      subscriber.InitWithNewPipeAndPassReceiver());
+  EXPECT_CALL(video_frame_handler, DoOnNewBuffer(_, _)).Times(AtLeast(1));
+  EXPECT_CALL(video_frame_handler, DoOnFrameReadyInBuffer(_, _, _, _))
       .WillRepeatedly(
           InvokeWithoutArgs([&wait_loop_2]() { wait_loop_2.Quit(); }));
 
diff --git a/services/video_capture/test/fake_device_unittest.cc b/services/video_capture/test/fake_device_unittest.cc
index 442ae9b5..f2b280d 100644
--- a/services/video_capture/test/fake_device_unittest.cc
+++ b/services/video_capture/test/fake_device_unittest.cc
@@ -10,9 +10,10 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "services/video_capture/broadcasting_receiver.h"
 #include "services/video_capture/device_media_to_mojo_adapter.h"
-#include "services/video_capture/public/cpp/mock_receiver.h"
+#include "services/video_capture/public/cpp/mock_video_frame_handler.h"
 #include "services/video_capture/public/mojom/constants.mojom.h"
 #include "services/video_capture/public/mojom/device_factory.mojom.h"
+#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
 #include "services/video_capture/test/fake_device_test.h"
 
 using testing::_;
@@ -33,10 +34,11 @@
   // https://social.msdn.microsoft.com/Forums/SqlServer/4abf18bd-4ae4-4c72-ba3e-3b13e7909d5f
   static const int kNumFramesToWaitFor = 3;
   int num_frames_arrived = 0;
-  mojo::PendingRemote<mojom::Receiver> receiver_proxy;
-  MockReceiver receiver(receiver_proxy.InitWithNewPipeAndPassReceiver());
-  EXPECT_CALL(receiver, DoOnNewBuffer(_, _)).Times(AtLeast(1));
-  EXPECT_CALL(receiver, DoOnFrameReadyInBuffer(_, _, _, _))
+  mojo::PendingRemote<mojom::VideoFrameHandler> handler_remote;
+  MockVideoFrameHandler video_frame_handler(
+      handler_remote.InitWithNewPipeAndPassReceiver());
+  EXPECT_CALL(video_frame_handler, DoOnNewBuffer(_, _)).Times(AtLeast(1));
+  EXPECT_CALL(video_frame_handler, DoOnFrameReadyInBuffer(_, _, _, _))
       .WillRepeatedly(InvokeWithoutArgs([&wait_loop, &num_frames_arrived]() {
         num_frames_arrived += 1;
         if (num_frames_arrived >= kNumFramesToWaitFor) {
@@ -45,7 +47,7 @@
       }));
 
   i420_fake_device_remote_->Start(requestable_settings_,
-                                  std::move(receiver_proxy));
+                                  std::move(handler_remote));
   wait_loop.Run();
 }
 
@@ -58,20 +60,21 @@
   // https://social.msdn.microsoft.com/Forums/SqlServer/4abf18bd-4ae4-4c72-ba3e-3b13e7909d5f
   static const int kNumFramesToWaitFor = 3;
   int num_frames_arrived = 0;
-  mojo::PendingRemote<mojom::Receiver> receiver_proxy;
-  MockReceiver receiver(receiver_proxy.InitWithNewPipeAndPassReceiver());
-  EXPECT_CALL(receiver, DoOnNewBuffer(_, _)).Times(AtLeast(1));
-  EXPECT_CALL(receiver, DoOnFrameReadyInBuffer(_, _, _, _))
+  mojo::PendingRemote<mojom::VideoFrameHandler> handler_remote;
+  MockVideoFrameHandler video_frame_handler(
+      handler_remote.InitWithNewPipeAndPassReceiver());
+  EXPECT_CALL(video_frame_handler, DoOnNewBuffer(_, _)).Times(AtLeast(1));
+  EXPECT_CALL(video_frame_handler, DoOnFrameReadyInBuffer(_, _, _, _))
       .WillRepeatedly(InvokeWithoutArgs([&wait_loop, &num_frames_arrived]() {
         num_frames_arrived += 1;
         if (num_frames_arrived >= kNumFramesToWaitFor) {
           wait_loop.Quit();
         }
       }));
-  EXPECT_CALL(receiver, OnStartedUsingGpuDecode()).Times(0);
+  EXPECT_CALL(video_frame_handler, OnStartedUsingGpuDecode()).Times(0);
 
   mjpeg_fake_device_remote_->Start(requestable_settings_,
-                                   std::move(receiver_proxy));
+                                   std::move(handler_remote));
   wait_loop.Run();
 }
 
@@ -87,12 +90,13 @@
   static const int kNumFramesToWaitFor = kMaxBufferPoolBuffers + 3;
   int num_buffers_created = 0;
   int num_frames_arrived = 0;
-  mojo::PendingRemote<mojom::Receiver> receiver_proxy;
-  MockReceiver receiver(receiver_proxy.InitWithNewPipeAndPassReceiver());
-  EXPECT_CALL(receiver, DoOnNewBuffer(_, _))
+  mojo::PendingRemote<mojom::VideoFrameHandler> handler_remote;
+  MockVideoFrameHandler video_frame_handler(
+      handler_remote.InitWithNewPipeAndPassReceiver());
+  EXPECT_CALL(video_frame_handler, DoOnNewBuffer(_, _))
       .WillRepeatedly(InvokeWithoutArgs(
           [&num_buffers_created]() { num_buffers_created++; }));
-  EXPECT_CALL(receiver, DoOnFrameReadyInBuffer(_, _, _, _))
+  EXPECT_CALL(video_frame_handler, DoOnFrameReadyInBuffer(_, _, _, _))
       .WillRepeatedly(InvokeWithoutArgs([&wait_loop, &num_frames_arrived]() {
         if (++num_frames_arrived >= kNumFramesToWaitFor) {
           wait_loop.Quit();
@@ -100,7 +104,7 @@
       }));
 
   i420_fake_device_remote_->Start(requestable_settings_,
-                                  std::move(receiver_proxy));
+                                  std::move(handler_remote));
   wait_loop.Run();
 
   ASSERT_LT(num_buffers_created, num_frames_arrived);
@@ -114,15 +118,16 @@
   static const int kNumFramesToWaitFor = 2;
   std::vector<int32_t> known_buffer_ids;
   int num_frames_arrived = 0;
-  mojo::PendingRemote<mojom::Receiver> receiver_proxy;
-  MockReceiver receiver(receiver_proxy.InitWithNewPipeAndPassReceiver());
-  EXPECT_CALL(receiver, DoOnNewBuffer(_, _))
+  mojo::PendingRemote<mojom::VideoFrameHandler> handler_remote;
+  MockVideoFrameHandler video_frame_handler(
+      handler_remote.InitWithNewPipeAndPassReceiver());
+  EXPECT_CALL(video_frame_handler, DoOnNewBuffer(_, _))
       .WillRepeatedly(
           Invoke([&known_buffer_ids](int32_t buffer_id,
                                      media::mojom::VideoBufferHandlePtr*) {
             known_buffer_ids.push_back(buffer_id);
           }));
-  EXPECT_CALL(receiver, DoOnFrameReadyInBuffer(_, _, _, _))
+  EXPECT_CALL(video_frame_handler, DoOnFrameReadyInBuffer(_, _, _, _))
       .WillRepeatedly(
           InvokeWithoutArgs([&wait_for_frames_loop, &num_frames_arrived]() {
             if (++num_frames_arrived >= kNumFramesToWaitFor) {
@@ -131,18 +136,18 @@
           }));
 
   i420_fake_device_remote_->Start(requestable_settings_,
-                                  std::move(receiver_proxy));
+                                  std::move(handler_remote));
   wait_for_frames_loop.Run();
 
   base::RunLoop wait_for_on_stopped_loop;
-  EXPECT_CALL(receiver, DoOnBufferRetired(_))
+  EXPECT_CALL(video_frame_handler, DoOnBufferRetired(_))
       .WillRepeatedly(Invoke([&known_buffer_ids](int32_t buffer_id) {
         auto iter = std::find(known_buffer_ids.begin(), known_buffer_ids.end(),
                               buffer_id);
         ASSERT_TRUE(iter != known_buffer_ids.end());
         known_buffer_ids.erase(iter);
       }));
-  EXPECT_CALL(receiver, OnStopped())
+  EXPECT_CALL(video_frame_handler, OnStopped())
       .WillOnce(Invoke(
           [&wait_for_on_stopped_loop]() { wait_for_on_stopped_loop.Quit(); }));
 
@@ -161,9 +166,10 @@
   static const int kNumFramesToWaitFor = 3;
   int num_frames_arrived = 0;
   std::map<int32_t, media::mojom::VideoBufferHandlePtr> buffers_by_id;
-  mojo::PendingRemote<mojom::Receiver> receiver_proxy;
-  MockReceiver receiver(receiver_proxy.InitWithNewPipeAndPassReceiver());
-  EXPECT_CALL(receiver, DoOnNewBuffer(_, _))
+  mojo::PendingRemote<mojom::VideoFrameHandler> handler_remote;
+  MockVideoFrameHandler video_frame_handler(
+      handler_remote.InitWithNewPipeAndPassReceiver());
+  EXPECT_CALL(video_frame_handler, DoOnNewBuffer(_, _))
       .Times(AtLeast(1))
       .WillRepeatedly(Invoke(
           [&buffers_by_id](int32_t buffer_id,
@@ -172,8 +178,8 @@
                 (*buffer_handle)->is_shared_memory_via_raw_file_descriptor());
             // |buffer_handle| is a |VideoBufferHandlePtr*| only because gmock
             // doesn't handle move-only types. Because |buffer_handle| is not
-            // used in the MockReceiver implementation of |OnNewBuffer|, it is
-            // safe to move the reference.
+            // used in the MockVideoFrameHandler implementation of
+            // |OnNewBuffer|, it is safe to move the reference.
             BroadcastingReceiver::BufferContext context(
                 buffer_id, std::move(*buffer_handle));
             // Use |context| to convert the raw file descriptor handle to a
@@ -184,7 +190,7 @@
                                media::VideoCaptureBufferType::kSharedMemory)));
           }));
   bool found_unexpected_all_zero_frame = false;
-  EXPECT_CALL(receiver, DoOnFrameReadyInBuffer(_, _, _, _))
+  EXPECT_CALL(video_frame_handler, DoOnFrameReadyInBuffer(_, _, _, _))
       .WillRepeatedly(
           Invoke([&wait_loop, &num_frames_arrived, &buffers_by_id,
                   &found_unexpected_all_zero_frame](
@@ -221,7 +227,7 @@
   settings_to_request.buffer_type =
       media::VideoCaptureBufferType::kSharedMemoryViaRawFileDescriptor;
   i420_fake_device_remote_->Start(settings_to_request,
-                                  std::move(receiver_proxy));
+                                  std::move(handler_remote));
   wait_loop.Run();
   EXPECT_FALSE(found_unexpected_all_zero_frame);
 }
diff --git a/services/video_capture/test/mock_device_shared_access_unittest.cc b/services/video_capture/test/mock_device_shared_access_unittest.cc
index 89133bf..04ea779 100644
--- a/services/video_capture/test/mock_device_shared_access_unittest.cc
+++ b/services/video_capture/test/mock_device_shared_access_unittest.cc
@@ -16,8 +16,9 @@
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/video_capture/device_factory_media_to_mojo_adapter.h"
 #include "services/video_capture/device_media_to_mojo_adapter.h"
-#include "services/video_capture/public/cpp/mock_receiver.h"
+#include "services/video_capture/public/cpp/mock_video_frame_handler.h"
 #include "services/video_capture/public/mojom/video_capture_service.mojom.h"
+#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
 #include "services/video_capture/public/mojom/video_source.mojom.h"
 #include "services/video_capture/video_source_provider_impl.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -33,8 +34,10 @@
 class MockDeviceSharedAccessTest : public ::testing::Test {
  public:
   MockDeviceSharedAccessTest()
-      : mock_receiver_1_(receiver_1_.InitWithNewPipeAndPassReceiver()),
-        mock_receiver_2_(receiver_2_.InitWithNewPipeAndPassReceiver()),
+      : mock_video_frame_handler_1_(
+            video_frame_handler_1_.InitWithNewPipeAndPassReceiver()),
+        mock_video_frame_handler_2_(
+            video_frame_handler_2_.InitWithNewPipeAndPassReceiver()),
         next_arbitrary_frame_feedback_id_(123) {}
   ~MockDeviceSharedAccessTest() override {}
 
@@ -84,7 +87,7 @@
   void LetClient1ConnectWithRequestableSettingsAndExpectToGetThem() {
     base::RunLoop run_loop;
     source_->CreatePushSubscription(
-        std::move(receiver_1_), requestable_settings_,
+        std::move(video_frame_handler_1_), requestable_settings_,
         false /*force_reopen_with_new_settings*/,
         subscription_1_.BindNewPipeAndPassReceiver(),
         base::BindOnce(
@@ -114,7 +117,7 @@
       mojom::CreatePushSubscriptionResultCode expected_result_code) {
     base::RunLoop run_loop;
     source_->CreatePushSubscription(
-        std::move(receiver_2_), requestable_settings_,
+        std::move(video_frame_handler_2_), requestable_settings_,
         force_reopen_with_new_settings,
         subscription_2_.BindNewPipeAndPassReceiver(),
         base::BindOnce(
@@ -151,7 +154,7 @@
     base::RunLoop run_loop_1;
     base::RunLoop run_loop_2;
     source_->CreatePushSubscription(
-        std::move(receiver_1_), requestable_settings_,
+        std::move(video_frame_handler_1_), requestable_settings_,
         false /*force_reopen_with_new_settings*/,
         subscription_1_.BindNewPipeAndPassReceiver(),
         base::BindOnce(
@@ -174,7 +177,7 @@
     ASSERT_FALSE(requestable_settings_ == different_settings);
 
     source_->CreatePushSubscription(
-        std::move(receiver_2_), different_settings,
+        std::move(video_frame_handler_2_), different_settings,
         false /*force_reopen_with_new_settings*/,
         subscription_2_.BindNewPipeAndPassReceiver(),
         base::BindOnce(
@@ -200,19 +203,19 @@
         next_arbitrary_frame_feedback_id_++;
     const int32_t kArbitraryRotation = 0;
     base::RunLoop wait_loop_1;
-    EXPECT_CALL(mock_receiver_1_,
+    EXPECT_CALL(mock_video_frame_handler_1_,
                 DoOnFrameReadyInBuffer(_, kArbitraryFrameFeedbackId, _, _))
         .WillOnce(InvokeWithoutArgs([&wait_loop_1]() { wait_loop_1.Quit(); }));
     base::RunLoop wait_loop_2;
-    EXPECT_CALL(mock_receiver_2_,
+    EXPECT_CALL(mock_video_frame_handler_2_,
                 DoOnFrameReadyInBuffer(_, kArbitraryFrameFeedbackId, _, _))
         .WillOnce(InvokeWithoutArgs([&wait_loop_2]() { wait_loop_2.Quit(); }));
     mock_device_.SendStubFrame(requestable_settings_.requested_format,
                                kArbitraryRotation, kArbitraryFrameFeedbackId);
     wait_loop_1.Run();
     wait_loop_2.Run();
-    Mock::VerifyAndClearExpectations(&mock_receiver_1_);
-    Mock::VerifyAndClearExpectations(&mock_receiver_2_);
+    Mock::VerifyAndClearExpectations(&mock_video_frame_handler_1_);
+    Mock::VerifyAndClearExpectations(&mock_video_frame_handler_2_);
   }
 
   void SendFrameAndExpectToArriveOnlyAtSubscriber1() {
@@ -221,15 +224,16 @@
     const int32_t kArbitraryRotation = 0;
 
     base::RunLoop wait_loop;
-    EXPECT_CALL(mock_receiver_1_,
+    EXPECT_CALL(mock_video_frame_handler_1_,
                 DoOnFrameReadyInBuffer(_, kArbitraryFrameFeedbackId, _, _))
         .WillOnce(InvokeWithoutArgs([&wait_loop]() { wait_loop.Quit(); }));
-    EXPECT_CALL(mock_receiver_2_, DoOnFrameReadyInBuffer(_, _, _, _)).Times(0);
+    EXPECT_CALL(mock_video_frame_handler_2_, DoOnFrameReadyInBuffer(_, _, _, _))
+        .Times(0);
     mock_device_.SendStubFrame(requestable_settings_.requested_format,
                                kArbitraryRotation, kArbitraryFrameFeedbackId);
     wait_loop.Run();
-    Mock::VerifyAndClearExpectations(&mock_receiver_1_);
-    Mock::VerifyAndClearExpectations(&mock_receiver_2_);
+    Mock::VerifyAndClearExpectations(&mock_video_frame_handler_1_);
+    Mock::VerifyAndClearExpectations(&mock_video_frame_handler_2_);
   }
 
   void SendFrameAndExpectToArriveOnlyAtSubscriber2() {
@@ -238,15 +242,16 @@
     const int32_t kArbitraryRotation = 0;
 
     base::RunLoop wait_loop;
-    EXPECT_CALL(mock_receiver_1_, DoOnFrameReadyInBuffer(_, _, _, _)).Times(0);
-    EXPECT_CALL(mock_receiver_2_,
+    EXPECT_CALL(mock_video_frame_handler_1_, DoOnFrameReadyInBuffer(_, _, _, _))
+        .Times(0);
+    EXPECT_CALL(mock_video_frame_handler_2_,
                 DoOnFrameReadyInBuffer(_, kArbitraryFrameFeedbackId, _, _))
         .WillOnce(InvokeWithoutArgs([&wait_loop]() { wait_loop.Quit(); }));
     mock_device_.SendStubFrame(requestable_settings_.requested_format,
                                kArbitraryRotation, kArbitraryFrameFeedbackId);
     wait_loop.Run();
-    Mock::VerifyAndClearExpectations(&mock_receiver_1_);
-    Mock::VerifyAndClearExpectations(&mock_receiver_2_);
+    Mock::VerifyAndClearExpectations(&mock_video_frame_handler_1_);
+    Mock::VerifyAndClearExpectations(&mock_video_frame_handler_2_);
   }
 
  protected:
@@ -259,11 +264,11 @@
   media::VideoCaptureParams requestable_settings_;
 
   mojo::Remote<mojom::PushVideoStreamSubscription> subscription_1_;
-  mojo::PendingRemote<mojom::Receiver> receiver_1_;
-  MockReceiver mock_receiver_1_;
+  mojo::PendingRemote<mojom::VideoFrameHandler> video_frame_handler_1_;
+  MockVideoFrameHandler mock_video_frame_handler_1_;
   mojo::Remote<mojom::PushVideoStreamSubscription> subscription_2_;
-  mojo::PendingRemote<mojom::Receiver> receiver_2_;
-  MockReceiver mock_receiver_2_;
+  mojo::PendingRemote<mojom::VideoFrameHandler> video_frame_handler_2_;
+  MockVideoFrameHandler mock_video_frame_handler_2_;
 
   int32_t next_arbitrary_frame_feedback_id_;
 
@@ -318,12 +323,12 @@
 TEST_F(MockVideoCaptureDeviceSharedAccessTest,
        InternalDeviceRestartIsTransparentToExistingSubscribers) {
   LetClient1ConnectWithRequestableSettingsAndExpectToGetThem();
-  EXPECT_CALL(mock_receiver_1_, DoOnNewBuffer(_, _)).Times(1);
-  EXPECT_CALL(mock_receiver_1_, OnStarted()).Times(1);
+  EXPECT_CALL(mock_video_frame_handler_1_, DoOnNewBuffer(_, _)).Times(1);
+  EXPECT_CALL(mock_video_frame_handler_1_, OnStarted()).Times(1);
   subscription_1_->Activate();
   mock_device_.SendOnStarted();
   SendFrameAndExpectToArriveOnlyAtSubscriber1();
-  Mock::VerifyAndClearExpectations(&mock_receiver_1_);
+  Mock::VerifyAndClearExpectations(&mock_video_frame_handler_1_);
 
   auto previously_requested_settings = requestable_settings_;
   // Change something arbitrary
@@ -332,11 +337,11 @@
 
   {
     testing::InSequence s;
-    EXPECT_CALL(mock_receiver_1_, DoOnBufferRetired(_)).Times(1);
-    EXPECT_CALL(mock_receiver_1_, DoOnNewBuffer(_, _)).Times(1);
+    EXPECT_CALL(mock_video_frame_handler_1_, DoOnBufferRetired(_)).Times(1);
+    EXPECT_CALL(mock_video_frame_handler_1_, DoOnNewBuffer(_, _)).Times(1);
   }
-  EXPECT_CALL(mock_receiver_1_, OnStopped()).Times(0);
-  EXPECT_CALL(mock_receiver_1_, OnStarted()).Times(0);
+  EXPECT_CALL(mock_video_frame_handler_1_, OnStopped()).Times(0);
+  EXPECT_CALL(mock_video_frame_handler_1_, OnStarted()).Times(0);
 
   LetClient2ConnectWithRequestableSettings(
       true /*force_reopen_with_new_settings*/,
@@ -345,7 +350,7 @@
 
   mock_device_.SendOnStarted();
   SendFrameAndExpectToArriveAtBothSubscribers();
-  Mock::VerifyAndClearExpectations(&mock_receiver_1_);
+  Mock::VerifyAndClearExpectations(&mock_video_frame_handler_1_);
 }
 
 TEST_F(MockVideoCaptureDeviceSharedAccessTest,
@@ -383,9 +388,10 @@
   subscription_2_.reset();
   wait_loop.Run();
 
-  // DeviceMediaToMojoAdapter::Stop() issues a DeleteSoon for its |receiver_|
-  // on the current sequence. Wait for this before exiting the test in order to
-  // avoid leaked object failing ASAN tests. See also  https://crbug.com/961066.
+  // DeviceMediaToMojoAdapter::Stop() issues a DeleteSoon for its
+  // |video_frame_handler_| on the current sequence. Wait for this before
+  // exiting the test in order to avoid leaked object failing ASAN tests. See
+  // also  https://crbug.com/961066.
   base::RunLoop().RunUntilIdle();
 }
 
@@ -401,9 +407,10 @@
   source_.reset();
   wait_loop.Run();
 
-  // DeviceMediaToMojoAdapter::Stop() issues a DeleteSoon for its |receiver_|
-  // on the current sequence. Wait for this before exiting the test in order to
-  // avoid leaked object failing ASAN tests. See also  https://crbug.com/961066.
+  // DeviceMediaToMojoAdapter::Stop() issues a DeleteSoon for its
+  // |video_frame_handler_| on the current sequence. Wait for this before
+  // exiting the test in order to avoid leaked object failing ASAN tests. See
+  // also  https://crbug.com/961066.
   base::RunLoop().RunUntilIdle();
 }
 
@@ -454,7 +461,8 @@
         [](base::RunLoop* wait_loop) { wait_loop->Quit(); }, &wait_loop));
     wait_loop.Run();
   }
-  EXPECT_CALL(mock_receiver_1_, DoOnFrameReadyInBuffer(_, _, _, _)).Times(0);
+  EXPECT_CALL(mock_video_frame_handler_1_, DoOnFrameReadyInBuffer(_, _, _, _))
+      .Times(0);
 
   // Send a couple of frames. We want to send at least as many frames as
   // the maximum buffer count in the video frame pool to make sure that
@@ -470,7 +478,7 @@
     // We need to wait until the frame has arrived at BroadcastingReceiver
     base::RunLoop().RunUntilIdle();
   }
-  Mock::VerifyAndClearExpectations(&mock_receiver_1_);
+  Mock::VerifyAndClearExpectations(&mock_video_frame_handler_1_);
 
   subscription_1_->Resume();
   subscription_1_.FlushForTesting();
diff --git a/services/video_capture/test/mock_device_test.cc b/services/video_capture/test/mock_device_test.cc
index 4b9637df..65f4bf81 100644
--- a/services/video_capture/test/mock_device_test.cc
+++ b/services/video_capture/test/mock_device_test.cc
@@ -68,7 +68,7 @@
   requested_settings_.power_line_frequency =
       media::PowerLineFrequency::FREQUENCY_DEFAULT;
 
-  mock_receiver_ = std::make_unique<MockReceiver>(
+  mock_video_frame_handler_ = std::make_unique<MockVideoFrameHandler>(
       mock_subscriber_.InitWithNewPipeAndPassReceiver());
 }
 
diff --git a/services/video_capture/test/mock_device_test.h b/services/video_capture/test/mock_device_test.h
index 3370f60..0a08dc8 100644
--- a/services/video_capture/test/mock_device_test.h
+++ b/services/video_capture/test/mock_device_test.h
@@ -13,9 +13,10 @@
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/video_capture/device_factory_media_to_mojo_adapter.h"
-#include "services/video_capture/public/cpp/mock_receiver.h"
+#include "services/video_capture/public/cpp/mock_video_frame_handler.h"
 #include "services/video_capture/public/mojom/device.mojom.h"
 #include "services/video_capture/public/mojom/video_capture_service.mojom.h"
+#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace base {
@@ -44,9 +45,9 @@
       device_infos_receiver_;
 
   media::MockDevice mock_device_;
-  std::unique_ptr<MockReceiver> mock_receiver_;
+  std::unique_ptr<MockVideoFrameHandler> mock_video_frame_handler_;
   mojo::Remote<mojom::Device> device_remote_;
-  mojo::PendingRemote<mojom::Receiver> mock_subscriber_;
+  mojo::PendingRemote<mojom::VideoFrameHandler> mock_subscriber_;
   media::VideoCaptureParams requested_settings_;
 
  private:
diff --git a/services/video_capture/test/mock_device_unittest.cc b/services/video_capture/test/mock_device_unittest.cc
index 16ada4a..32e61f9 100644
--- a/services/video_capture/test/mock_device_unittest.cc
+++ b/services/video_capture/test/mock_device_unittest.cc
@@ -47,7 +47,7 @@
         .WillOnce(Invoke([&wait_loop]() { wait_loop.Quit(); }));
 
     device_remote_->Start(requested_settings_, std::move(mock_subscriber_));
-    mock_receiver_.reset();
+    mock_video_frame_handler_.reset();
 
     wait_loop.Run();
   }
diff --git a/services/video_capture/test/service_lifecycle_unittest.cc b/services/video_capture/test/service_lifecycle_unittest.cc
index 39ec466..89a7a78b 100644
--- a/services/video_capture/test/service_lifecycle_unittest.cc
+++ b/services/video_capture/test/service_lifecycle_unittest.cc
@@ -11,10 +11,11 @@
 #include "media/base/media_switches.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
-#include "services/video_capture/public/cpp/mock_receiver.h"
+#include "services/video_capture/public/cpp/mock_video_frame_handler.h"
 #include "services/video_capture/public/mojom/constants.mojom.h"
 #include "services/video_capture/public/mojom/device.mojom.h"
 #include "services/video_capture/public/mojom/video_capture_service.mojom.h"
+#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
 #include "services/video_capture/public/mojom/video_source_provider.mojom.h"
 #include "services/video_capture/video_capture_service_impl.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -172,12 +173,13 @@
       }));
   media::VideoCaptureParams requestable_settings;
   requestable_settings.requested_format = fake_device_info.supported_formats[0];
-  mojo::PendingRemote<mojom::Receiver> receiver_proxy;
-  MockReceiver mock_receiver(receiver_proxy.InitWithNewPipeAndPassReceiver());
-  fake_device->Start(requestable_settings, std::move(receiver_proxy));
+  mojo::PendingRemote<mojom::VideoFrameHandler> handler_remote;
+  MockVideoFrameHandler mock_video_frame_handler(
+      handler_remote.InitWithNewPipeAndPassReceiver());
+  fake_device->Start(requestable_settings, std::move(handler_remote));
   {
     base::RunLoop wait_loop;
-    EXPECT_CALL(mock_receiver, OnStarted()).WillOnce([&wait_loop]() {
+    EXPECT_CALL(mock_video_frame_handler, OnStarted()).WillOnce([&wait_loop]() {
       wait_loop.Quit();
     });
     wait_loop.Run();
diff --git a/services/video_capture/test/virtual_device_unittest.cc b/services/video_capture/test/virtual_device_unittest.cc
index f3fbb91..2f9ab38 100644
--- a/services/video_capture/test/virtual_device_unittest.cc
+++ b/services/video_capture/test/virtual_device_unittest.cc
@@ -10,7 +10,8 @@
 #include "media/capture/video/video_capture_device_info.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/video_capture/public/cpp/mock_producer.h"
-#include "services/video_capture/public/cpp/mock_receiver.h"
+#include "services/video_capture/public/cpp/mock_video_frame_handler.h"
+#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
 #include "services/video_capture/shared_memory_virtual_device_mojo_adapter.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -131,24 +132,25 @@
   // Release all buffers back to consumer, then back to the pool
   // after |Receiver::OnFrameReadyInBuffer| is invoked.
   base::RunLoop wait_loop;
-  mojo::PendingRemote<mojom::Receiver> receiver_proxy;
-  MockReceiver receiver(receiver_proxy.InitWithNewPipeAndPassReceiver());
-  EXPECT_CALL(receiver, OnStarted());
-  EXPECT_CALL(receiver, DoOnNewBuffer(_, _))
+  mojo::PendingRemote<mojom::VideoFrameHandler> handler_remote;
+  MockVideoFrameHandler video_frame_handler(
+      handler_remote.InitWithNewPipeAndPassReceiver());
+  EXPECT_CALL(video_frame_handler, OnStarted());
+  EXPECT_CALL(video_frame_handler, DoOnNewBuffer(_, _))
       .Times(
           SharedMemoryVirtualDeviceMojoAdapter::max_buffer_pool_buffer_count());
-  EXPECT_CALL(receiver, DoOnFrameReadyInBuffer(_, _, _, _))
+  EXPECT_CALL(video_frame_handler, DoOnFrameReadyInBuffer(_, _, _, _))
       .Times(
           SharedMemoryVirtualDeviceMojoAdapter::max_buffer_pool_buffer_count());
   device_adapter_->Start(media::VideoCaptureParams(),
-                         std::move(receiver_proxy));
+                         std::move(handler_remote));
   for (auto buffer_id : received_buffer_ids_) {
     media::mojom::VideoFrameInfoPtr info = media::mojom::VideoFrameInfo::New();
     info->metadata = base::Value(base::Value::Type::DICTIONARY);
     device_adapter_->OnFrameReadyInBuffer(buffer_id, std::move(info));
   }
   wait_loop.RunUntilIdle();
-  Mock::VerifyAndClearExpectations(&receiver);
+  Mock::VerifyAndClearExpectations(&video_frame_handler);
 
   // Verify that requesting a buffer doesn't create a new one, will reuse
   // the available buffer in the pool.
@@ -172,10 +174,10 @@
   base::RunLoop wait_for_stopped_loop;
   {
     testing::InSequence s;
-    EXPECT_CALL(receiver, DoOnBufferRetired(_))
+    EXPECT_CALL(video_frame_handler, DoOnBufferRetired(_))
         .Times(SharedMemoryVirtualDeviceMojoAdapter::
                    max_buffer_pool_buffer_count());
-    EXPECT_CALL(receiver, OnStopped())
+    EXPECT_CALL(video_frame_handler, OnStopped())
         .WillOnce(Invoke(
             [&wait_for_stopped_loop]() { wait_for_stopped_loop.Quit(); }));
   }
diff --git a/services/video_capture/texture_virtual_device_mojo_adapter.cc b/services/video_capture/texture_virtual_device_mojo_adapter.cc
index 57e7751..8248e4f7 100644
--- a/services/video_capture/texture_virtual_device_mojo_adapter.cc
+++ b/services/video_capture/texture_virtual_device_mojo_adapter.cc
@@ -36,12 +36,12 @@
   known_buffer_handles_.insert(
       std::make_pair(buffer_id, mailbox_handles->Clone()));
 
-  if (!receiver_.is_bound())
+  if (!video_frame_handler_.is_bound())
     return;
   media::mojom::VideoBufferHandlePtr buffer_handle =
       media::mojom::VideoBufferHandle::New();
   buffer_handle->set_mailbox_handles(std::move(mailbox_handles));
-  receiver_->OnNewBuffer(buffer_id, std::move(buffer_handle));
+  video_frame_handler_->OnNewBuffer(buffer_id, std::move(buffer_handle));
 }
 
 void TextureVirtualDeviceMojoAdapter::OnFrameReadyInBuffer(
@@ -49,37 +49,37 @@
     mojo::PendingRemote<mojom::ScopedAccessPermission> access_permission,
     media::mojom::VideoFrameInfoPtr frame_info) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!receiver_.is_bound())
+  if (!video_frame_handler_.is_bound())
     return;
-  receiver_->OnFrameReadyInBuffer(buffer_id, 0 /* frame_feedback_id */,
-                                  std::move(access_permission),
-                                  std::move(frame_info));
+  video_frame_handler_->OnFrameReadyInBuffer(
+      buffer_id, 0 /* frame_feedback_id */, std::move(access_permission),
+      std::move(frame_info));
 }
 
 void TextureVirtualDeviceMojoAdapter::OnBufferRetired(int buffer_id) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   known_buffer_handles_.erase(buffer_id);
-  if (!receiver_.is_bound())
+  if (!video_frame_handler_.is_bound())
     return;
-  receiver_->OnBufferRetired(buffer_id);
+  video_frame_handler_->OnBufferRetired(buffer_id);
 }
 
 void TextureVirtualDeviceMojoAdapter::Start(
     const media::VideoCaptureParams& requested_settings,
-    mojo::PendingRemote<mojom::Receiver> receiver) {
+    mojo::PendingRemote<mojom::VideoFrameHandler> handler) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  receiver_.Bind(std::move(receiver));
-  receiver_.set_disconnect_handler(base::BindOnce(
+  video_frame_handler_.Bind(std::move(handler));
+  video_frame_handler_.set_disconnect_handler(base::BindOnce(
       &TextureVirtualDeviceMojoAdapter::OnReceiverConnectionErrorOrClose,
       base::Unretained(this)));
-  receiver_->OnStarted();
+  video_frame_handler_->OnStarted();
 
   // Notify receiver of known buffer handles */
   for (auto& entry : known_buffer_handles_) {
     media::mojom::VideoBufferHandlePtr buffer_handle =
         media::mojom::VideoBufferHandle::New();
     buffer_handle->set_mailbox_handles(entry.second->Clone());
-    receiver_->OnNewBuffer(entry.first, std::move(buffer_handle));
+    video_frame_handler_->OnNewBuffer(entry.first, std::move(buffer_handle));
   }
 }
 
@@ -109,15 +109,15 @@
 
 void TextureVirtualDeviceMojoAdapter::Stop() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!receiver_.is_bound())
+  if (!video_frame_handler_.is_bound())
     return;
   // Unsubscribe from connection error callbacks.
-  receiver_.set_disconnect_handler(base::OnceClosure());
+  video_frame_handler_.set_disconnect_handler(base::OnceClosure());
   // Send out OnBufferRetired events and OnStopped.
   for (const auto& entry : known_buffer_handles_)
-    receiver_->OnBufferRetired(entry.first);
-  receiver_->OnStopped();
-  receiver_.reset();
+    video_frame_handler_->OnBufferRetired(entry.first);
+  video_frame_handler_->OnStopped();
+  video_frame_handler_.reset();
 }
 
 void TextureVirtualDeviceMojoAdapter::OnReceiverConnectionErrorOrClose() {
diff --git a/services/video_capture/texture_virtual_device_mojo_adapter.h b/services/video_capture/texture_virtual_device_mojo_adapter.h
index 4690fe8c..f7608012 100644
--- a/services/video_capture/texture_virtual_device_mojo_adapter.h
+++ b/services/video_capture/texture_virtual_device_mojo_adapter.h
@@ -12,7 +12,7 @@
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/video_capture/public/mojom/device.mojom.h"
 #include "services/video_capture/public/mojom/producer.mojom.h"
-#include "services/video_capture/public/mojom/receiver.mojom.h"
+#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
 #include "services/video_capture/public/mojom/virtual_device.mojom.h"
 
 namespace video_capture {
@@ -37,7 +37,7 @@
 
   // mojom::Device implementation.
   void Start(const media::VideoCaptureParams& requested_settings,
-             mojo::PendingRemote<mojom::Receiver> receiver) override;
+             mojo::PendingRemote<mojom::VideoFrameHandler> handler) override;
   void MaybeSuspend() override;
   void Resume() override;
   void GetPhotoState(GetPhotoStateCallback callback) override;
@@ -51,7 +51,7 @@
   void OnReceiverConnectionErrorOrClose();
 
   base::OnceClosure optional_receiver_disconnected_callback_;
-  mojo::Remote<mojom::Receiver> receiver_;
+  mojo::Remote<mojom::VideoFrameHandler> video_frame_handler_;
   std::unordered_map<int32_t, media::mojom::MailboxBufferHandleSetPtr>
       known_buffer_handles_;
   SEQUENCE_CHECKER(sequence_checker_);
diff --git a/services/video_capture/texture_virtual_device_mojo_adapter_unittest.cc b/services/video_capture/texture_virtual_device_mojo_adapter_unittest.cc
index e50fae3..75f28d3 100644
--- a/services/video_capture/texture_virtual_device_mojo_adapter_unittest.cc
+++ b/services/video_capture/texture_virtual_device_mojo_adapter_unittest.cc
@@ -7,7 +7,8 @@
 #include "base/run_loop.h"
 #include "base/test/task_environment.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
-#include "services/video_capture/public/cpp/mock_receiver.h"
+#include "services/video_capture/public/cpp/mock_video_frame_handler.h"
+#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -21,10 +22,10 @@
   TextureVirtualDeviceMojoAdapterTest() = default;
 
   void SetUp() override {
-    mock_receiver_1_ = std::make_unique<MockReceiver>(
-        receiver_1_.InitWithNewPipeAndPassReceiver());
-    mock_receiver_2_ = std::make_unique<MockReceiver>(
-        receiver_2_.InitWithNewPipeAndPassReceiver());
+    mock_video_frame_handler_1_ = std::make_unique<MockVideoFrameHandler>(
+        video_frame_handler_1_.InitWithNewPipeAndPassReceiver());
+    mock_video_frame_handler_2_ = std::make_unique<MockVideoFrameHandler>(
+        video_frame_handler_2_.InitWithNewPipeAndPassReceiver());
     adapter_ = std::make_unique<TextureVirtualDeviceMojoAdapter>();
   }
 
@@ -42,29 +43,31 @@
 
   void Receiver1Connects() {
     const media::VideoCaptureParams kArbitraryRequestedSettings;
-    adapter_->Start(kArbitraryRequestedSettings, std::move(receiver_1_));
+    adapter_->Start(kArbitraryRequestedSettings,
+                    std::move(video_frame_handler_1_));
   }
 
   void Receiver2Connects() {
     const media::VideoCaptureParams kArbitraryRequestedSettings;
-    adapter_->Start(kArbitraryRequestedSettings, std::move(receiver_2_));
+    adapter_->Start(kArbitraryRequestedSettings,
+                    std::move(video_frame_handler_2_));
   }
 
   void Receiver1Disconnects() {
     base::RunLoop wait_loop;
     adapter_->SetReceiverDisconnectedCallback(wait_loop.QuitClosure());
-    mock_receiver_1_.reset();
+    mock_video_frame_handler_1_.reset();
     wait_loop.Run();
   }
 
-  std::unique_ptr<MockReceiver> mock_receiver_1_;
-  std::unique_ptr<MockReceiver> mock_receiver_2_;
+  std::unique_ptr<MockVideoFrameHandler> mock_video_frame_handler_1_;
+  std::unique_ptr<MockVideoFrameHandler> mock_video_frame_handler_2_;
 
  private:
   base::test::TaskEnvironment task_environment_;
   std::unique_ptr<TextureVirtualDeviceMojoAdapter> adapter_;
-  mojo::PendingRemote<mojom::Receiver> receiver_1_;
-  mojo::PendingRemote<mojom::Receiver> receiver_2_;
+  mojo::PendingRemote<mojom::VideoFrameHandler> video_frame_handler_1_;
+  mojo::PendingRemote<mojom::VideoFrameHandler> video_frame_handler_2_;
 };
 
 // Tests that when buffer handles are shared by the producer before a receiver
@@ -79,13 +82,15 @@
 
   base::RunLoop wait_loop;
   int buffer_received_count = 0;
-  EXPECT_CALL(*mock_receiver_1_, DoOnNewBuffer(kArbitraryBufferId1, _))
+  EXPECT_CALL(*mock_video_frame_handler_1_,
+              DoOnNewBuffer(kArbitraryBufferId1, _))
       .WillOnce(InvokeWithoutArgs([&wait_loop, &buffer_received_count]() {
         buffer_received_count++;
         if (buffer_received_count == 2)
           wait_loop.Quit();
       }));
-  EXPECT_CALL(*mock_receiver_1_, DoOnNewBuffer(kArbitraryBufferId2, _))
+  EXPECT_CALL(*mock_video_frame_handler_1_,
+              DoOnNewBuffer(kArbitraryBufferId2, _))
       .WillOnce(InvokeWithoutArgs([&wait_loop, &buffer_received_count]() {
         buffer_received_count++;
         if (buffer_received_count == 2)
@@ -110,7 +115,8 @@
   ProducerRetiresBufferHandle(kArbitraryBufferId1);
 
   base::RunLoop wait_loop;
-  EXPECT_CALL(*mock_receiver_2_, DoOnNewBuffer(kArbitraryBufferId2, _))
+  EXPECT_CALL(*mock_video_frame_handler_2_,
+              DoOnNewBuffer(kArbitraryBufferId2, _))
       .WillOnce(InvokeWithoutArgs([&wait_loop]() { wait_loop.Quit(); }));
   Receiver2Connects();
   wait_loop.Run();
diff --git a/services/video_capture/video_source_impl.cc b/services/video_capture/video_source_impl.cc
index 21863fd7..a19d6ed4 100644
--- a/services/video_capture/video_source_impl.cc
+++ b/services/video_capture/video_source_impl.cc
@@ -36,7 +36,7 @@
 }
 
 void VideoSourceImpl::CreatePushSubscription(
-    mojo::PendingRemote<mojom::Receiver> subscriber,
+    mojo::PendingRemote<mojom::VideoFrameHandler> subscriber,
     const media::VideoCaptureParams& requested_settings,
     bool force_reopen_with_new_settings,
     mojo::PendingReceiver<mojom::PushVideoStreamSubscription>
@@ -105,9 +105,10 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   switch (result_code) {
     case mojom::DeviceAccessResultCode::SUCCESS: {
-      broadcaster_receiver_.reset();
-      device_->Start(device_start_settings_,
-                     broadcaster_receiver_.BindNewPipeAndPassRemote());
+      broadcaster_video_frame_handler_.reset();
+      device_->Start(
+          device_start_settings_,
+          broadcaster_video_frame_handler_.BindNewPipeAndPassRemote());
       device_status_ = DeviceStatus::kStarted;
       if (push_subscriptions_.empty()) {
         StopDeviceAsynchronously();
diff --git a/services/video_capture/video_source_impl.h b/services/video_capture/video_source_impl.h
index 7fd377d..7a8379be 100644
--- a/services/video_capture/video_source_impl.h
+++ b/services/video_capture/video_source_impl.h
@@ -18,6 +18,7 @@
 #include "services/video_capture/broadcasting_receiver.h"
 #include "services/video_capture/device_factory_media_to_mojo_adapter.h"
 #include "services/video_capture/public/mojom/device.mojom.h"
+#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
 #include "services/video_capture/public/mojom/video_source.mojom.h"
 #include "services/video_capture/public/mojom/video_source_provider.mojom.h"
 
@@ -36,7 +37,7 @@
 
   // mojom::VideoSource implementation.
   void CreatePushSubscription(
-      mojo::PendingRemote<mojom::Receiver> subscriber,
+      mojo::PendingRemote<mojom::VideoFrameHandler> subscriber,
       const media::VideoCaptureParams& requested_settings,
       bool force_reopen_with_new_settings,
       mojo::PendingReceiver<mojom::PushVideoStreamSubscription> subscription,
@@ -70,7 +71,8 @@
            std::unique_ptr<PushVideoStreamSubscriptionImpl>>
       push_subscriptions_;
   BroadcastingReceiver broadcaster_;
-  mojo::Receiver<mojom::Receiver> broadcaster_receiver_{&broadcaster_};
+  mojo::Receiver<mojom::VideoFrameHandler> broadcaster_video_frame_handler_{
+      &broadcaster_};
   DeviceStatus device_status_;
   mojo::Remote<mojom::Device> device_;
   media::VideoCaptureParams device_start_settings_;
diff --git a/testing/scripts/representative_perf_test_data/representatives_frame_times_upper_limit.json b/testing/scripts/representative_perf_test_data/representatives_frame_times_upper_limit.json
index 92920d4..f8a9720 100644
--- a/testing/scripts/representative_perf_test_data/representatives_frame_times_upper_limit.json
+++ b/testing/scripts/representative_perf_test_data/representatives_frame_times_upper_limit.json
@@ -16,7 +16,7 @@
       "avg": 17.918,
       "ci_095": 3.238
     },
-    "runway": {
+    "runway_2019": {
       "avg": 28.237,
       "ci_095": 28.279
     },
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 969b9c1..f8284f0 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -5898,6 +5898,26 @@
             ]
         }
     ],
+    "SplitCacheByNetworkIsolationKey": [
+        {
+            "platforms": [
+                "android",
+                "android_webview",
+                "chromeos",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "SplitCacheByNetworkIsolationKey"
+                    ]
+                }
+            ]
+        }
+    ],
     "SqlSkipPreload": [
         {
             "platforms": [
@@ -6620,6 +6640,27 @@
             ]
         }
     ],
+    "UmaAndUkmDemographics": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "linux",
+                "mac",
+                "windows",
+                "ios"
+            ],
+            "experiments": [
+                {
+                    "name": "UKM_Enabled",
+                    "enable_features": [
+                        "DemographicMetricsReporting",
+                        "UkmReportNoisedUserBirthYearAndGender"
+                    ]
+                }
+            ]
+        }
+    ],
     "UnifiedAutoplay": [
         {
             "platforms": [
diff --git a/third_party/blink/public/blink_resources.grd b/third_party/blink/public/blink_resources.grd
index c503bc46..d2c2a80 100644
--- a/third_party/blink/public/blink_resources.grd
+++ b/third_party/blink/public/blink_resources.grd
@@ -25,6 +25,7 @@
       <include name="IDR_UASTYLE_THEME_FORCED_COLORS_CSS" file="../renderer/core/html/resources/forced_colors.css" type="BINDATA" compress="gzip"/>
       <include name="IDR_UASTYLE_SVG_CSS" file="../renderer/core/css/svg.css" type="BINDATA" compress="gzip"/>
       <include name="IDR_UASTYLE_MATHML_CSS" file="../renderer/core/css/mathml.css" type="BINDATA" compress="gzip"/>
+      <include name="IDR_UASTYLE_MATHML_FALLBACK_CSS" file="../renderer/core/css/mathml-fallback.css" type="BINDATA" compress="gzip"/>
       <include name="IDR_UASTYLE_FULLSCREEN_CSS" file="../renderer/core/css/fullscreen.css" type="BINDATA" compress="gzip"/>
       <include name="IDR_UASTYLE_XHTMLMP_CSS" file="../renderer/core/css/xhtmlmp.css" type="BINDATA" compress="gzip"/>
       <include name="IDR_UASTYLE_VIEWPORT_ANDROID_CSS" file="../renderer/core/css/viewportAndroid.css" type="BINDATA" compress="gzip"/>
diff --git a/third_party/blink/public/mojom/frame/frame.mojom b/third_party/blink/public/mojom/frame/frame.mojom
index da5d918d..a125444 100644
--- a/third_party/blink/public/mojom/frame/frame.mojom
+++ b/third_party/blink/public/mojom/frame/frame.mojom
@@ -6,6 +6,7 @@
 
 import "cc/mojom/touch_action.mojom";
 import "mojo/public/mojom/base/string16.mojom";
+import "skia/public/mojom/skcolor.mojom";
 import "third_party/blink/public/mojom/devtools/console_message.mojom";
 import "third_party/blink/public/mojom/frame/fullscreen.mojom";
 import "third_party/blink/public/mojom/frame/lifecycle.mojom";
@@ -74,6 +75,12 @@
   // of the viewport, or the need for a layout object changes, e.g. if the
   // frame owner assigns a display: none style.
   VisibilityChanged(blink.mojom.FrameVisibility visibility);
+
+  // Notifies the browser that the associated frame has changed theme color.
+  // This will only be called on main-frames only. |theme_color| is optional
+  // and indicates if a theme color has been specified or not, e.g. removal
+  // of a theme color meta tag  will generate a null value.
+  DidChangeThemeColor(skia.mojom.SkColor? theme_color);
 };
 
 // Implemented in Blink, this interface defines frame-specific methods that will
diff --git a/third_party/blink/public/web/web_local_frame_client.h b/third_party/blink/public/web/web_local_frame_client.h
index 6794708d..e72f8ba 100644
--- a/third_party/blink/public/web/web_local_frame_client.h
+++ b/third_party/blink/public/web/web_local_frame_client.h
@@ -471,9 +471,6 @@
   // WARNING: This method may be called very frequently.
   virtual void DidUpdateCurrentHistoryItem() {}
 
-  // The frame's theme color has changed.
-  virtual void DidChangeThemeColor() {}
-
   // Called to report resource timing information for this frame to the parent.
   // Only used when the parent frame is remote.
   virtual void ForwardResourceTimingToParent(const WebResourceTimingInfo&) {}
diff --git a/third_party/blink/renderer/core/DEPS b/third_party/blink/renderer/core/DEPS
index 1558f23..aa6d0214 100644
--- a/third_party/blink/renderer/core/DEPS
+++ b/third_party/blink/renderer/core/DEPS
@@ -66,7 +66,7 @@
     "+services/network/public/cpp/shared_url_loader_factory.h",
     "+services/resource_coordinator/public/mojom/coordination_unit.mojom-blink.h",
     "+services/service_manager/public",
-    "+skia/public/mojom/bitmap_skbitmap_mojom_traits.h",
+    "+skia/public/mojom",
     "+skia/ext/image_operations.h",
     "+skia/ext/skia_utils_mac.h",
     "+third_party/blink/public/common",
diff --git a/third_party/blink/renderer/core/css/css_default_style_sheets.cc b/third_party/blink/renderer/core/css/css_default_style_sheets.cc
index 8eb2d6c..49217ef 100644
--- a/third_party/blink/renderer/core/css/css_default_style_sheets.cc
+++ b/third_party/blink/renderer/core/css/css_default_style_sheets.cc
@@ -215,8 +215,10 @@
   // FIXME: We should assert that the sheet only styles MathML elements.
   if (element.namespaceURI() == mathml_names::kNamespaceURI &&
       !mathml_style_sheet_) {
-    mathml_style_sheet_ =
-        ParseUASheet(UncompressResourceAsASCIIString(IDR_UASTYLE_MATHML_CSS));
+    mathml_style_sheet_ = ParseUASheet(
+        RuntimeEnabledFeatures::MathMLCoreEnabled()
+            ? UncompressResourceAsASCIIString(IDR_UASTYLE_MATHML_CSS)
+            : UncompressResourceAsASCIIString(IDR_UASTYLE_MATHML_FALLBACK_CSS));
     default_style_->AddRulesFromSheet(MathmlStyleSheet(), ScreenEval());
     default_print_style_->AddRulesFromSheet(MathmlStyleSheet(), PrintEval());
     changed_default_style = true;
diff --git a/third_party/blink/renderer/core/css/mathml-fallback.css b/third_party/blink/renderer/core/css/mathml-fallback.css
new file mode 100644
index 0000000..a9ea84ca
--- /dev/null
+++ b/third_party/blink/renderer/core/css/mathml-fallback.css
@@ -0,0 +1,49 @@
+/*
+ * The default style sheet used to render MathML (MathMLCore disabled).
+ *
+ * Copyright (C) 2014 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+@namespace "http://www.w3.org/1998/Math/MathML";
+
+/* By default, we only display the MathML formulas without any formatting other than the one specified by the display attribute. */
+math {
+    display: inline;
+}
+
+math[display="block"] {
+    display: block;
+    text-align: center;
+}
+
+/* We hide the PresentationExpression constructions that are children of a <semantics> element.
+   http://www.w3.org/TR/MathML/appendixa.html#parsing_PresentationExpression */
+semantics > mi, semantics > mn, semantics > mo, semantics > mtext, semantics > mspace, semantics > ms, semantics > maligngroup, semantics > malignmark, semantics > mrow, semantics > mfrac, semantics > msqrt, semantics > mroot, semantics > mstyle, semantics > merror, semantics > mpadded, semantics > mphantom, semantics > mfenced, semantics > menclose, semantics > msub, semantics > msup, semantics > msubsup, semantics > munder, semantics > mover, semantics > munderover, semantics > mmultiscripts, semantics > mtable, semantics > mstack, semantics > mlongdiv, semantics > maction {
+    display: none;
+}
+
+/* However, we display all the annotations. */
+annotation, annotation-xml {
+    display: inline-block;
+}
diff --git a/third_party/blink/renderer/core/css/mathml.css b/third_party/blink/renderer/core/css/mathml.css
index cef10e3f..1260f32 100644
--- a/third_party/blink/renderer/core/css/mathml.css
+++ b/third_party/blink/renderer/core/css/mathml.css
@@ -1,5 +1,5 @@
 /*
- * The default style sheet used to render MathML.
+ * The default style sheet used to render MathML (MathMLCore enabled).
  *
  * Copyright (C) 2014 Google Inc. All rights reserved.
  *
diff --git a/third_party/blink/renderer/core/exported/local_frame_client_impl.cc b/third_party/blink/renderer/core/exported/local_frame_client_impl.cc
index 41ef375..8c4c45d 100644
--- a/third_party/blink/renderer/core/exported/local_frame_client_impl.cc
+++ b/third_party/blink/renderer/core/exported/local_frame_client_impl.cc
@@ -462,11 +462,6 @@
   web_frame_->DidFinish();
 }
 
-void LocalFrameClientImpl::DispatchDidChangeThemeColor() {
-  if (web_frame_->Client())
-    web_frame_->Client()->DidChangeThemeColor();
-}
-
 void LocalFrameClientImpl::BeginNavigation(
     const ResourceRequest& request,
     network::mojom::RequestContextFrameType frame_type,
diff --git a/third_party/blink/renderer/core/exported/local_frame_client_impl.h b/third_party/blink/renderer/core/exported/local_frame_client_impl.h
index 8ba32f2..77c62f9f 100644
--- a/third_party/blink/renderer/core/exported/local_frame_client_impl.h
+++ b/third_party/blink/renderer/core/exported/local_frame_client_impl.h
@@ -112,7 +112,6 @@
   void DispatchDidFinishDocumentLoad() override;
   void DispatchDidFinishLoad() override;
 
-  void DispatchDidChangeThemeColor() override;
   void BeginNavigation(
       const ResourceRequest&,
       network::mojom::RequestContextFrameType,
diff --git a/third_party/blink/renderer/core/exported/web_frame_test.cc b/third_party/blink/renderer/core/exported/web_frame_test.cc
index 76ea7a0..71b65dc 100644
--- a/third_party/blink/renderer/core/exported/web_frame_test.cc
+++ b/third_party/blink/renderer/core/exported/web_frame_test.cc
@@ -42,6 +42,7 @@
 #include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
+#include "skia/public/mojom/skcolor.mojom-blink.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/context_menu_data/edit_flags.h"
@@ -8750,64 +8751,73 @@
   frame->PrintEnd();
 }
 
-class ThemeColorTestWebFrameClient
-    : public frame_test_helpers::TestWebFrameClient {
+class ThemeColorTestLocalFrameHost : public FakeLocalFrameHost {
  public:
-  ThemeColorTestWebFrameClient() : did_notify_(false) {}
-  ~ThemeColorTestWebFrameClient() override = default;
+  ThemeColorTestLocalFrameHost() = default;
+  ~ThemeColorTestLocalFrameHost() override = default;
 
   void Reset() { did_notify_ = false; }
 
   bool DidNotify() const { return did_notify_; }
 
  private:
-  // frame_test_helpers::TestWebFrameClient:
-  void DidChangeThemeColor() override { did_notify_ = true; }
+  // FakeLocalFrameHost:
+  void DidChangeThemeColor(
+      const base::Optional<::SkColor>& theme_color) override {
+    did_notify_ = true;
+  }
 
-  bool did_notify_;
+  bool did_notify_ = false;
 };
 
 TEST_F(WebFrameTest, ThemeColor) {
   RegisterMockedHttpURLLoad("theme_color_test.html");
-  ThemeColorTestWebFrameClient client;
+  ThemeColorTestLocalFrameHost host;
+  frame_test_helpers::TestWebFrameClient client;
+  host.Init(client.GetRemoteNavigationAssociatedInterfaces());
   frame_test_helpers::WebViewHelper web_view_helper;
   web_view_helper.InitializeAndLoad(base_url_ + "theme_color_test.html",
                                     &client);
-  EXPECT_TRUE(client.DidNotify());
+  EXPECT_TRUE(host.DidNotify());
   WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
   EXPECT_EQ(Color(0, 0, 255), frame->GetDocument().ThemeColor());
   // Change color by rgb.
-  client.Reset();
+  host.Reset();
   frame->ExecuteScript(
       WebScriptSource("document.getElementById('tc1').setAttribute('content', "
                       "'rgb(0, 0, 0)');"));
-  EXPECT_TRUE(client.DidNotify());
+  RunPendingTasks();
+  EXPECT_TRUE(host.DidNotify());
   EXPECT_EQ(Color::kBlack, frame->GetDocument().ThemeColor());
   // Change color by hsl.
-  client.Reset();
+  host.Reset();
   frame->ExecuteScript(
       WebScriptSource("document.getElementById('tc1').setAttribute('content', "
                       "'hsl(240,100%, 50%)');"));
-  EXPECT_TRUE(client.DidNotify());
+  RunPendingTasks();
+  EXPECT_TRUE(host.DidNotify());
   EXPECT_EQ(Color(0, 0, 255), frame->GetDocument().ThemeColor());
   // Change of second theme-color meta tag will not change frame's theme
   // color.
-  client.Reset();
+  host.Reset();
   frame->ExecuteScript(WebScriptSource(
       "document.getElementById('tc2').setAttribute('content', '#00FF00');"));
-  EXPECT_TRUE(client.DidNotify());
+  RunPendingTasks();
+  EXPECT_TRUE(host.DidNotify());
   EXPECT_EQ(Color(0, 0, 255), frame->GetDocument().ThemeColor());
   // Remove the first theme-color meta tag to apply the second.
-  client.Reset();
+  host.Reset();
   frame->ExecuteScript(
       WebScriptSource("document.getElementById('tc1').remove();"));
-  EXPECT_TRUE(client.DidNotify());
+  RunPendingTasks();
+  EXPECT_TRUE(host.DidNotify());
   EXPECT_EQ(Color(0, 255, 0), frame->GetDocument().ThemeColor());
   // Remove the name attribute of the remaining meta.
-  client.Reset();
+  host.Reset();
   frame->ExecuteScript(WebScriptSource(
       "document.getElementById('tc2').removeAttribute('name');"));
-  EXPECT_TRUE(client.DidNotify());
+  RunPendingTasks();
+  EXPECT_TRUE(host.DidNotify());
   EXPECT_EQ(base::nullopt, frame->GetDocument().ThemeColor());
 }
 
diff --git a/third_party/blink/renderer/core/frame/hosts_using_features.cc b/third_party/blink/renderer/core/frame/hosts_using_features.cc
index 81c07e5..cc91ef2f 100644
--- a/third_party/blink/renderer/core/frame/hosts_using_features.cc
+++ b/third_party/blink/renderer/core/frame/hosts_using_features.cc
@@ -129,12 +129,6 @@
 }
 
 void HostsUsingFeatures::Value::RecordHostToRappor(const String& host) {
-  if (Get(Feature::kDeviceMotionInsecureHost))
-    Platform::Current()->RecordRappor(
-        "PowerfulFeatureUse.Host.DeviceMotion.Insecure", host);
-  if (Get(Feature::kDeviceOrientationInsecureHost))
-    Platform::Current()->RecordRappor(
-        "PowerfulFeatureUse.Host.DeviceOrientation.Insecure", host);
   if (Get(Feature::kFullscreenInsecureHost))
     Platform::Current()->RecordRappor(
         "PowerfulFeatureUse.Host.Fullscreen.Insecure", host);
diff --git a/third_party/blink/renderer/core/frame/hosts_using_features.h b/third_party/blink/renderer/core/frame/hosts_using_features.h
index f773348f..ce681b51 100644
--- a/third_party/blink/renderer/core/frame/hosts_using_features.h
+++ b/third_party/blink/renderer/core/frame/hosts_using_features.h
@@ -30,8 +30,8 @@
     kElementCreateShadowRoot_Unused,
     kDocumentRegisterElement_Unused,
     kEventPath_Unused,
-    kDeviceMotionInsecureHost,
-    kDeviceOrientationInsecureHost,
+    kDeviceMotionInsecureHost_Unused,
+    kDeviceOrientationInsecureHost_Unused,
     kFullscreenInsecureHost,
     kGeolocationInsecureHost,
     kGetUserMediaInsecureHost,
diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc
index 3640a0c..ab831fd 100644
--- a/third_party/blink/renderer/core/frame/local_frame.cc
+++ b/third_party/blink/renderer/core/frame/local_frame.cc
@@ -36,6 +36,7 @@
 
 #include "services/network/public/cpp/features.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
+#include "skia/public/mojom/skcolor.mojom-blink.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 #include "third_party/blink/public/common/browser_interface_broker_proxy.h"
 #include "third_party/blink/public/common/frame/blocked_navigation_types.h"
@@ -562,6 +563,18 @@
                                           owner_element);
 }
 
+void LocalFrame::DidChangeThemeColor() {
+  if (Tree().Parent())
+    return;
+
+  base::Optional<Color> color = GetDocument()->ThemeColor();
+  base::Optional<SkColor> sk_color;
+  if (color)
+    sk_color = color->Rgb();
+
+  GetLocalFrameHostRemote().DidChangeThemeColor(sk_color);
+}
+
 LocalFrame& LocalFrame::LocalFrameRoot() const {
   const LocalFrame* cur_frame = this;
   while (cur_frame && IsA<LocalFrame>(cur_frame->Tree().Parent()))
diff --git a/third_party/blink/renderer/core/frame/local_frame.h b/third_party/blink/renderer/core/frame/local_frame.h
index 75cb56b5..02a1b084 100644
--- a/third_party/blink/renderer/core/frame/local_frame.h
+++ b/third_party/blink/renderer/core/frame/local_frame.h
@@ -154,6 +154,8 @@
                                          ScrollGranularity granularity,
                                          Frame* child) override;
 
+  void DidChangeThemeColor();
+
   void DetachChildren();
   // After Document is attached, resets state related to document, and sets
   // context to the current document.
diff --git a/third_party/blink/renderer/core/frame/local_frame_client.h b/third_party/blink/renderer/core/frame/local_frame_client.h
index 41e8871..2f0052e 100644
--- a/third_party/blink/renderer/core/frame/local_frame_client.h
+++ b/third_party/blink/renderer/core/frame/local_frame_client.h
@@ -146,7 +146,6 @@
                                    WebHistoryCommitType) = 0;
   virtual void DispatchDidFinishDocumentLoad() = 0;
   virtual void DispatchDidFinishLoad() = 0;
-  virtual void DispatchDidChangeThemeColor() = 0;
 
   virtual void BeginNavigation(
       const ResourceRequest&,
diff --git a/third_party/blink/renderer/core/html/html_meta_element.cc b/third_party/blink/renderer/core/html/html_meta_element.cc
index 91bc896..8ceb36a2 100644
--- a/third_party/blink/renderer/core/html/html_meta_element.cc
+++ b/third_party/blink/renderer/core/html/html_meta_element.cc
@@ -480,7 +480,7 @@
     return;
   if (EqualIgnoringASCIICase(name_value, "theme-color") &&
       GetDocument().GetFrame()) {
-    GetDocument().GetFrame()->Client()->DispatchDidChangeThemeColor();
+    GetDocument().GetFrame()->DidChangeThemeColor();
   } else if (EqualIgnoringASCIICase(name_value, "color-scheme")) {
     GetDocument().ColorSchemeMetaChanged();
   }
@@ -557,7 +557,7 @@
 
   if (EqualIgnoringASCIICase(name_value, "theme-color") &&
       GetDocument().GetFrame()) {
-    GetDocument().GetFrame()->Client()->DispatchDidChangeThemeColor();
+    GetDocument().GetFrame()->DidChangeThemeColor();
     return;
   }
   if (EqualIgnoringASCIICase(name_value, "color-scheme")) {
diff --git a/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.cc b/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.cc
index 4e8e678..f3107a3 100644
--- a/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.cc
+++ b/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.cc
@@ -76,9 +76,13 @@
 void LayoutNGListItem::OrdinalValueChanged() {
   if (marker_type_ == kOrdinalValue && is_marker_text_updated_) {
     is_marker_text_updated_ = false;
-    DCHECK(marker_);
-    marker_->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation(
-        layout_invalidation_reason::kListValueChange);
+
+    // |marker_| can be a nullptr, for example, in the case of :after list item
+    // elements.
+    if (marker_) {
+      marker_->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation(
+          layout_invalidation_reason::kListValueChange);
+    }
   }
 }
 
diff --git a/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.cc
index 7a1cc31..e568317 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.cc
@@ -223,6 +223,7 @@
 
   space_builder.SetAvailableSize(content_box_size_);
   space_builder.SetPercentageResolutionSize(child_percentage_size_);
+  space_builder.SetTextDirection(child_style.Direction());
   return space_builder.ToConstraintSpace();
 }
 
@@ -534,6 +535,8 @@
                                              /* is_new_fc */ true);
       SetOrthogonalFallbackInlineSizeIfNeeded(Style(), flex_item.ng_input_node,
                                               &space_builder);
+      space_builder.SetTextDirection(
+          flex_item.ng_input_node.Style().Direction());
 
       LogicalSize available_size;
       if (is_column_) {
@@ -622,6 +625,7 @@
       space_builder.SetIsFixedBlockSizeIndefinite(true);
     }
   }
+  space_builder.SetTextDirection(flex_item.ng_input_node.Style().Direction());
   space_builder.SetAvailableSize(available_size);
   space_builder.SetPercentageResolutionSize(child_percentage_size_);
   space_builder.SetIsFixedInlineSize(true);
diff --git a/third_party/blink/renderer/core/loader/empty_clients.h b/third_party/blink/renderer/core/loader/empty_clients.h
index 098703b..8c43163 100644
--- a/third_party/blink/renderer/core/loader/empty_clients.h
+++ b/third_party/blink/renderer/core/loader/empty_clients.h
@@ -285,7 +285,6 @@
                            WebHistoryCommitType) override {}
   void DispatchDidFinishDocumentLoad() override {}
   void DispatchDidFinishLoad() override {}
-  void DispatchDidChangeThemeColor() override {}
 
   void BeginNavigation(
       const ResourceRequest&,
diff --git a/third_party/blink/renderer/core/testing/fake_local_frame_host.cc b/third_party/blink/renderer/core/testing/fake_local_frame_host.cc
index e3af385..33f415a 100644
--- a/third_party/blink/renderer/core/testing/fake_local_frame_host.cc
+++ b/third_party/blink/renderer/core/testing/fake_local_frame_host.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/core/testing/fake_local_frame_host.h"
 
+#include "skia/public/mojom/skcolor.mojom-blink.h"
 #include "third_party/blink/public/mojom/frame/fullscreen.mojom-blink.h"
 
 namespace blink {
@@ -45,6 +46,9 @@
 void FakeLocalFrameHost::VisibilityChanged(
     mojom::blink::FrameVisibility visibility) {}
 
+void FakeLocalFrameHost::DidChangeThemeColor(
+    const base::Optional<::SkColor>& theme_color) {}
+
 void FakeLocalFrameHost::BindFrameHostReceiver(
     mojo::ScopedInterfaceEndpointHandle handle) {
   receiver_.Bind(mojo::PendingAssociatedReceiver<mojom::blink::LocalFrameHost>(
diff --git a/third_party/blink/renderer/core/testing/fake_local_frame_host.h b/third_party/blink/renderer/core/testing/fake_local_frame_host.h
index b803afa0..f4aa5ff 100644
--- a/third_party/blink/renderer/core/testing/fake_local_frame_host.h
+++ b/third_party/blink/renderer/core/testing/fake_local_frame_host.h
@@ -37,6 +37,8 @@
   void LifecycleStateChanged(mojom::blink::FrameLifecycleState state) override;
   void EvictFromBackForwardCache() override;
   void VisibilityChanged(mojom::blink::FrameVisibility visibility) override;
+  void DidChangeThemeColor(
+      const base::Optional<::SkColor>& theme_color) override;
 
  private:
   void BindFrameHostReceiver(mojo::ScopedInterfaceEndpointHandle handle);
diff --git a/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.idl b/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.idl
index e50f610..6555d4c 100644
--- a/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.idl
+++ b/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.idl
@@ -23,6 +23,8 @@
     void translate(unrestricted double x, unrestricted double y);
     void transform(unrestricted double a, unrestricted double b, unrestricted double c, unrestricted double d, unrestricted double e, unrestricted double f);
     void setTransform(unrestricted double a, unrestricted double b, unrestricted double c, unrestricted double d, unrestricted double e, unrestricted double f);
+    [RaisesException] void setTransform(optional DOMMatrix2DInit transform);
+    DOMMatrix getTransform();
     void resetTransform();
 
     // compositing
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler_test.cc b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler_test.cc
index 12e78284..de87c33 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler_test.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler_test.cc
@@ -942,7 +942,7 @@
       EXPECT_EQ(stats->Timestamp(), 2.0);
       std::set<webrtc::RTCStatsMemberInterface::Type> members;
       for (size_t i = 0; i < stats->MembersCount(); ++i) {
-        std::unique_ptr<blink::WebRTCStatsMember> member = stats->GetMember(i);
+        std::unique_ptr<RTCStatsMember> member = stats->GetMember(i);
         // TODO(hbos): A WebRTC-change is adding new members, this would cause
         // not all members to be defined. This if-statement saves Chromium from
         // crashing. As soon as the change has been rolled in, I will update
@@ -971,7 +971,7 @@
             EXPECT_EQ(member->ValueDouble(), 42.0);
             break;
           case webrtc::RTCStatsMemberInterface::kString:
-            EXPECT_EQ(member->ValueString(), blink::WebString::FromUTF8("42"));
+            EXPECT_EQ(member->ValueString(), "42");
             break;
           case webrtc::RTCStatsMemberInterface::kSequenceBool:
             ExpectSequenceEquals(member->ValueSequenceBool(), 1);
@@ -997,7 +997,7 @@
             break;
           case webrtc::RTCStatsMemberInterface::kSequenceString:
             ExpectSequenceEquals(member->ValueSequenceString(),
-                                 blink::WebString::FromUTF8("42"));
+                                 String::FromUTF8("42"));
             break;
           default:
             NOTREACHED();
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_stats_report.cc b/third_party/blink/renderer/modules/peerconnection/rtc_stats_report.cc
index e985bcd..3efe7eb 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_stats_report.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_stats_report.cc
@@ -31,7 +31,7 @@
   };
 
   for (size_t i = 0; i < stats->MembersCount(); ++i) {
-    std::unique_ptr<WebRTCStatsMember> member = stats->GetMember(i);
+    std::unique_ptr<RTCStatsMember> member = stats->GetMember(i);
     if (!member->IsDefined())
       continue;
     WebString name = member->GetName();
diff --git a/third_party/blink/renderer/modules/webgl/webgl_context_attribute_helpers.cc b/third_party/blink/renderer/modules/webgl/webgl_context_attribute_helpers.cc
index 810e91b..7902b3a0 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_context_attribute_helpers.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_context_attribute_helpers.cc
@@ -31,9 +31,9 @@
     Platform::ContextType context_type,
     bool support_own_offscreen_surface) {
   Platform::ContextAttributes result;
-  // Must keep the meaning of the "default" GPU choice in sync with the code
-  // below, in PowerPreferenceToGpuPreference.
-  result.prefer_low_power_gpu = attrs.power_preference == "low-power";
+  result.prefer_low_power_gpu =
+      (PowerPreferenceToGpuPreference(attrs.power_preference) ==
+       gl::GpuPreference::kLowPower);
   result.fail_if_major_performance_caveat =
       attrs.fail_if_major_performance_caveat;
   result.context_type = context_type;
@@ -49,8 +49,7 @@
 }
 
 gl::GpuPreference PowerPreferenceToGpuPreference(String power_preference) {
-  // Must keep the interpretation of the "default" GPU in sync with the code
-  // above which sets the prefer_low_power_gpu attribute.
+  // This code determines the handling of the "default" power preference.
   if (power_preference == "low-power")
     return gl::GpuPreference::kLowPower;
   return gl::GpuPreference::kHighPerformance;
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
index a3420be..84a58f78 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
@@ -841,6 +841,7 @@
     UpdateNumberOfUserAllocatedMultisampledRenderbuffers(int delta) {
   DCHECK(delta >= -1 && delta <= 1);
   number_of_user_allocated_multisampled_renderbuffers_ += delta;
+  DCHECK_GE(number_of_user_allocated_multisampled_renderbuffers_, 0);
 }
 
 namespace {
diff --git a/third_party/blink/renderer/modules/xr/xr.cc b/third_party/blink/renderer/modules/xr/xr.cc
index a29117d..261a0b9b 100644
--- a/third_party/blink/renderer/modules/xr/xr.cc
+++ b/third_party/blink/renderer/modules/xr/xr.cc
@@ -1001,15 +1001,8 @@
 
   // immersive sessions must supply display info.
   DCHECK(session_ptr->display_info);
-  // If the session supports environment integration, ensure the device does
-  // as well.
-  DCHECK(!environment_integration || session_ptr->display_info->capabilities
-                                         ->can_provide_environment_integration);
   DVLOG(2) << __func__
-           << ": environment_integration=" << environment_integration
-           << "can_provide_environment_integration="
-           << session_ptr->display_info->capabilities
-                  ->can_provide_environment_integration;
+           << ": environment_integration=" << environment_integration;
 
   // TODO(https://crbug.com/944936): The blend mode could be "additive".
   XRSession::EnvironmentBlendMode blend_mode = XRSession::kBlendModeOpaque;
diff --git a/third_party/blink/renderer/modules/xr/xr_frame_provider.cc b/third_party/blink/renderer/modules/xr/xr_frame_provider.cc
index a4b8313..38c6bc7 100644
--- a/third_party/blink/renderer/modules/xr/xr_frame_provider.cc
+++ b/third_party/blink/renderer/modules/xr/xr_frame_provider.cc
@@ -535,12 +535,11 @@
     // Image is written to shared buffer already. Just submit with a
     // placeholder.
     scoped_refptr<Image> image_ref = nullptr;
-    bool needs_copy = false;
     DVLOG(3) << __FUNCTION__ << ": FrameSubmit for SharedBuffer mode";
-    frame_transport_->FrameSubmit(
-        immersive_presentation_provider_.get(), webgl_context->ContextGL(),
-        webgl_context, std::move(image_ref), std::move(image_release_callback),
-        frame_id_, needs_copy);
+    frame_transport_->FrameSubmit(immersive_presentation_provider_.get(),
+                                  webgl_context->ContextGL(), webgl_context,
+                                  std::move(image_ref),
+                                  std::move(image_release_callback), frame_id_);
     return;
   }
 
@@ -557,14 +556,10 @@
     return;
   }
 
-  // TODO(bajones): Remove this when the Windows path has been updated to no
-  // longer require a texture copy.
-  bool needs_copy = immersive_session_->External();
-
-  frame_transport_->FrameSubmit(
-      immersive_presentation_provider_.get(), webgl_context->ContextGL(),
-      webgl_context, std::move(image_ref), std::move(image_release_callback),
-      frame_id_, needs_copy);
+  frame_transport_->FrameSubmit(immersive_presentation_provider_.get(),
+                                webgl_context->ContextGL(), webgl_context,
+                                std::move(image_ref),
+                                std::move(image_release_callback), frame_id_);
 
   // Reset our frame id, since anything we'd want to do (resizing/etc) can
   // no-longer happen to this frame.
diff --git a/third_party/blink/renderer/modules/xr/xr_session.cc b/third_party/blink/renderer/modules/xr/xr_session.cc
index 8ce7b82..9ad0039 100644
--- a/third_party/blink/renderer/modules/xr/xr_session.cc
+++ b/third_party/blink/renderer/modules/xr/xr_session.cc
@@ -1556,7 +1556,6 @@
 
   display_info_id_++;
   display_info_ = std::move(display_info);
-  is_external_ = display_info_->capabilities->has_external_display;
 }
 
 WTF::Vector<XRViewData>& XRSession::views() {
diff --git a/third_party/blink/renderer/modules/xr/xr_session.h b/third_party/blink/renderer/modules/xr/xr_session.h
index 41581e1..c880f85 100644
--- a/third_party/blink/renderer/modules/xr/xr_session.h
+++ b/third_party/blink/renderer/modules/xr/xr_session.h
@@ -233,7 +233,6 @@
       const device::mojom::blink::VREyeParametersPtr& right_eye);
   void UpdateStageParameters(
       const device::mojom::blink::VRStageParametersPtr& stage_parameters);
-  bool External() const { return is_external_; }
   // Incremented every time display_info_ is changed, so that other objects that
   // depend on it can know when they need to update.
   unsigned int DisplayInfoPtrId() const { return display_info_id_; }
@@ -386,7 +385,6 @@
   HeapHashSet<Member<ScriptPromiseResolver>> request_hit_test_source_promises_;
   HeapVector<Member<XRReferenceSpace>> reference_spaces_;
 
-  bool is_external_ = false;
   unsigned int display_info_id_ = 0;
   unsigned int stage_parameters_id_ = 0;
   device::mojom::blink::VRDisplayInfoPtr display_info_;
diff --git a/third_party/blink/renderer/platform/exported/web_rtc_stats.cc b/third_party/blink/renderer/platform/exported/web_rtc_stats.cc
index f871cdb..52175634 100644
--- a/third_party/blink/renderer/platform/exported/web_rtc_stats.cc
+++ b/third_party/blink/renderer/platform/exported/web_rtc_stats.cc
@@ -6,6 +6,4 @@
 
 namespace blink {
 
-WebRTCStatsMember::~WebRTCStatsMember() = default;
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/gpu/xr_frame_transport.cc b/third_party/blink/renderer/platform/graphics/gpu/xr_frame_transport.cc
index e599e2e..63b92488 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/xr_frame_transport.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/xr_frame_transport.cc
@@ -102,16 +102,13 @@
     DrawingBuffer::Client* drawing_buffer_client,
     scoped_refptr<Image> image_ref,
     std::unique_ptr<viz::SingleReleaseCallback> release_callback,
-    int16_t vr_frame_id,
-    bool needs_copy) {
+    int16_t vr_frame_id) {
   DCHECK(transport_options_);
 
   if (transport_options_->transport_method ==
       device::mojom::blink::XRPresentationTransportMethod::
           SUBMIT_AS_TEXTURE_HANDLE) {
 #if defined(OS_WIN)
-    // Currently, we assume that this transport needs a copy.
-    DCHECK(needs_copy);
     TRACE_EVENT0("gpu", "XRFrameTransport::CopyImage");
     // Update last_transfer_succeeded_ value. This should usually complete
     // without waiting.
@@ -152,10 +149,6 @@
   } else if (transport_options_->transport_method ==
              device::mojom::blink::XRPresentationTransportMethod::
                  SUBMIT_AS_MAILBOX_HOLDER) {
-    // Currently, this transport assumes we don't need to make a separate copy
-    // of the canvas content.
-    DCHECK(!needs_copy);
-
     // The AcceleratedStaticBitmapImage must be kept alive until the
     // mailbox is used via createAndConsumeTextureCHROMIUM, the mailbox
     // itself does not keep it alive. We must keep a reference to the
diff --git a/third_party/blink/renderer/platform/graphics/gpu/xr_frame_transport.h b/third_party/blink/renderer/platform/graphics/gpu/xr_frame_transport.h
index 4bd1027..f0fa052 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/xr_frame_transport.h
+++ b/third_party/blink/renderer/platform/graphics/gpu/xr_frame_transport.h
@@ -53,8 +53,7 @@
                    DrawingBuffer::Client*,
                    scoped_refptr<Image> image_ref,
                    std::unique_ptr<viz::SingleReleaseCallback>,
-                   int16_t vr_frame_id,
-                   bool needs_copy);
+                   int16_t vr_frame_id);
 
   void FrameSubmitMissing(device::mojom::blink::XRPresentationProvider*,
                           gpu::gles2::GLES2Interface*,
diff --git a/third_party/blink/renderer/platform/heap/thread_state.cc b/third_party/blink/renderer/platform/heap/thread_state.cc
index 3d7b47d8a..252a80da 100644
--- a/third_party/blink/renderer/platform/heap/thread_state.cc
+++ b/third_party/blink/renderer/platform/heap/thread_state.cc
@@ -1078,6 +1078,21 @@
     DCHECK(!static_persistents_.Contains(persistent_node));
 }
 
+void ThreadState::RegisterPreFinalizer(void* object,
+                                       PreFinalizerCallback callback) {
+#if DCHECK_IS_ON()
+  DCHECK(CheckThread());
+#endif
+  DCHECK(!SweepForbidden());
+
+  HeapObjectHeader* header = HeapObjectHeader::FromInnerAddress(object);
+  DCHECK(ordered_pre_finalizers_.end() ==
+         std::find(ordered_pre_finalizers_.begin(),
+                   ordered_pre_finalizers_.end(),
+                   PreFinalizer{header, object, callback}));
+  ordered_pre_finalizers_.push_back(PreFinalizer{header, object, callback});
+}
+
 void ThreadState::InvokePreFinalizers() {
   DCHECK(CheckThread());
   DCHECK(!SweepForbidden());
@@ -1085,23 +1100,22 @@
   ThreadHeapStatsCollector::Scope stats_scope(
       Heap().stats_collector(), ThreadHeapStatsCollector::kInvokePreFinalizers);
   SweepForbiddenScope sweep_forbidden(this);
-  // Pre finalizers are forbidden from allocating objects
+  // Pre finalizers are forbidden from allocating objects.
   NoAllocationScope no_allocation_scope(this);
 
   // Call the prefinalizers in the opposite order to their registration.
-  //
-  // LinkedHashSet does not support modification during iteration, so
-  // copy items first.
-  //
-  // The prefinalizer callback wrapper returns |true| when its associated
-  // object is unreachable garbage and the prefinalizer callback has run.
-  // The registered prefinalizer entry must then be removed and deleted.
   Deque<PreFinalizer> remaining_ordered_pre_finalizers;
   for (auto rit = ordered_pre_finalizers_.rbegin();
        rit != ordered_pre_finalizers_.rend(); ++rit) {
     const PreFinalizer& pre_finalizer = *rit;
-    if (!(pre_finalizer.second)(pre_finalizer.first))
+    // Check if pre-finalizer should be executed.
+    if (pre_finalizer.header->IsMarked()) {
+      // Re-queue for checking in next garbage collection.
       remaining_ordered_pre_finalizers.push_front(pre_finalizer);
+    } else {
+      // Execute pre-finalizer.
+      pre_finalizer.callback(pre_finalizer.object);
+    }
   }
 
   ordered_pre_finalizers_ = std::move(remaining_ordered_pre_finalizers);
diff --git a/third_party/blink/renderer/platform/heap/thread_state.h b/third_party/blink/renderer/platform/heap/thread_state.h
index b443861..88cbe4fb 100644
--- a/third_party/blink/renderer/platform/heap/thread_state.h
+++ b/third_party/blink/renderer/platform/heap/thread_state.h
@@ -62,6 +62,7 @@
 }  // namespace incremental_marking_test
 
 class CancelableTaskScheduler;
+class HeapObjectHeader;
 class MarkingVisitor;
 class PersistentNode;
 class PersistentRegion;
@@ -93,17 +94,13 @@
 //     Member<Bar> bar_;
 //   };
 #define USING_PRE_FINALIZER(Class, preFinalizer)                          \
- public:                                                                  \
-  static bool InvokePreFinalizer(void* object) {                          \
-    Class* self = reinterpret_cast<Class*>(object);                       \
-    if (ThreadHeap::IsHeapObjectAlive(self))                              \
-      return false;                                                       \
-    self->Class::preFinalizer();                                          \
-    return true;                                                          \
+ private:                                                                 \
+  static void PreFinalizerDispatch(void* object) {                        \
+    reinterpret_cast<Class*>(object)->Class::preFinalizer();              \
   }                                                                       \
                                                                           \
- private:                                                                 \
-  ThreadState::PrefinalizerRegistration<Class> prefinalizer_dummy_{this}; \
+  friend class ThreadState::PreFinalizerRegistration<Class>;              \
+  ThreadState::PreFinalizerRegistration<Class> prefinalizer_dummy_{this}; \
   using UsingPreFinalizerMacroNeedsTrailingSemiColon = char
 
 class PLATFORM_EXPORT BlinkGCObserver {
@@ -133,24 +130,16 @@
   // Register the pre-finalizer for the |self| object. The class T be using
   // USING_PRE_FINALIZER() macro.
   template <typename T>
-  class PrefinalizerRegistration final {
+  class PreFinalizerRegistration final {
     DISALLOW_NEW();
 
    public:
-    PrefinalizerRegistration(T* self) {
-      static_assert(sizeof(&T::InvokePreFinalizer) > 0,
+    PreFinalizerRegistration(T* self) {
+      static_assert(sizeof(&T::PreFinalizerDispatch) > 0,
                     "USING_PRE_FINALIZER(T) must be defined.");
       ThreadState* state =
           ThreadStateFor<ThreadingTrait<T>::kAffinity>::GetState();
-#if DCHECK_IS_ON()
-      DCHECK(state->CheckThread());
-#endif
-      DCHECK(!state->SweepForbidden());
-      DCHECK(std::find(state->ordered_pre_finalizers_.begin(),
-                       state->ordered_pre_finalizers_.end(),
-                       PreFinalizer(self, T::InvokePreFinalizer)) ==
-             state->ordered_pre_finalizers_.end());
-      state->ordered_pre_finalizers_.emplace_back(self, T::InvokePreFinalizer);
+      state->RegisterPreFinalizer(self, T::PreFinalizerDispatch);
     }
   };
 
@@ -385,6 +374,17 @@
  private:
   class IncrementalMarkingScheduler;
 
+  using PreFinalizerCallback = void (*)(void*);
+  struct PreFinalizer {
+    HeapObjectHeader* header;
+    void* object;
+    PreFinalizerCallback callback;
+
+    bool operator==(const PreFinalizer& other) const {
+      return object == other.object && callback == other.callback;
+    }
+  };
+
   // Duration of one incremental marking step. Should be short enough that it
   // doesn't cause jank even though it is scheduled as a normal task.
   static constexpr base::TimeDelta kDefaultIncrementalMarkingStepDuration =
@@ -509,6 +509,7 @@
 
   void SynchronizeAndFinishConcurrentSweeping();
 
+  void RegisterPreFinalizer(void*, PreFinalizerCallback);
   void InvokePreFinalizers();
 
   // Adds the given observer to the ThreadState's observer list. This doesn't
@@ -557,9 +558,6 @@
   BlinkGC::GCReason reason_for_scheduled_gc_ =
       BlinkGC::GCReason::kForcedGCForTesting;
 
-  using PreFinalizerCallback = bool (*)(void*);
-  using PreFinalizer = std::pair<void*, PreFinalizerCallback>;
-
   // Pre-finalizers are called in the reverse order in which they are
   // registered by the constructors (including constructors of Mixin objects)
   // for an object, by processing the ordered_pre_finalizers_ back-to-front.
@@ -607,7 +605,7 @@
   friend class IncrementalMarkingTestDriver;
   friend class HeapAllocator;
   template <typename T>
-  friend class PrefinalizerRegistration;
+  friend class PreFinalizerRegistration;
   friend class TestGCScope;
   friend class ThreadStateSchedulingTest;
   friend class UnifiedHeapController;
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_stats.cc b/third_party/blink/renderer/platform/peerconnection/rtc_stats.cc
index 9619bce..6093874 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_stats.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_stats.cc
@@ -138,7 +138,7 @@
 }
 
 std::unique_ptr<RTCStats> RTCStatsReportPlatform::GetStats(
-    blink::WebString id) const {
+    const String& id) const {
   const webrtc::RTCStats* stats = stats_report_->Get(id.Utf8());
   if (!stats || !IsWhitelistedStats(*stats))
     return std::unique_ptr<RTCStats>();
@@ -175,12 +175,12 @@
 
 RTCStats::~RTCStats() {}
 
-blink::WebString RTCStats::Id() const {
-  return blink::WebString::FromUTF8(stats_->id());
+String RTCStats::Id() const {
+  return String::FromUTF8(stats_->id());
 }
 
-blink::WebString RTCStats::GetType() const {
-  return blink::WebString::FromUTF8(stats_->type());
+String RTCStats::GetType() const {
+  return String::FromUTF8(stats_->type());
 }
 
 double RTCStats::Timestamp() const {
@@ -192,15 +192,9 @@
   return stats_members_.size();
 }
 
-std::unique_ptr<blink::WebRTCStatsMember> RTCStats::GetMember(size_t i) const {
+std::unique_ptr<RTCStatsMember> RTCStats::GetMember(size_t i) const {
   DCHECK_LT(i, stats_members_.size());
-  return CreateRTCStatsMember(stats_owner_, stats_members_[i]);
-}
-
-std::unique_ptr<blink::WebRTCStatsMember> CreateRTCStatsMember(
-    const scoped_refptr<const webrtc::RTCStatsReport>& stats_owner,
-    const webrtc::RTCStatsMemberInterface* member) {
-  return std::make_unique<RTCStatsMember>(std::move(stats_owner), member);
+  return std::make_unique<RTCStatsMember>(stats_owner_, stats_members_[i]);
 }
 
 RTCStatsMember::RTCStatsMember(
@@ -213,8 +207,8 @@
 
 RTCStatsMember::~RTCStatsMember() {}
 
-blink::WebString RTCStatsMember::GetName() const {
-  return blink::WebString::FromUTF8(member_->name());
+String RTCStatsMember::GetName() const {
+  return String::FromUTF8(member_->name());
 }
 
 webrtc::RTCStatsMemberInterface::Type RTCStatsMember::GetType() const {
@@ -255,9 +249,9 @@
   return *member_->cast_to<webrtc::RTCStatsMember<double>>();
 }
 
-blink::WebString RTCStatsMember::ValueString() const {
+String RTCStatsMember::ValueString() const {
   DCHECK(IsDefined());
-  return blink::WebString::FromUTF8(
+  return String::FromUTF8(
       *member_->cast_to<webrtc::RTCStatsMember<std::string>>());
 }
 
@@ -303,13 +297,13 @@
       *member_->cast_to<webrtc::RTCStatsMember<std::vector<double>>>());
 }
 
-blink::WebVector<blink::WebString> RTCStatsMember::ValueSequenceString() const {
+blink::WebVector<String> RTCStatsMember::ValueSequenceString() const {
   DCHECK(IsDefined());
   const std::vector<std::string>& sequence =
       *member_->cast_to<webrtc::RTCStatsMember<std::vector<std::string>>>();
-  blink::WebVector<blink::WebString> web_sequence(sequence.size());
+  blink::WebVector<String> web_sequence(sequence.size());
   for (size_t i = 0; i < sequence.size(); ++i)
-    web_sequence[i] = blink::WebString::FromUTF8(sequence[i]);
+    web_sequence[i] = String::FromUTF8(sequence[i]);
   return web_sequence;
 }
 
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_stats.h b/third_party/blink/renderer/platform/peerconnection/rtc_stats.h
index 9e3087c4..e518f20 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_stats.h
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_stats.h
@@ -9,6 +9,7 @@
 #include "base/single_thread_task_runner.h"
 #include "third_party/blink/public/platform/web_rtc_stats.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 #include "third_party/webrtc/api/stats/rtc_stats.h"
 #include "third_party/webrtc/api/stats/rtc_stats_collector_callback.h"
 #include "third_party/webrtc/api/stats/rtc_stats_report.h"
@@ -16,6 +17,7 @@
 namespace blink {
 
 class RTCStats;
+class RTCStatsMember;
 
 // Wrapper around a webrtc::RTCStatsReport. Filters out any stats objects that
 // aren't whitelisted. |filter| controls whether to include only standard
@@ -26,8 +28,7 @@
 // |RTCStatsReport|, from renderer/modules/peerconnection/rtc_stats_report.cc|h.
 //
 // TODO(crbug.com/787254): Switch over the classes below from using WebVector
-// and WebString to WTF::Vector and WTF::String, when their respective parent
-// classes are gone.
+// to WTF::Vector, when their respective parent classes are gone.
 class PLATFORM_EXPORT RTCStatsReportPlatform {
  public:
   RTCStatsReportPlatform(
@@ -40,7 +41,7 @@
   std::unique_ptr<RTCStatsReportPlatform> CopyHandle() const;
 
   // Gets stats object by |id|, or null if no stats with that |id| exists.
-  std::unique_ptr<RTCStats> GetStats(blink::WebString id) const;
+  std::unique_ptr<RTCStats> GetStats(const String& id) const;
 
   // The next stats object, or null if the end has been reached.
   std::unique_ptr<RTCStats> Next();
@@ -65,12 +66,12 @@
       const blink::WebVector<webrtc::NonStandardGroupId>& exposed_group_ids);
   virtual ~RTCStats();
 
-  blink::WebString Id() const;
-  blink::WebString GetType() const;
+  String Id() const;
+  String GetType() const;
   double Timestamp() const;
 
   size_t MembersCount() const;
-  std::unique_ptr<blink::WebRTCStatsMember> GetMember(size_t i) const;
+  std::unique_ptr<RTCStatsMember> GetMember(size_t i) const;
 
  private:
   // Reference to keep the report that owns |stats_| alive.
@@ -81,30 +82,30 @@
   const std::vector<const webrtc::RTCStatsMemberInterface*> stats_members_;
 };
 
-class PLATFORM_EXPORT RTCStatsMember : public blink::WebRTCStatsMember {
+class PLATFORM_EXPORT RTCStatsMember {
  public:
   RTCStatsMember(const scoped_refptr<const webrtc::RTCStatsReport>& stats_owner,
                  const webrtc::RTCStatsMemberInterface* member);
-  ~RTCStatsMember() override;
+  virtual ~RTCStatsMember();
 
-  blink::WebString GetName() const override;
-  webrtc::RTCStatsMemberInterface::Type GetType() const override;
-  bool IsDefined() const override;
+  String GetName() const;
+  webrtc::RTCStatsMemberInterface::Type GetType() const;
+  bool IsDefined() const;
 
-  bool ValueBool() const override;
-  int32_t ValueInt32() const override;
-  uint32_t ValueUint32() const override;
-  int64_t ValueInt64() const override;
-  uint64_t ValueUint64() const override;
-  double ValueDouble() const override;
-  blink::WebString ValueString() const override;
-  blink::WebVector<int> ValueSequenceBool() const override;
-  blink::WebVector<int32_t> ValueSequenceInt32() const override;
-  blink::WebVector<uint32_t> ValueSequenceUint32() const override;
-  blink::WebVector<int64_t> ValueSequenceInt64() const override;
-  blink::WebVector<uint64_t> ValueSequenceUint64() const override;
-  blink::WebVector<double> ValueSequenceDouble() const override;
-  blink::WebVector<blink::WebString> ValueSequenceString() const override;
+  bool ValueBool() const;
+  int32_t ValueInt32() const;
+  uint32_t ValueUint32() const;
+  int64_t ValueInt64() const;
+  uint64_t ValueUint64() const;
+  double ValueDouble() const;
+  String ValueString() const;
+  blink::WebVector<int> ValueSequenceBool() const;
+  blink::WebVector<int32_t> ValueSequenceInt32() const;
+  blink::WebVector<uint32_t> ValueSequenceUint32() const;
+  blink::WebVector<int64_t> ValueSequenceInt64() const;
+  blink::WebVector<uint64_t> ValueSequenceUint64() const;
+  blink::WebVector<double> ValueSequenceDouble() const;
+  blink::WebVector<String> ValueSequenceString() const;
 
  private:
   // Reference to keep the report that owns |member_|'s stats object alive.
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_stats_test.cc b/third_party/blink/renderer/platform/peerconnection/rtc_stats_test.cc
index 8e05e2c..12678fb 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_stats_test.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_stats_test.cc
@@ -26,8 +26,8 @@
       new webrtc::RTCPeerConnectionStats(whitelisted_id, 42)));
 
   RTCStatsReportPlatform report(webrtc_report.get(), {});
-  EXPECT_FALSE(report.GetStats(blink::WebString::FromUTF8(not_whitelisted_id)));
-  EXPECT_TRUE(report.GetStats(blink::WebString::FromUTF8(whitelisted_id)));
+  EXPECT_FALSE(report.GetStats(not_whitelisted_id));
+  EXPECT_TRUE(report.GetStats(whitelisted_id));
 }
 
 TEST(RTCStatsTest, OnlyIncludeWhitelistedStats_Iteration) {
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.cc b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.cc
index 55ec5e8..59f7558 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.cc
@@ -154,7 +154,8 @@
       GuessVideoCodecProfile(format),
       media::VideoDecoderConfig::AlphaMode::kIsOpaque, media::VideoColorSpace(),
       media::kNoTransformation, kDefaultSize, gfx::Rect(kDefaultSize),
-      kDefaultSize, media::EmptyExtraData(), media::Unencrypted());
+      kDefaultSize, media::EmptyExtraData(),
+      media::EncryptionScheme::kUnencrypted);
   if (!gpu_factories->IsDecoderConfigSupported(kImplementation, config))
     return nullptr;
 
diff --git a/third_party/blink/tools/blinkpy/common/host.py b/third_party/blink/tools/blinkpy/common/host.py
index e624503..604ee03 100644
--- a/third_party/blink/tools/blinkpy/common/host.py
+++ b/third_party/blink/tools/blinkpy/common/host.py
@@ -31,7 +31,7 @@
 
 from blinkpy.common.checkout.git import Git
 from blinkpy.common.net import web
-from blinkpy.common.net.buildbot import BuildBot
+from blinkpy.common.net.results_fetcher import TestResultsFetcher
 from blinkpy.common.system.system_host import SystemHost
 from blinkpy.web_tests.builder_list import BuilderList
 from blinkpy.web_tests.port.factory import PortFactory
@@ -49,7 +49,7 @@
         self._git = None
 
         # Everything below this line is WebKit-specific and belongs on a higher-level object.
-        self.buildbot = BuildBot()
+        self.results_fetcher = TestResultsFetcher()
 
         # FIXME: Unfortunately Port objects are currently the central-dispatch objects of the NRWT world.
         # In order to instantiate a port correctly, we have to pass it at least an executive, user, git, and filesystem
diff --git a/third_party/blink/tools/blinkpy/common/host_mock.py b/third_party/blink/tools/blinkpy/common/host_mock.py
index b76978f6..05b23c4 100644
--- a/third_party/blink/tools/blinkpy/common/host_mock.py
+++ b/third_party/blink/tools/blinkpy/common/host_mock.py
@@ -27,7 +27,7 @@
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 from blinkpy.common.checkout.git_mock import MockGit
-from blinkpy.common.net.buildbot_mock import MockBuildBot
+from blinkpy.common.net.results_fetcher_mock import MockTestResultsFetcher
 from blinkpy.common.net.web_mock import MockWeb
 from blinkpy.common.path_finder import PathFinder
 from blinkpy.common.system.system_host_mock import MockSystemHost
@@ -59,7 +59,7 @@
         self.web = web or MockWeb()
         self._git = git
 
-        self.buildbot = MockBuildBot()
+        self.results_fetcher = MockTestResultsFetcher()
 
         # Note: We're using a real PortFactory here. Tests which don't wish to depend
         # on the list of known ports should override this with a MockPortFactory.
diff --git a/third_party/blink/tools/blinkpy/common/net/git_cl.py b/third_party/blink/tools/blinkpy/common/net/git_cl.py
index 2caf360..23d868e 100644
--- a/third_party/blink/tools/blinkpy/common/net/git_cl.py
+++ b/third_party/blink/tools/blinkpy/common/net/git_cl.py
@@ -14,7 +14,7 @@
 import re
 
 from blinkpy.common.checkout.git import Git
-from blinkpy.common.net.buildbot import Build, filter_latest_builds
+from blinkpy.common.net.results_fetcher import Build, filter_latest_builds
 from blinkpy.common.net.luci_auth import LuciAuth
 
 
diff --git a/third_party/blink/tools/blinkpy/common/net/git_cl_unittest.py b/third_party/blink/tools/blinkpy/common/net/git_cl_unittest.py
index 06f76d22..bd2a171 100644
--- a/third_party/blink/tools/blinkpy/common/net/git_cl_unittest.py
+++ b/third_party/blink/tools/blinkpy/common/net/git_cl_unittest.py
@@ -5,7 +5,7 @@
 import unittest
 
 from blinkpy.common.host_mock import MockHost
-from blinkpy.common.net.buildbot import Build
+from blinkpy.common.net.results_fetcher import Build
 from blinkpy.common.net.git_cl import CLStatus
 from blinkpy.common.net.git_cl import GitCL
 from blinkpy.common.net.git_cl import SEARCHBUILDS_RESPONSE_PREFIX
diff --git a/third_party/blink/tools/blinkpy/common/net/buildbot.py b/third_party/blink/tools/blinkpy/common/net/results_fetcher.py
similarity index 98%
rename from third_party/blink/tools/blinkpy/common/net/buildbot.py
rename to third_party/blink/tools/blinkpy/common/net/results_fetcher.py
index b9fa6cec..d73e5d1ce 100644
--- a/third_party/blink/tools/blinkpy/common/net/buildbot.py
+++ b/third_party/blink/tools/blinkpy/common/net/results_fetcher.py
@@ -53,8 +53,8 @@
         return super(Build, cls).__new__(cls, builder_name, build_number)
 
 
-class BuildBot(object):
-    """This class represents an interface to BuildBot-related functionality.
+class TestResultsFetcher(object):
+    """This class represents an interface to test results for particular builds.
 
     This includes fetching web test results from Google Storage;
     for more information about the web test result format, see:
diff --git a/third_party/blink/tools/blinkpy/common/net/buildbot_mock.py b/third_party/blink/tools/blinkpy/common/net/results_fetcher_mock.py
similarity index 92%
rename from third_party/blink/tools/blinkpy/common/net/buildbot_mock.py
rename to third_party/blink/tools/blinkpy/common/net/results_fetcher_mock.py
index 2ca7f49..cf0bbad 100644
--- a/third_party/blink/tools/blinkpy/common/net/buildbot_mock.py
+++ b/third_party/blink/tools/blinkpy/common/net/results_fetcher_mock.py
@@ -26,14 +26,15 @@
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-from blinkpy.common.net.buildbot import BuildBot
+from blinkpy.common.net.results_fetcher import TestResultsFetcher
+
 
 # TODO(qyearsley): To be consistent with other fake ("mock") classes, this
-# could be changed so it's not a subclass of BuildBot.
-class MockBuildBot(BuildBot):
+# could be changed so it's not a subclass of TestResultsFetcher.
+class MockTestResultsFetcher(TestResultsFetcher):
 
     def __init__(self):
-        super(MockBuildBot, self).__init__()
+        super(MockTestResultsFetcher, self).__init__()
         self._canned_results = {}
         self._canned_retry_summary_json = {}
         self._webdriver_results = {}
diff --git a/third_party/blink/tools/blinkpy/common/net/buildbot_unittest.py b/third_party/blink/tools/blinkpy/common/net/results_fetcher_test.py
similarity index 80%
rename from third_party/blink/tools/blinkpy/common/net/buildbot_unittest.py
rename to third_party/blink/tools/blinkpy/common/net/results_fetcher_test.py
index deb460a..a706d2d 100644
--- a/third_party/blink/tools/blinkpy/common/net/buildbot_unittest.py
+++ b/third_party/blink/tools/blinkpy/common/net/results_fetcher_test.py
@@ -30,7 +30,7 @@
 import logging
 import unittest
 
-from blinkpy.common.net.buildbot import BuildBot, Build, filter_latest_builds
+from blinkpy.common.net.results_fetcher import TestResultsFetcher, Build, filter_latest_builds
 from blinkpy.common.net.web_mock import MockWeb
 from blinkpy.common.system.log_testing import LoggingTestCase
 
@@ -42,39 +42,39 @@
 
     def test_results_url_no_build_number(self):
         self.assertEqual(
-            BuildBot().results_url('Test Builder'),
+            TestResultsFetcher().results_url('Test Builder'),
             'https://test-results.appspot.com/data/layout_results/Test_Builder/results/layout-test-results')
 
     def test_results_url_with_build_number(self):
         self.assertEqual(
-            BuildBot().results_url('Test Builder', 10),
+            TestResultsFetcher().results_url('Test Builder', 10),
             'https://test-results.appspot.com/data/layout_results/Test_Builder/10/layout-test-results')
 
     def test_results_url_with_build_number_step_name(self):
         self.assertEqual(
-            BuildBot().results_url('Test Builder', 10,
+            TestResultsFetcher().results_url('Test Builder', 10,
                                    'webkit_layout_tests (with patch)'),
             'https://test-results.appspot.com/data/layout_results/Test_Builder'
             '/10/webkit_layout_tests%20%28with%20patch%29/layout-test-results')
 
     def test_results_url_with_non_numeric_build_number(self):
         with self.assertRaisesRegexp(AssertionError, 'expected numeric build number'):
-            BuildBot().results_url('Test Builder', 'ba5eba11')
+            TestResultsFetcher().results_url('Test Builder', 'ba5eba11')
 
     def test_builder_results_url_base(self):
         self.assertEqual(
-            BuildBot().builder_results_url_base('WebKit Mac10.8 (dbg)'),
+            TestResultsFetcher().builder_results_url_base('WebKit Mac10.8 (dbg)'),
             'https://test-results.appspot.com/data/layout_results/WebKit_Mac10_8__dbg_')
 
     def test_accumulated_results_url(self):
         self.assertEqual(
-            BuildBot().accumulated_results_url_base('WebKit Mac10.8 (dbg)'),
+            TestResultsFetcher().accumulated_results_url_base('WebKit Mac10.8 (dbg)'),
             'https://test-results.appspot.com/data/layout_results/WebKit_Mac10_8__dbg_/results/layout-test-results')
 
     def test_fetch_web_test_results_with_no_results_fetched(self):
-        buildbot = BuildBot()
-        buildbot.web = MockWeb()
-        results = buildbot.fetch_web_test_results(buildbot.results_url('B'))
+        fetcher = TestResultsFetcher()
+        fetcher.web = MockWeb()
+        results = fetcher.fetch_web_test_results(fetcher.results_url('B'))
         self.assertIsNone(results)
         self.assertLog([
             'DEBUG: Got 404 response from:\n'
@@ -82,8 +82,8 @@
         ])
 
     def test_fetch_results_with_weird_step_name(self):
-        buildbot = BuildBot()
-        buildbot.web = MockWeb(urls={
+        fetcher = TestResultsFetcher()
+        fetcher.web = MockWeb(urls={
             'https://test-results.appspot.com/testfile?buildnumber=123&'
             'callback=ADD_RESULTS&builder=builder&name=full_results.json':
                 'ADD_RESULTS(%s);' % (json.dumps(
@@ -94,19 +94,19 @@
             'layout-test-results/failing_results.json':
                 json.dumps({'passed': True}),
         })
-        results = buildbot.fetch_results(Build('builder', 123))
+        results = fetcher.fetch_results(Build('builder', 123))
         self.assertEqual(results._results, {  # pylint: disable=protected-access
             'passed': True
         })
         self.assertLog([])
 
     def test_fetch_results_without_build_number(self):
-        buildbot = BuildBot()
-        self.assertIsNone(buildbot.fetch_results(Build('builder', None)))
+        fetcher = TestResultsFetcher()
+        self.assertIsNone(fetcher.fetch_results(Build('builder', None)))
 
     def test_get_step_name(self):
-        buildbot = BuildBot()
-        buildbot.web = MockWeb(urls={
+        fetcher = TestResultsFetcher()
+        fetcher.web = MockWeb(urls={
             'https://test-results.appspot.com/testfile?buildnumber=5&'
             'callback=ADD_RESULTS&builder=foo&name=full_results.json':
                 'ADD_RESULTS(%s);' % (json.dumps(
@@ -115,33 +115,33 @@
                      {"TestType": "webkit_layout_tests (retry with patch)"},
                      {"TestType": "base_unittests (with patch)"}]))
         })
-        step_name = buildbot.get_layout_test_step_name(Build('foo', 5))
+        step_name = fetcher.get_layout_test_step_name(Build('foo', 5))
         self.assertEqual(step_name, 'webkit_layout_tests (with patch)')
         self.assertLog([])
 
     def test_get_step_name_without_build_number(self):
-        buildbot = BuildBot()
+        fetcher = TestResultsFetcher()
         self.assertIsNone(
-            buildbot.get_layout_test_step_name(Build('builder', None)))
+            fetcher.get_layout_test_step_name(Build('builder', None)))
 
     def test_fetch_webdriver_results_without_build_number(self):
-        buildbot = BuildBot()
-        self.assertIsNone(buildbot.fetch_webdriver_test_results(
+        fetcher = TestResultsFetcher()
+        self.assertIsNone(fetcher.fetch_webdriver_test_results(
             Build('builder', None), 'bar'))
         self.assertLog(
             ['DEBUG: Builder name or build number or master is None\n'])
 
     def test_fetch_webdriver_results_without_master(self):
-        buildbot = BuildBot()
-        self.assertIsNone(buildbot.fetch_webdriver_test_results(
+        fetcher = TestResultsFetcher()
+        self.assertIsNone(fetcher.fetch_webdriver_test_results(
             Build('builder', 1), ''))
         self.assertLog(
             ['DEBUG: Builder name or build number or master is None\n'])
 
     def test_fetch_webdriver_test_results_with_no_results(self):
-        buildbot = BuildBot()
-        buildbot.web = MockWeb()
-        results = buildbot.fetch_webdriver_test_results(
+        fetcher = TestResultsFetcher()
+        fetcher.web = MockWeb()
+        results = fetcher.fetch_webdriver_test_results(
             Build('bar-rel', 123), 'foo.chrome')
         self.assertIsNone(results)
         self.assertLog([
@@ -152,15 +152,15 @@
         ])
 
     def test_fetch_webdriver_results_success(self):
-        buildbot = BuildBot()
-        buildbot.web = MockWeb(urls={
+        fetcher = TestResultsFetcher()
+        fetcher.web = MockWeb(urls={
             'https://test-results.appspot.com/testfile?buildnumber=123&'
             'master=foo.chrome&builder=bar-rel&'
             'testtype=webdriver_tests_suite+%28with+patch%29&'
             'name=full_results.json':
                 json.dumps({'passed': True}),
         })
-        results = buildbot.fetch_webdriver_test_results(
+        results = fetcher.fetch_webdriver_test_results(
             Build('bar-rel', 123), 'foo.chrome')
         self.assertEqual(results._results, {  # pylint: disable=protected-access
             'passed': True
@@ -168,7 +168,7 @@
         self.assertLog([])
 
 
-class BuildBotHelperFunctionTest(unittest.TestCase):
+class TestResultsFetcherHelperFunctionTest(unittest.TestCase):
 
     def test_filter_latest_jobs_empty(self):
         self.assertEqual(filter_latest_builds([]), [])
diff --git a/third_party/blink/tools/blinkpy/tool/commands/rebaseline.py b/third_party/blink/tools/blinkpy/tool/commands/rebaseline.py
index d3b6973..3b57d39 100644
--- a/third_party/blink/tools/blinkpy/tool/commands/rebaseline.py
+++ b/third_party/blink/tools/blinkpy/tool/commands/rebaseline.py
@@ -34,7 +34,7 @@
 
 from blinkpy.common.path_finder import WEB_TESTS_LAST_COMPONENT
 from blinkpy.common.memoized import memoized
-from blinkpy.common.net.buildbot import Build
+from blinkpy.common.net.results_fetcher import Build
 from blinkpy.tool.commands.command import Command
 from blinkpy.web_tests.models import test_failures
 from blinkpy.web_tests.models.test_expectations import TestExpectations
@@ -312,7 +312,7 @@
             if options.results_directory:
                 args.extend(['--results-directory', options.results_directory])
 
-            step_name = self._tool.buildbot.get_layout_test_step_name(build)
+            step_name = self._tool.results_fetcher.get_layout_test_step_name(build)
             if step_name:
                 args.extend(['--step-name', step_name])
 
@@ -512,7 +512,7 @@
     def _result_for_test(self, test, build):
         # We need full results to know if a test passed or was skipped.
         # TODO(robertma): Make memoized support kwargs, and use full=True here.
-        results = self._tool.buildbot.fetch_results(build, True)
+        results = self._tool.results_fetcher.fetch_results(build, True)
         if not results:
             _log.debug('No results found for build %s', build)
             return None
diff --git a/third_party/blink/tools/blinkpy/tool/commands/rebaseline_cl.py b/third_party/blink/tools/blinkpy/tool/commands/rebaseline_cl.py
index 595b989eb..5b664cd 100644
--- a/third_party/blink/tools/blinkpy/tool/commands/rebaseline_cl.py
+++ b/third_party/blink/tools/blinkpy/tool/commands/rebaseline_cl.py
@@ -218,7 +218,7 @@
         Returns:
             A dict mapping Build to WebTestResults for all completed jobs.
         """
-        buildbot = self._tool.buildbot
+        results_fetcher = self._tool.results_fetcher
         results = {}
         for build, status in jobs.iteritems():
             if status == TryJobStatus('COMPLETED', 'SUCCESS'):
@@ -230,8 +230,8 @@
                 # Only completed failed builds will contain actual failed
                 # web tests to download baselines for.
                 continue
-            results_url = buildbot.results_url(build.builder_name, build.build_number)
-            web_test_results = buildbot.fetch_results(build)
+            results_url = results_fetcher.results_url(build.builder_name, build.build_number)
+            web_test_results = results_fetcher.fetch_results(build)
             if web_test_results is None:
                 _log.info('Failed to fetch results for "%s".', build.builder_name)
                 _log.info('Results URL: %s/results.html', results_url)
@@ -345,8 +345,8 @@
 
         If the list of new failures could not be obtained, this returns None.
         """
-        buildbot = self._tool.buildbot
-        content = buildbot.fetch_retry_summary_json(build)
+        results_fetcher = self._tool.results_fetcher
+        content = results_fetcher.fetch_retry_summary_json(build)
         if content is None:
             return None
         try:
diff --git a/third_party/blink/tools/blinkpy/tool/commands/rebaseline_cl_unittest.py b/third_party/blink/tools/blinkpy/tool/commands/rebaseline_cl_unittest.py
index 78ddd92..b5865320 100644
--- a/third_party/blink/tools/blinkpy/tool/commands/rebaseline_cl_unittest.py
+++ b/third_party/blink/tools/blinkpy/tool/commands/rebaseline_cl_unittest.py
@@ -7,9 +7,9 @@
 import textwrap
 
 from blinkpy.common.checkout.git_mock import MockGit
-from blinkpy.common.net.buildbot import Build
 from blinkpy.common.net.git_cl import TryJobStatus
 from blinkpy.common.net.git_cl_mock import MockGitCL
+from blinkpy.common.net.results_fetcher import Build
 from blinkpy.common.net.web_test_results import WebTestResults
 from blinkpy.common.path_finder import RELATIVE_WEB_TESTS
 from blinkpy.common.system.log_testing import LoggingTestCase
@@ -95,8 +95,8 @@
         })
 
         for build in builds:
-            self.tool.buildbot.set_results(build, web_test_results)
-            self.tool.buildbot.set_retry_sumary_json(build, json.dumps({
+            self.tool.results_fetcher.set_results(build, web_test_results)
+            self.tool.results_fetcher.set_retry_sumary_json(build, json.dumps({
                 'failures': [
                     'one/flaky-fail.html',
                     'one/missing.html',
@@ -344,7 +344,7 @@
             Build('MOCK Try Linux', 6000): TryJobStatus('COMPLETED', 'FAILURE'),
         }
         for build in builds:
-            self.tool.buildbot.set_retry_sumary_json(build, json.dumps({
+            self.tool.results_fetcher.set_retry_sumary_json(build, json.dumps({
                 'failures': ['one/text-fail.html'],
                 'ignored': ['two/image-fail.html'],
             }))
@@ -358,7 +358,7 @@
     def test_execute_with_no_retry_summary_downloaded(self):
         # In this example, the retry summary could not be downloaded, so
         # a warning is printed and all tests are rebaselined.
-        self.tool.buildbot.set_retry_sumary_json(
+        self.tool.results_fetcher.set_retry_sumary_json(
             Build('MOCK Try Win', 5000), None)
         exit_code = self.command.execute(self.command_options(), [], self.tool)
         self.assertEqual(exit_code, 0)
@@ -427,7 +427,7 @@
         ])
 
     def test_execute_missing_results_with_no_fill_missing_prompts(self):
-        self.tool.buildbot.set_results(Build('MOCK Try Win', 5000), None)
+        self.tool.results_fetcher.set_results(Build('MOCK Try Win', 5000), None)
         exit_code = self.command.execute(self.command_options(), [], self.tool)
         self.assertEqual(exit_code, 1)
         self.assertLog([
@@ -442,7 +442,7 @@
         ])
 
     def test_execute_missing_results_with_fill_missing_continues(self):
-        self.tool.buildbot.set_results(Build('MOCK Try Win', 5000), None)
+        self.tool.results_fetcher.set_results(Build('MOCK Try Win', 5000), None)
         exit_code = self.command.execute(
             self.command_options(fill_missing=True),
             ['one/flaky-fail.html'], self.tool)
diff --git a/third_party/blink/tools/blinkpy/tool/commands/rebaseline_test.py b/third_party/blink/tools/blinkpy/tool/commands/rebaseline_test.py
index 55821b3..88f884b 100644
--- a/third_party/blink/tools/blinkpy/tool/commands/rebaseline_test.py
+++ b/third_party/blink/tools/blinkpy/tool/commands/rebaseline_test.py
@@ -35,7 +35,7 @@
         if options.results_directory:
             results_url = 'file://' + options.results_directory
         else:
-            results_url = self._tool.buildbot.results_url(
+            results_url = self._tool.results_fetcher.results_url(
                 options.builder, build_number=options.build_number,
                 step_name=options.step_name)
 
diff --git a/third_party/blink/tools/blinkpy/tool/commands/rebaseline_unittest.py b/third_party/blink/tools/blinkpy/tool/commands/rebaseline_unittest.py
index 4afc49d4..88ad5a4f 100644
--- a/third_party/blink/tools/blinkpy/tool/commands/rebaseline_unittest.py
+++ b/third_party/blink/tools/blinkpy/tool/commands/rebaseline_unittest.py
@@ -6,7 +6,7 @@
 import optparse
 import unittest
 
-from blinkpy.common.net.buildbot import Build
+from blinkpy.common.net.results_fetcher import Build
 from blinkpy.common.net.web_test_results import WebTestResults
 from blinkpy.common.path_finder import RELATIVE_WEB_TESTS
 from blinkpy.common.system.executive_mock import MockExecutive
@@ -89,7 +89,7 @@
 
     def _setup_mock_build_data(self):
         for builder in ['MOCK Win7', 'MOCK Win7 (dbg)', 'MOCK Mac10.11']:
-            self.tool.buildbot.set_results(Build(builder), WebTestResults({
+            self.tool.results_fetcher.set_results(Build(builder), WebTestResults({
                 'tests': {
                     'userscripts': {
                         'first-test.html': {
@@ -190,7 +190,7 @@
         }, **kwargs))
 
     def test_rebaseline_test_passes_on_all_builders(self):
-        self.tool.buildbot.set_results(Build('MOCK Win7'), WebTestResults({
+        self.tool.results_fetcher.set_results(Build('MOCK Win7'), WebTestResults({
             'tests': {
                 'userscripts': {
                     'first-test.html': {
@@ -482,7 +482,7 @@
                     ('Bug(x) [ Mac ] userscripts/skipped-test.html [ WontFix ]\n'
                      'Bug(y) [ Win ] userscripts/skipped-test.html [ Skip ]\n'))
         self._write('userscripts/skipped-test.html', 'Dummy test contents')
-        self.tool.buildbot.set_results(Build('MOCK Mac10.11'), WebTestResults({
+        self.tool.results_fetcher.set_results(Build('MOCK Mac10.11'), WebTestResults({
             'tests': {
                 'userscripts': {
                     'skipped-test.html': {
@@ -492,7 +492,7 @@
                 }
             }
         }))
-        self.tool.buildbot.set_results(Build('MOCK Win7'), WebTestResults({
+        self.tool.results_fetcher.set_results(Build('MOCK Win7'), WebTestResults({
             'tests': {
                 'userscripts': {
                     'skipped-test.html': {
@@ -519,7 +519,7 @@
         # Flaky expectations should be kept even if the test passes.
         self._write(self.test_expectations_path, 'Bug(x) userscripts/flaky-test.html [ Pass Failure ]\n')
         self._write('userscripts/flaky-test.html', 'Dummy test contents')
-        self.tool.buildbot.set_results(Build('MOCK Mac10.11'), WebTestResults({
+        self.tool.results_fetcher.set_results(Build('MOCK Mac10.11'), WebTestResults({
             'tests': {
                 'userscripts': {
                     'flaky-test.html': {
@@ -545,7 +545,7 @@
         self._write(self.test_expectations_path, 'Bug(foo) userscripts/all-pass.html [ Failure ]\n')
         self._write('userscripts/all-pass.html', 'Dummy test contents')
         test_baseline_set = TestBaselineSet(self.tool)
-        self.tool.buildbot.set_results(Build('MOCK Mac10.11'), WebTestResults({
+        self.tool.results_fetcher.set_results(Build('MOCK Mac10.11'), WebTestResults({
             'tests': {
                 'userscripts': {
                     'all-pass.html': {
@@ -572,7 +572,7 @@
         self._write('userscripts/all-pass.html', 'Dummy test contents')
         test_baseline_set = TestBaselineSet(self.tool)
         for builder in ['MOCK Win7', 'MOCK Win10', 'MOCK Mac10.10', 'MOCK Mac10.11', 'MOCK Precise', 'MOCK Trusty']:
-            self.tool.buildbot.set_results(Build(builder), WebTestResults({
+            self.tool.results_fetcher.set_results(Build(builder), WebTestResults({
                 'tests': {
                     'userscripts': {
                         'all-pass.html': {
@@ -599,7 +599,7 @@
         self._write(self.test_expectations_path, 'Bug(foo) userscripts/all-pass.html [ Failure ]\n')
         self._write('userscripts/all-pass.html', 'Dummy test contents')
         test_baseline_set = TestBaselineSet(self.tool)
-        self.tool.buildbot.set_results(Build('MOCK Mac10.11'), WebTestResults({
+        self.tool.results_fetcher.set_results(Build('MOCK Mac10.11'), WebTestResults({
             'tests': {
                 'userscripts': {
                     'all-pass.html': {
diff --git a/third_party/blink/tools/blinkpy/w3c/test_importer.py b/third_party/blink/tools/blinkpy/w3c/test_importer.py
index ef9bd83..60d218a 100644
--- a/third_party/blink/tools/blinkpy/w3c/test_importer.py
+++ b/third_party/blink/tools/blinkpy/w3c/test_importer.py
@@ -17,9 +17,9 @@
 import logging
 import re
 
-from blinkpy.common.net.buildbot import current_build_link
 from blinkpy.common.net.git_cl import GitCL
 from blinkpy.common.net.network_transaction import NetworkTimeout
+from blinkpy.common.net.results_fetcher import current_build_link
 from blinkpy.common.path_finder import PathFinder
 from blinkpy.common.system.executive import ScriptError
 from blinkpy.common.system.log_utils import configure_logging
diff --git a/third_party/blink/tools/blinkpy/w3c/test_importer_unittest.py b/third_party/blink/tools/blinkpy/w3c/test_importer_unittest.py
index e3d78bd..19d8ba2d 100644
--- a/third_party/blink/tools/blinkpy/w3c/test_importer_unittest.py
+++ b/third_party/blink/tools/blinkpy/w3c/test_importer_unittest.py
@@ -7,11 +7,11 @@
 
 from blinkpy.common.checkout.git_mock import MockGit
 from blinkpy.common.host_mock import MockHost
-from blinkpy.common.net.buildbot import Build
 from blinkpy.common.net.git_cl import CLStatus
 from blinkpy.common.net.git_cl import TryJobStatus
 from blinkpy.common.net.git_cl_mock import MockGitCL
 from blinkpy.common.net.network_transaction import NetworkTimeout
+from blinkpy.common.net.results_fetcher import Build
 from blinkpy.common.path_finder import RELATIVE_WEB_TESTS
 from blinkpy.common.system.executive_mock import MockCall
 from blinkpy.common.system.executive_mock import MockExecutive
diff --git a/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater.py b/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater.py
index ca00808..edde8b43 100644
--- a/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater.py
+++ b/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater.py
@@ -131,14 +131,14 @@
             # All tests passed, so there should be no failing results.
             return {}
 
-        test_result_list = [self.host.buildbot.fetch_results(build)]
+        test_result_list = [self.host.results_fetcher.fetch_results(build)]
         has_webdriver_tests = self.host.builders.has_webdriver_tests_for_builder(
             build.builder_name)
         if has_webdriver_tests:
             master = self.host.builders.master_for_builder(
                 build.builder_name)
             test_result_list.append(
-                self.host.buildbot.fetch_webdriver_test_results(build, master))
+                self.host.results_fetcher.fetch_webdriver_test_results(build, master))
 
         test_result_list = filter(None, test_result_list)
         if not test_result_list:
diff --git a/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater_unittest.py b/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater_unittest.py
index 3bf488c..5f7c6724 100644
--- a/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater_unittest.py
+++ b/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater_unittest.py
@@ -6,10 +6,10 @@
 import json
 
 from blinkpy.common.host_mock import MockHost
-from blinkpy.common.net.buildbot import Build
-from blinkpy.common.net.buildbot_mock import MockBuildBot
 from blinkpy.common.net.git_cl import TryJobStatus
 from blinkpy.common.net.git_cl_mock import MockGitCL
+from blinkpy.common.net.results_fetcher import Build
+from blinkpy.common.net.results_fetcher_mock import MockTestResultsFetcher
 from blinkpy.common.net.web_test_results import WebTestResult, WebTestResults
 from blinkpy.common.system.executive import ScriptError
 from blinkpy.common.system.log_testing import LoggingTestCase
@@ -107,7 +107,7 @@
         # results are for the other builders since we shouldn't need to even
         # fetch results, since the try job status already tells us that all
         # of the tests passed.
-        host.buildbot.set_results(
+        host.results_fetcher.set_results(
             Build('MOCK Try Mac10.10', 333),
             WebTestResults({
                 'tests': {
@@ -127,7 +127,7 @@
         self.assertEqual(0, updater.run(args=[]))
 
         # Results are only fetched for failing builds.
-        self.assertEqual(host.buildbot.fetched_builds, [Build('MOCK Try Mac10.10', 333)])
+        self.assertEqual(host.results_fetcher.fetched_builds, [Build('MOCK Try Mac10.10', 333)])
 
         self.assertEqual(
             host.filesystem.read_text_file(expectations_path),
@@ -136,7 +136,7 @@
 
     def test_get_failing_results_dict_only_passing_results(self):
         host = self.mock_host()
-        host.buildbot.set_results(Build('MOCK Try Mac10.10', 123), WebTestResults({
+        host.results_fetcher.set_results(Build('MOCK Try Mac10.10', 123), WebTestResults({
             'tests': {
                 'external': {
                     'wpt': {
@@ -156,7 +156,7 @@
 
     def test_get_failing_results_dict_unexpected_pass(self):
         host = self.mock_host()
-        host.buildbot.set_results(Build('MOCK Try Mac10.10', 123), WebTestResults({
+        host.results_fetcher.set_results(Build('MOCK Try Mac10.10', 123), WebTestResults({
             'tests': {
                 'external': {
                     'wpt': {
@@ -177,15 +177,15 @@
 
     def test_get_failing_results_dict_no_results(self):
         host = self.mock_host()
-        host.buildbot = MockBuildBot()
-        host.buildbot.set_results(Build('MOCK Try Mac10.10', 123), None)
+        host.results_fetcher = MockTestResultsFetcher()
+        host.results_fetcher.set_results(Build('MOCK Try Mac10.10', 123), None)
         updater = WPTExpectationsUpdater(host)
         self.assertEqual(
             updater.get_failing_results_dict(Build('MOCK Try Mac10.10', 123)), {})
 
     def test_get_failing_results_dict_some_failing_results(self):
         host = self.mock_host()
-        host.buildbot.set_results(Build('MOCK Try Mac10.10', 123), WebTestResults({
+        host.results_fetcher.set_results(Build('MOCK Try Mac10.10', 123), WebTestResults({
             'tests': {
                 'external': {
                     'wpt': {
@@ -216,7 +216,7 @@
 
     def test_get_failing_results_dict_non_wpt_test(self):
         host = self.mock_host()
-        host.buildbot.set_results(Build('MOCK Try Mac10.10', 123), WebTestResults({
+        host.results_fetcher.set_results(Build('MOCK Try Mac10.10', 123), WebTestResults({
             'tests': {
                 'x': {
                     'failing-test.html': {
@@ -233,7 +233,7 @@
 
     def test_get_failing_results_dict_webdriver_failing_results_(self):
         host = self.mock_host()
-        host.buildbot.set_results(Build('MOCK Try Trusty', 123), WebTestResults({
+        host.results_fetcher.set_results(Build('MOCK Try Trusty', 123), WebTestResults({
             'tests': {
                 'external': {
                     'wpt': {
@@ -249,7 +249,7 @@
             },
         }))
 
-        host.buildbot.set_webdriver_test_results(Build('MOCK Try Trusty', 123), "tryserver.blink", WebTestResults({
+        host.results_fetcher.set_webdriver_test_results(Build('MOCK Try Trusty', 123), "tryserver.blink", WebTestResults({
             'tests': {
                 'external': {
                     'wpt': {
diff --git a/third_party/blink/tools/blinkpy/web_tests/builder_list.py b/third_party/blink/tools/blinkpy/web_tests/builder_list.py
index b4aa967..7826ad2 100644
--- a/third_party/blink/tools/blinkpy/web_tests/builder_list.py
+++ b/third_party/blink/tools/blinkpy/web_tests/builder_list.py
@@ -48,14 +48,14 @@
                 Valid values for the version specifier can be found in
                 TestExpectationsParser._configuration_tokens_list, and valid
                 values for the build type specifier include "Release" and "Debug".
-            "is_try_builder": Whether the builder is a try bot.
+            "is_try_builder": Whether the builder is a trybot.
             "master": The master name of the builder. It is deprecated, but still required
                 by test-results.appspot.com API."
             "has_webdriver_tests": Whether webdriver_tests_suite runs on this builder.
 
         Possible refactoring note: Potentially, it might make sense to use
-        blinkpy.common.buildbot.Builder and add port_name and specifiers
-        properties to that class.
+        blinkpy.common.net.results_fetcher.Builder and add port_name and
+        specifiers properties to that class.
         """
         self._builders = builders_dict
         for builder in builders_dict:
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/base.py b/third_party/blink/tools/blinkpy/web_tests/port/base.py
index 8879f61f..dafaf0a 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/base.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/base.py
@@ -33,6 +33,7 @@
 """
 
 import collections
+import itertools
 import json
 import logging
 import optparse
@@ -809,9 +810,10 @@
                 tests.extend(self._wpt_test_urls_matching_paths(paths))
         else:
             tests.extend(self._all_virtual_tests())
-            tests.extend([wpt_path + self.TEST_PATH_SEPARATOR + test for wpt_path in self.WPT_DIRS
+            # '/' is used instead of filesystem.sep as the WPT manifest always
+            # uses '/' for paths (it is not OS dependent).
+            tests.extend([wpt_path + '/' + test for wpt_path in self.WPT_DIRS
                           for test in self.wpt_manifest(wpt_path).all_urls()])
-
         return tests
 
     def real_tests(self, paths):
@@ -1665,9 +1667,33 @@
 
     def _all_virtual_tests(self):
         tests = []
+
+        # The set of paths to find tests for each virtual test suite.
+        suite_paths = []
+        # For each path, a map functor that converts the test path to be under
+        # the virtual test suite.
+        suite_prefixes = []
         for suite in self.virtual_test_suites():
-            self._populate_virtual_suite(suite)
-            tests.extend(suite.tests.keys())
+            for b in suite.bases:
+                suite_paths.append(b)
+                suite_prefixes.append(suite.full_prefix)
+
+            # TODO(crbug.com/982208): If we can pass in the set of paths and
+            # maps then this could be more efficient.
+            if suite.bases:
+                tests.extend(map(lambda x : suite.full_prefix + x, self.real_tests(suite.bases)))
+
+        if suite_paths:
+            tests.extend(self._wpt_test_urls_matching_paths(suite_paths, suite_prefixes))
+        return tests
+
+    def _all_virtual_tests_for_suite(self, suite):
+        if not suite.bases:
+            return []
+        tests = []
+        tests.extend(map(lambda x : suite.full_prefix + x, self.real_tests(suite.bases)))
+        tests.extend(self._wpt_test_urls_matching_paths(suite.bases,
+                                                        [suite.full_prefix] * len(suite.bases)))
         return tests
 
     def _virtual_tests_matching_paths(self, paths):
@@ -1676,8 +1702,7 @@
         for suite in self.virtual_test_suites():
             if not any(p.startswith(suite.full_prefix) for p in normalized_paths):
                 continue
-            self._populate_virtual_suite(suite)
-            for test in suite.tests:
+            for test in self._all_virtual_tests_for_suite(suite):
                 if any(test.startswith(p) for p in normalized_paths):
                     tests.append(test)
 
@@ -1689,58 +1714,89 @@
     def _path_has_wildcard(self, path):
         return '*' in path
 
-    def _wpt_test_urls_matching_paths(self, paths):
-        # This is to make sure "external[\\/]?" can also match to external/wpt.
-        # TODO(robertma): Remove this special case when external/wpt is moved to wpt.
-        paths = [self._filesystem.join(path, 'wpt') if path.rstrip('\\/').endswith('external')
-                 else path for path in paths]
-        # '/' is used throughout this function instead of filesystem.sep as the WPT manifest always
-        # uses '/' for paths (it is not OS dependent).
-        if self._filesystem.sep != '/':
-            paths = [path.replace(self._filesystem.sep, '/') for path in paths]
+    def _wpt_test_urls_matching_paths(self, filter_paths, virtual_prefixes=[]):
+        """Returns a set of paths that are tests to be run from the
+        web-platform-test manifest files.
+
+        filter_paths: A list of strings that are prefix matched against the
+            list of tests in the WPT manifests. Only tests that match are returned.
+        virtual_prefixes: A list of prefixes corresponding to paths in |filter_paths|.
+            If present, each test path output should have its virtual prefix
+            prepended to the resulting path to the test.
+        """
+        # Generate the manifest files if needed and then read them. Do this once
+        # for this whole method as the file is large and generation/loading is
+        # slow.
+        wpts = [(wpt_path, self.wpt_manifest(wpt_path)) for wpt_path in self.WPT_DIRS]
+
+        _log.debug("Finding WPT tests that match %d path prefixes", len(filter_paths));
 
         tests = []
-        for wpt_path in self.WPT_DIRS:
-            tests += self._wpt_test_urls(wpt_path, paths)
-        return tests
+        # This walks through the set of paths where we should look for tests.
+        # For each path, a map can be provided that we replace 'path' with in
+        # the result.
+        for filter_path, virtual_prefix in itertools.izip_longest(filter_paths, virtual_prefixes):
+            # This is to make sure "external[\\/]?" can also match to
+            # external/wpt.
+            # TODO(robertma): Remove this special case when external/wpt is
+            # moved to wpt.
+            if filter_path.rstrip('\\/').endswith('external'):
+                filter_path = self._filesystem.join(filter_path, 'wpt')
+            # '/' is used throughout this function instead of filesystem.sep as
+            # the WPT manifest always uses '/' for paths (it is not OS
+            # dependent).
+            if self._filesystem.sep != '/':
+                filter_path = filter_path.replace(self._filesystem.sep, '/')
 
-    def _wpt_test_urls(self, wpt_path, paths):
-        tests = []
-        for test_url_path in self.wpt_manifest(wpt_path).all_urls():
-            assert not test_url_path.startswith('/')
-            full_test_url_path = wpt_path + '/' + test_url_path
+            # Drop empty path components.
+            filter_path = filter_path.replace('//', '/')
 
-            for path in paths:
-                if not path.startswith(wpt_path):
+            # We now have in |filter_path| a path to an actual test directory or file
+            # on disk, in unix format, relative to the root of the web_tests
+            # directory.
+
+            for wpt_path, wpt_manifest in wpts:
+                # If the |filter_path| is not inside a WPT dir, then we will
+                # match no tests in the manifest.
+                if not filter_path.startswith(wpt_path):
                     continue
+                # Drop the WPT prefix (including the joining '/') from |path|.
+                filter_path_from_wpt = filter_path[len(wpt_path) + 1:]
 
-                # Also remove the slash after wpt_path, if any.
-                path_in_wpt = path[len(wpt_path) + 1:]
+                # An empty filter matches everything.
+                if filter_path_from_wpt:
+                    # If the filter is to a specific test file that ends with .js,
+                    # we match that against tests with any extension by dropping
+                    # the extension from the filter.
+                    #
+                    # Else, when matching a directory, ensure the filter ends in '/'
+                    # to only match the exact directory name and not directories
+                    # with the filter as a prefix.
+                    if wpt_manifest.is_test_file(filter_path_from_wpt):
+                        filter_path_from_wpt = re.sub(r'\.js$', '.', filter_path_from_wpt)
+                    elif not wpt_manifest.is_test_url(filter_path_from_wpt):
+                        filter_path_from_wpt = filter_path_from_wpt.rstrip('/') + '/'
 
-                # When `test_url_path` is test.any.html etc., and `path_in_wpt` is test.any.js:
-                matches_any_js_test = (
-                    self.wpt_manifest(wpt_path).is_test_file(path_in_wpt)
-                    and test_url_path.startswith(re.sub(r'\.js$', '', path_in_wpt))
-                )
+                # We now have a path to an actual test directory or file on
+                # disk, in unix format, relative to the WPT directory.
+                #
+                # Look for all tests in the manifest that are under the relative
+                # |filter_path_from_wpt|.
+                for test_path_from_wpt in wpt_manifest.all_urls():
+                    assert not test_path_from_wpt.startswith('/')
+                    assert not test_path_from_wpt.endswith('/')
 
-                # For all other path matches within WPT:
-                # Get a list of directories for both paths, filter empty strings.
-                full_test_url_directories = filter(None, full_test_url_path.split('/'))
-                path_directories = filter(None, path.split('/'))
-                test_is_in_path = path_directories == full_test_url_directories[0:len(path_directories)]
+                    # Drop empty path components.
+                    test_path_from_wpt = test_path_from_wpt.replace('//', '/')
 
-                if matches_any_js_test or test_is_in_path:
-                    tests.append(full_test_url_path)
+                    if test_path_from_wpt.startswith(filter_path_from_wpt):
+                        # The result is a test path from the root web test
+                        # directory. If a |virtual_prefix| was given, we prepend
+                        # that to the result.
+                        prefix = virtual_prefix if virtual_prefix else ''
+                        tests.append(prefix + wpt_path + '/' + test_path_from_wpt)
         return tests
 
-    def _populate_virtual_suite(self, suite):
-        if not suite.tests:
-            base_tests = self.real_tests(suite.bases) if suite.bases else []
-            base_tests.extend(self._wpt_test_urls_matching_paths(suite.bases))
-            suite.tests = {}
-            for test in base_tests:
-                suite.tests[suite.full_prefix + test] = test
-
     def _lookup_virtual_suite(self, test_name):
         for suite in self.virtual_test_suites():
             if test_name.startswith(suite.full_prefix):
@@ -1870,7 +1926,6 @@
         self.full_prefix = 'virtual/' + prefix + '/'
         self.bases = bases
         self.args = args
-        self.tests = {}
 
     def __repr__(self):
         return "VirtualTestSuite('%s', %s, %s)" % (self.full_prefix, self.bases, self.args)
diff --git a/third_party/blink/tools/blinkpy/web_tests/try_flag.py b/third_party/blink/tools/blinkpy/web_tests/try_flag.py
index 0060996b..ac00830 100644
--- a/third_party/blink/tools/blinkpy/web_tests/try_flag.py
+++ b/third_party/blink/tools/blinkpy/web_tests/try_flag.py
@@ -118,12 +118,12 @@
         self._host.print_('Fetching results...')
         # TODO: Get jobs from the _tryflag branch. Current branch for now.
         jobs = self._git_cl.latest_try_jobs(builder_names=BUILDER_CONFIGS.keys())
-        buildbot = self._host.buildbot
+        results_fetcher = self._host.results_fetcher
         for build in sorted(jobs):
             self._host.print_('-- %s: %s/results.html' % (
                 BUILDER_CONFIGS[build.builder_name].version,
-                buildbot.results_url(build.builder_name, build.build_number)))
-            results = buildbot.fetch_results(build, True)
+                results_fetcher.results_url(build.builder_name, build.build_number)))
+            results = results_fetcher.fetch_results(build, True)
             results.for_each_test(
                 lambda result, b=build: self._process_result(b, result))
 
diff --git a/third_party/blink/tools/blinkpy/web_tests/try_flag_unittest.py b/third_party/blink/tools/blinkpy/web_tests/try_flag_unittest.py
index 62e53fe..3a810ab 100644
--- a/third_party/blink/tools/blinkpy/web_tests/try_flag_unittest.py
+++ b/third_party/blink/tools/blinkpy/web_tests/try_flag_unittest.py
@@ -5,9 +5,9 @@
 import unittest
 
 from blinkpy.common.host_mock import MockHost
-from blinkpy.common.net.buildbot import Build
 from blinkpy.common.net.git_cl import TryJobStatus
 from blinkpy.common.net.git_cl_mock import MockGitCL
+from blinkpy.common.net.results_fetcher import Build
 from blinkpy.common.net.web_test_results import WebTestResults
 from blinkpy.common.path_finder import PathFinder
 from blinkpy.web_tests.try_flag import TryFlag
@@ -68,8 +68,8 @@
         self._run_trigger_test(regenerate=False)
         self._run_trigger_test(regenerate=True)
 
-    def _setup_mock_results(self, buildbot):
-        buildbot.set_results(self.linux_build, WebTestResults({
+    def _setup_mock_results(self, results_fetcher):
+        results_fetcher.set_results(self.linux_build, WebTestResults({
             'tests': {
                 'something': {
                     'fail-everywhere.html': {
@@ -85,7 +85,7 @@
                 }
             }
         }))
-        buildbot.set_results(self.win_build, WebTestResults({
+        results_fetcher.set_results(self.win_build, WebTestResults({
             'tests': {
                 'something': {
                     'fail-everywhere.html': {
@@ -101,7 +101,7 @@
                 }
             }
         }))
-        buildbot.set_results(self.mac_build, WebTestResults({
+        results_fetcher.set_results(self.mac_build, WebTestResults({
             'tests': {
                 'something': {
                     'pass-unexpectedly-mac.html': {
@@ -129,7 +129,7 @@
             flag_expectations_file,
             'something/pass-unexpectedly-mac.html [ Fail ]')
 
-        self._setup_mock_results(host.buildbot)
+        self._setup_mock_results(host.results_fetcher)
         cmd = ['update', '--flag=--foo']
         TryFlag(cmd, host, MockGitCL(host, self.mock_try_results)).run()
 
@@ -163,7 +163,7 @@
         finder = PathFinder(filesystem)
         flag_expectations_file = finder.path_from_web_tests(
             'FlagExpectations', 'foo')
-        self._setup_mock_results(host.buildbot)
+        self._setup_mock_results(host.results_fetcher)
         cmd = ['update', '--flag=--foo']
 
         # Unexpected passes that don't have flag-specific failure expectations
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index e7f620bb..b1a92b1 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1387,11 +1387,9 @@
 crbug.com/591099 virtual/layout_ng_flex_box/css3/flexbox/auto-height-column-with-border-and-padding.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_flex_box/css3/flexbox/flex-align-vertical-writing-mode.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_flex_box/css3/flexbox/flex-align.html [ Failure ]
-crbug.com/591099 virtual/layout_ng_flex_box/css3/flexbox/flex-flow-2.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_flex_box/css3/flexbox/flex-flow-border.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_flex_box/css3/flexbox/flex-flow-margins-auto-size.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_flex_box/css3/flexbox/flex-flow-padding.html [ Failure ]
-crbug.com/591099 virtual/layout_ng_flex_box/css3/flexbox/flex-flow.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_flex_box/css3/flexbox/flex-order.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_flex_box/css3/flexbox/flexbox-baseline-margins.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_flex_box/css3/flexbox/flexbox-baseline.html [ Failure ]
diff --git a/third_party/blink/web_tests/external/wpt/2dcontext/transformations/2d.transformation.getTransform.html b/third_party/blink/web_tests/external/wpt/2dcontext/transformations/2d.transformation.getTransform.html
new file mode 100644
index 0000000..664efd5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/2dcontext/transformations/2d.transformation.getTransform.html
@@ -0,0 +1,39 @@
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+// Ensure that context2d.getTransform works
+const epsilon = 1e-5;
+const canvas = document.createElement('canvas');
+const ctx = canvas.getContext('2d');
+
+test(function(t) {
+  assert_array_equals(ctx.getTransform().toFloat32Array(),
+    [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
+    "Assert that an untransformed matrix is identity");
+
+  ctx.scale(2, 3);
+  transform = ctx.getTransform();
+  assert_array_equals(ctx.getTransform().toFloat32Array(),
+    [2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
+    "Assert that context2d scaling works");
+
+  ctx.rotate(Math.PI/2);
+  transform = ctx.getTransform();
+  assert_array_approx_equals(ctx.getTransform().toFloat32Array(),
+    [0, 3, 0, 0, -2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], epsilon,
+    "Assert that context2d rotate works");
+
+  ctx.translate(1, -1);
+  transform = ctx.getTransform();
+  assert_array_approx_equals(ctx.getTransform().toFloat32Array(),
+    [0, 3, 0, 0, -2, 0, 0, 0, 0, 0, 1, 0, 2, 3, 0, 1], epsilon,
+    "Assert context2d translate works.");
+
+  ctx.resetTransform();
+  assert_array_equals(ctx.getTransform().toFloat32Array(),
+    [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
+    "Assert that a reset matrix is identity");
+}, 'This test ensures that getTransform works correctly.');
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/IndexedDB/idbindex_reverse_cursor.any.js b/third_party/blink/web_tests/external/wpt/IndexedDB/idbindex_reverse_cursor.any.js
new file mode 100644
index 0000000..0b3c767f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/IndexedDB/idbindex_reverse_cursor.any.js
@@ -0,0 +1,60 @@
+// META: title=Reverse Cursor Validity
+// META: script=support-promises.js
+
+async function iterateAndReturnAllCursorResult(testCase, cursor) {
+  return new Promise((resolve, reject) => {
+    let results = [];
+    cursor.onsuccess = testCase.step_func(function(e) {
+      let cursor = e.target.result;
+      if (!cursor) {
+        resolve(results);
+        return;
+      }
+      results.push(cursor.value);
+      cursor.continue();
+    });
+    cursor.onerror = reject;
+  });
+}
+
+promise_test(async testCase => {
+  const db = await createDatabase(testCase, db => {
+    db.createObjectStore('objectStore', {keyPath: 'key'})
+              .createIndex('index', 'indexedOn');
+  });
+  const txn1 = db.transaction(['objectStore'], 'readwrite');
+  txn1.objectStore('objectStore').add({'key': 'firstItem', 'indexedOn': 3});
+  const txn2 = db.transaction(['objectStore'], 'readwrite');
+  txn2.objectStore('objectStore').put({'key': 'firstItem', 'indexedOn': -1});
+  const txn3= db.transaction(['objectStore'], 'readwrite');
+  txn3.objectStore('objectStore').add({'key': 'secondItem', 'indexedOn': 2});
+
+  const txn4 = db.transaction(['objectStore'], 'readonly');
+  cursor = txn4.objectStore('objectStore').index('index').openCursor(IDBKeyRange.bound(0, 10), "prev");
+  let results = await iterateAndReturnAllCursorResult(testCase, cursor);
+
+  assert_equals(results.length, 1);
+
+  await promiseForTransaction(testCase, txn4);
+  db.close();
+}, 'Reverse cursor sees update from separate transactions.');
+
+promise_test(async testCase => {
+  const db = await createDatabase(testCase, db => {
+    db.createObjectStore('objectStore', {keyPath: 'key'})
+              .createIndex('index', 'indexedOn');
+  });
+  const txn = db.transaction(['objectStore'], 'readwrite');
+  txn.objectStore('objectStore').add({'key': '1', 'indexedOn': 2});
+  txn.objectStore('objectStore').put({'key': '1', 'indexedOn': -1});
+  txn.objectStore('objectStore').add({'key': '2', 'indexedOn': 1});
+
+  const txn2 = db.transaction(['objectStore'], 'readonly');
+  cursor = txn2.objectStore('objectStore').index('index').openCursor(IDBKeyRange.bound(0, 10), "prev");
+  let results = await iterateAndReturnAllCursorResult(testCase, cursor);
+
+  assert_equals(1, results.length);
+
+  await promiseForTransaction(testCase, txn2);
+  db.close();
+}, 'Reverse cursor sees in-transaction update.');
diff --git a/third_party/blink/web_tests/external/wpt/css/css-lists/ol-change-display-type-ref.html b/third_party/blink/web_tests/external/wpt/css/css-lists/ol-change-display-type-ref.html
new file mode 100644
index 0000000..a10bc17
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-lists/ol-change-display-type-ref.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel=help href="https://crbug.com/1020669">
+<style>
+  .item {
+    display: list-item;
+  }
+  .item:after {
+    content: counter(section);
+    display: list-item;
+  }
+  .table-header {
+    display: table-header-group;
+  }
+</style>
+<body>
+  <ol reversed="reversed">
+    <span class="table-header">
+      <figure class="item"></figure>
+    </span>
+  </ol>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-lists/ol-change-display-type.html b/third_party/blink/web_tests/external/wpt/css/css-lists/ol-change-display-type.html
new file mode 100644
index 0000000..e711cfe
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-lists/ol-change-display-type.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Lists: Change display type of reverse ol list element.</title>
+<link rel=help href="https://crbug.com/1020669">
+<link rel=match href="ol-change-display-type-ref.html">
+<style>
+  .item {
+    display: list-item;
+  }
+  .item:after {
+    content: counter(section);
+    display: list-item;
+  }
+  .table-header {
+    display: table-header-group;
+  }
+</style>
+<body>
+  <ol reversed="reversed">
+    <span id="span">
+      <figure class="item"></figure>
+    </span>
+  </ol>
+</body>
+<script>
+  document.body.offsetTop;
+  document.getElementById('span').setAttribute('class', 'table-header');
+</script>
+</html>
diff --git "a/third_party/blink/web_tests/external/wpt/html/dom/idlharness.https_exclude=\050Document_Window_HTML._\051-expected.txt" "b/third_party/blink/web_tests/external/wpt/html/dom/idlharness.https_exclude=\050Document_Window_HTML._\051-expected.txt"
index 3b82a104..819e356 100644
--- "a/third_party/blink/web_tests/external/wpt/html/dom/idlharness.https_exclude=\050Document_Window_HTML._\051-expected.txt"
+++ "b/third_party/blink/web_tests/external/wpt/html/dom/idlharness.https_exclude=\050Document_Window_HTML._\051-expected.txt"
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 1397 tests; 1354 PASS, 43 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 1397 tests; 1357 PASS, 40 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idl_test setup
 PASS idl_test validation
 PASS Partial interface Document: original interface defined
@@ -715,9 +715,9 @@
 PASS OffscreenCanvasRenderingContext2D interface: operation rotate(unrestricted double)
 PASS OffscreenCanvasRenderingContext2D interface: operation translate(unrestricted double, unrestricted double)
 PASS OffscreenCanvasRenderingContext2D interface: operation transform(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double)
-FAIL OffscreenCanvasRenderingContext2D interface: operation getTransform() assert_own_property: interface prototype object missing non-static operation expected property "getTransform" missing
-FAIL OffscreenCanvasRenderingContext2D interface: operation setTransform(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double) assert_equals: property has wrong .length expected 0 but got 6
-FAIL OffscreenCanvasRenderingContext2D interface: operation setTransform(DOMMatrix2DInit) assert_equals: property has wrong .length expected 0 but got 6
+PASS OffscreenCanvasRenderingContext2D interface: operation getTransform()
+PASS OffscreenCanvasRenderingContext2D interface: operation setTransform(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double)
+PASS OffscreenCanvasRenderingContext2D interface: operation setTransform(DOMMatrix2DInit)
 PASS OffscreenCanvasRenderingContext2D interface: operation resetTransform()
 PASS OffscreenCanvasRenderingContext2D interface: attribute globalAlpha
 PASS OffscreenCanvasRenderingContext2D interface: attribute globalCompositeOperation
diff --git a/third_party/blink/web_tests/external/wpt/html/dom/idlharness.worker-expected.txt b/third_party/blink/web_tests/external/wpt/html/dom/idlharness.worker-expected.txt
index 544c738..88f80ee 100644
--- a/third_party/blink/web_tests/external/wpt/html/dom/idlharness.worker-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/html/dom/idlharness.worker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 790 tests; 770 PASS, 20 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 790 tests; 773 PASS, 17 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idl_test setup
 PASS idl_test validation
 PASS Partial interface Document: original interface defined
@@ -339,9 +339,9 @@
 PASS OffscreenCanvasRenderingContext2D interface: operation rotate(unrestricted double)
 PASS OffscreenCanvasRenderingContext2D interface: operation translate(unrestricted double, unrestricted double)
 PASS OffscreenCanvasRenderingContext2D interface: operation transform(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double)
-FAIL OffscreenCanvasRenderingContext2D interface: operation getTransform() assert_own_property: interface prototype object missing non-static operation expected property "getTransform" missing
-FAIL OffscreenCanvasRenderingContext2D interface: operation setTransform(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double) assert_equals: property has wrong .length expected 0 but got 6
-FAIL OffscreenCanvasRenderingContext2D interface: operation setTransform(DOMMatrix2DInit) assert_equals: property has wrong .length expected 0 but got 6
+PASS OffscreenCanvasRenderingContext2D interface: operation getTransform()
+PASS OffscreenCanvasRenderingContext2D interface: operation setTransform(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double)
+PASS OffscreenCanvasRenderingContext2D interface: operation setTransform(DOMMatrix2DInit)
 PASS OffscreenCanvasRenderingContext2D interface: operation resetTransform()
 PASS OffscreenCanvasRenderingContext2D interface: attribute globalAlpha
 PASS OffscreenCanvasRenderingContext2D interface: attribute globalCompositeOperation
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/conformance-requirements/2d.missingargs.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/conformance-requirements/2d.missingargs.html
index 9aa437c..6715257 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/conformance-requirements/2d.missingargs.html
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/conformance-requirements/2d.missingargs.html
@@ -30,7 +30,6 @@
     assert_throws(new TypeError(), function() { ctx.transform(1, 0, 0, 1, 0); });
 }
 if (ctx.setTransform) {
-    assert_throws(new TypeError(), function() { ctx.setTransform(); });
     assert_throws(new TypeError(), function() { ctx.setTransform(1); });
     assert_throws(new TypeError(), function() { ctx.setTransform(1, 0); });
     assert_throws(new TypeError(), function() { ctx.setTransform(1, 0, 0); });
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/conformance-requirements/2d.missingargs.worker.js b/third_party/blink/web_tests/external/wpt/offscreen-canvas/conformance-requirements/2d.missingargs.worker.js
index 8f8d00e..755e881e 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/conformance-requirements/2d.missingargs.worker.js
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/conformance-requirements/2d.missingargs.worker.js
@@ -26,7 +26,6 @@
     assert_throws(new TypeError(), function() { ctx.transform(1, 0, 0, 1, 0); });
 }
 if (ctx.setTransform) {
-    assert_throws(new TypeError(), function() { ctx.setTransform(); });
     assert_throws(new TypeError(), function() { ctx.setTransform(1); });
     assert_throws(new TypeError(), function() { ctx.setTransform(1, 0); });
     assert_throws(new TypeError(), function() { ctx.setTransform(1, 0, 0); });
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/transformations/2d.transformation.getTransform.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/transformations/2d.transformation.getTransform.html
new file mode 100644
index 0000000..b3b70ac
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/transformations/2d.transformation.getTransform.html
@@ -0,0 +1,40 @@
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+// Ensure that context2d.getTransform works
+const epsilon = 1e-5;
+const canvas = new OffscreenCanvas(300, 150);
+const ctx = canvas.getContext('2d');
+
+test(function(t) {
+
+  assert_array_equals(ctx.getTransform().toFloat32Array(),
+    [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
+    "Assert that an untransformed matrix is identity");
+
+  ctx.scale(2, 3);
+  transform = ctx.getTransform();
+  assert_array_equals(ctx.getTransform().toFloat32Array(),
+    [2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
+    "Assert that context2d scaling works");
+
+  ctx.rotate(Math.PI/2);
+  transform = ctx.getTransform();
+  assert_array_approx_equals(ctx.getTransform().toFloat32Array(),
+    [0, 3, 0, 0, -2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], epsilon,
+    "Assert that context2d rotate works");
+
+  ctx.translate(1, -1);
+  transform = ctx.getTransform();
+  assert_array_approx_equals(ctx.getTransform().toFloat32Array(),
+    [0, 3, 0, 0, -2, 0, 0, 0, 0, 0, 1, 0, 2, 3, 0, 1], epsilon,
+    "Assert context2d translate works.");
+
+  ctx.resetTransform();
+  assert_array_equals(ctx.getTransform().toFloat32Array(),
+    [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
+    "Assert that a reset matrix is identity");
+}, 'This test ensures that getTransform works correctly.');
+</script>
+</body>
diff --git a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index 12996e81..c8e88fbe 100644
--- a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -990,6 +990,7 @@
     method fillText
     method getImageData
     method getLineDash
+    method getTransform
     method isPointInPath
     method isPointInStroke
     method lineTo
diff --git a/third_party/blink/web_tests/inspector-protocol/timeline/timeline-layout.js b/third_party/blink/web_tests/inspector-protocol/timeline/timeline-layout.js
index 9507a16..d9dc48c 100644
--- a/third_party/blink/web_tests/inspector-protocol/timeline/timeline-layout.js
+++ b/third_party/blink/web_tests/inspector-protocol/timeline/timeline-layout.js
@@ -21,21 +21,23 @@
   await tracingHelper.invokeAsyncWithTracing(performActions);
 
   var schedRecalc = tracingHelper.findEvent('ScheduleStyleRecalculation', 'I');
-  var recalc = tracingHelper.findEvent('UpdateLayoutTree', 'X');
-  testRunner.log('UpdateLayoutTree frames match: ' + (schedRecalc.args.data.frame === recalc.args.beginData.frame));
-  testRunner.log('UpdateLayoutTree elementCount > 0: ' + (recalc.args.elementCount > 0));
+  var recalcBegin = tracingHelper.findEvent('UpdateLayoutTree', 'B');
+  var recalcEnd = tracingHelper.findEvent('UpdateLayoutTree', 'E');
+  testRunner.log('UpdateLayoutTree frames match: ' + (schedRecalc.args.data.frame === recalcBegin.args.beginData.frame));
+  testRunner.log('UpdateLayoutTree elementCount > 0: ' + (recalcEnd.args.elementCount > 0));
 
   var invalidate = tracingHelper.findEvent('InvalidateLayout', 'I');
-  var layout = tracingHelper.findEvent('Layout', 'X');
+  var layoutBegin = tracingHelper.findEvent('Layout', 'B');
+  var layoutEnd = tracingHelper.findEvent('Layout', 'E');
 
-  testRunner.log('InvalidateLayout frames match: ' + (recalc.args.beginData.frame === invalidate.args.data.frame));
+  testRunner.log('InvalidateLayout frames match: ' + (recalcBegin.args.beginData.frame === invalidate.args.data.frame));
 
-  var beginData = layout.args.beginData;
+  var beginData = layoutBegin.args.beginData;
   testRunner.log('Layout frames match: ' + (invalidate.args.data.frame === beginData.frame));
   testRunner.log('dirtyObjects > 0: ' + (beginData.dirtyObjects > 0));
   testRunner.log('totalObjects > 0: ' + (beginData.totalObjects > 0));
 
-  var endData = layout.args.endData;
+  var endData = layoutEnd.args.endData;
   testRunner.log('has rootNode id: ' + (endData.rootNode > 0));
   testRunner.log('has root quad: ' + !!endData.root);
 
diff --git a/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index e9c65a5a..6b37168b 100644
--- a/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -821,6 +821,7 @@
     method fillText
     method getImageData
     method getLineDash
+    method getTransform
     method isPointInPath
     method isPointInStroke
     method lineTo
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
index c2efd05..4f6b1bb 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -773,6 +773,7 @@
 [Worker]     method fillText
 [Worker]     method getImageData
 [Worker]     method getLineDash
+[Worker]     method getTransform
 [Worker]     method isPointInPath
 [Worker]     method isPointInStroke
 [Worker]     method lineTo
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 2365233..dc5bc65ec 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
@@ -4509,6 +4509,7 @@
     method fillText
     method getImageData
     method getLineDash
+    method getTransform
     method isPointInPath
     method isPointInStroke
     method lineTo
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
index 84ada2d..33d2887 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
@@ -768,6 +768,7 @@
 [Worker]     method fillText
 [Worker]     method getImageData
 [Worker]     method getLineDash
+[Worker]     method getTransform
 [Worker]     method isPointInPath
 [Worker]     method isPointInStroke
 [Worker]     method lineTo
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
index 37974b2..d85b359 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -956,6 +956,7 @@
 [Worker]     method fillText
 [Worker]     method getImageData
 [Worker]     method getLineDash
+[Worker]     method getTransform
 [Worker]     method isPointInPath
 [Worker]     method isPointInStroke
 [Worker]     method lineTo
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index b1d280a4..c339c82 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -5395,6 +5395,7 @@
     method fillText
     method getImageData
     method getLineDash
+    method getTransform
     method isPointInPath
     method isPointInStroke
     method lineTo
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
index f9e3191..3860279 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
@@ -938,6 +938,7 @@
 [Worker]     method fillText
 [Worker]     method getImageData
 [Worker]     method getLineDash
+[Worker]     method getTransform
 [Worker]     method isPointInPath
 [Worker]     method isPointInStroke
 [Worker]     method lineTo
diff --git a/third_party/webxr_test_pages/.gitignore b/third_party/webxr_test_pages/.gitignore
index c59768b..3a52d57 100644
--- a/third_party/webxr_test_pages/.gitignore
+++ b/third_party/webxr_test_pages/.gitignore
@@ -1 +1,2 @@
-media/
\ No newline at end of file
+media/
+webxr-samples-staging/
diff --git a/third_party/webxr_test_pages/webxr-samples/index.published.html b/third_party/webxr_test_pages/webxr-samples/index.published.html
index 3ed2391..524418e 100644
--- a/third_party/webxr_test_pages/webxr-samples/index.published.html
+++ b/third_party/webxr_test_pages/webxr-samples/index.published.html
@@ -87,8 +87,11 @@
       </header>
 
       <main class='main' id='main'>
-        <p>Sample pages demonstrating how to use various aspects of the WebXR API <b><em>as implemented in Chrome versions 76-77.</b></em><br/>
-           Note: these samples will only work when visiting this site securely (https://).</p>
+        <p>
+          <b><em>THESE SAMPLES ARE DEPRECATED - DO NOT USE WITH CHROME 78+</b></em>
+          <br/>
+           For Chrome 78 and later, please use <a href="https://immersive-web.github.io/webxr-samples">these samples</a>, maintained by the Immersive Web Working Group.
+        </p>
 
         <script>
           let pages = [
diff --git a/tools/gritsettings/resource_ids b/tools/gritsettings/resource_ids
index bfb9443..b338915 100644
--- a/tools/gritsettings/resource_ids
+++ b/tools/gritsettings/resource_ids
@@ -83,22 +83,28 @@
     "includes": [11000],
     "structures": [11800],
   },
+  "chrome/browser/resources/bookmarks/bookmarks_resources.grd": {
+    "structures": [11830],
+  },
+  "chrome/browser/resources/bookmarks/bookmarks_resources_vulcanized.grd": {
+    "structures": [11880],
+  },
   "chrome/browser/resources/chromeos/camera/camera_resources.grd": {
-    "includes": [11870],
-    "structures": [11930],
+    "includes": [11890],
+    "structures": [11950],
   },
   "chrome/browser/resources/chromeos/camera/src/strings/camera_strings.grd": {
-    "messages": [11970],
+    "messages": [12000],
   },
   "chrome/browser/resources/chromeos/cellular_setup/cellular_setup_resources.grd": {
-    "structures": [12040],
+    "structures": [12080],
   },
   "chrome/browser/resources/chromeos/multidevice_setup/multidevice_setup_resources.grd": {
-    "structures": [12050],
+    "structures": [12090],
   },
   "chrome/browser/resources/component_extension_resources.grd": {
-    "includes": [12060],
-    "structures": [12200],
+    "includes": [12100],
+    "structures": [12230],
   },
   "chrome/browser/resources/downloads/downloads_resources_vulcanized.grd": {
     "includes": [12240],
@@ -342,7 +348,7 @@
     "messages": [23125],
   },
   "ash/components/ash_components_strings.grd": {
-    "messages": [23740],
+    "messages": [23800],
   },
   "ash/keyboard/ui/keyboard_resources.grd": {
     "includes": [23960],
diff --git a/tools/measure_page_load_time/ff_ext/chrome.manifest b/tools/measure_page_load_time/ff_ext/chrome.manifest
deleted file mode 100644
index 9e1d73d4..0000000
--- a/tools/measure_page_load_time/ff_ext/chrome.manifest
+++ /dev/null
@@ -1,2 +0,0 @@
-content	measurepageloadtimeextension	content/

-overlay	chrome://browser/content/browser.xul	chrome://measurepageloadtimeextension/content/firefoxOverlay.xul

diff --git a/tools/measure_page_load_time/ff_ext/content/firefoxOverlay.xul b/tools/measure_page_load_time/ff_ext/content/firefoxOverlay.xul
deleted file mode 100644
index 1c5529ea..0000000
--- a/tools/measure_page_load_time/ff_ext/content/firefoxOverlay.xul
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>

-<?xml-stylesheet href="chrome://measurepageloadtimeextension/skin/overlay.css" type="text/css"?>

-<!DOCTYPE overlay SYSTEM "chrome://measurepageloadtimeextension/locale/measurepageloadtimeextension.dtd">

-<overlay id="measurepageloadtimeextension-overlay"

-         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

-  <script src="measure_page_load_time.js"/>

-</overlay>

diff --git a/tools/measure_page_load_time/ff_ext/content/measure_page_load_time.js b/tools/measure_page_load_time/ff_ext/content/measure_page_load_time.js
deleted file mode 100644
index 44473cc..0000000
--- a/tools/measure_page_load_time/ff_ext/content/measure_page_load_time.js
+++ /dev/null
@@ -1,209 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview measure_page_load_time.js implements a Firefox extension
- * for measuring how long a page takes to load. It waits on TCP port
- * 42492 for connections, then accepts URLs and returns strings of the
- * form url,time, where "time" is the load time in milliseconds or the
- * string "timeout" or "error". Load time is measured from the call to
- * loadURI until the load event fires, or until the status changes to
- * STATUS_STOP if the load event doesn't fire (there's an error.)
- * @author jhaas@google.com (Jonathan Haas) */
-
-// Shorthand reference to nsIWebProgress[Listener] interfaces
-var IWP  = Components.interfaces.nsIWebProgress;
-var IWPL = Components.interfaces.nsIWebProgressListener;
-    
-
-var MPLT = {
-  /**
-   * Constants
-   */
-  PORT_NUMBER : 42492,      // port to listen for connections on
-  TIME_OUT : 4 * 60 * 1000, // timeout in 4 minutes
-
-  /**
-   * Incoming URL buffer
-   * @type {string}
-   */
-  textBuffer : '',
-  
-  /**
-   * URL we're currently visiting
-   * @type {string}
-   */
-  URL : '',
-  
-  /**
-   * Listener to accept incoming connections
-   * @type {nsIServerSocketListener}
-   */
-  acceptListener :
-  {
-    onSocketAccepted : function(serverSocket, transport)
-    {
-      MPLT.streamInput  = transport.openInputStream(0,0,0);
-      MPLT.streamOutput = transport.openOutputStream(0,0,0);
-      
-      MPLT.scriptStream = Components.classes['@mozilla.org/scriptableinputstream;1']
-        .createInstance(Components.interfaces.nsIScriptableInputStream);
-      MPLT.scriptStream.init(MPLT.streamInput);
-      MPLT.pump = Components.classes['@mozilla.org/network/input-stream-pump;1']
-        .createInstance(Components.interfaces.nsIInputStreamPump);
-      MPLT.pump.init(MPLT.streamInput, -1, -1, 0, 0, false);
-      MPLT.pump.asyncRead(MPLT.dataListener,null);    
-    },
-
-    onStopListening : function(){}
-  },
-
-  /**
-   * Listener for network input
-   * @type {nsIStreamListener}
-   */
-  dataListener : 
-  {
-    onStartRequest: function(){},
-    onStopRequest: function(){},
-    onDataAvailable: function(request, context, inputStream, offset, count){
-      // Add the received data to the buffer, then process it
-      // Change CRLF to newline while we're at it
-      MPLT.textBuffer += MPLT.scriptStream.read(count).replace('\r\n', '\n');
-      
-      MPLT.process();
-    }
-  },
-  
-  /**
-   * Process the incoming data buffer
-   */
-  process : function()
-  {
-    // If we're waiting for a page to finish loading, wait
-    if (MPLT.timeLoadStarted)
-      return;
-    
-    // Look for a carriage return
-    var firstCR = MPLT.textBuffer.indexOf('\n');
-    
-    // If we haven't received a carriage return yet, wait
-    if (firstCR < 0) 
-      return;
-      
-    // If the first character was a carriage return, we're done!
-    if (firstCR == 0) {
-      MPLT.textBuffer = '';
-      MPLT.streamInput.close();
-      MPLT.streamOutput.close();
-      
-      return;
-    }
-    
-    // Remove the URL from the buffer
-    MPLT.URL = MPLT.textBuffer.substr(0, firstCR);
-    MPLT.textBuffer = MPLT.textBuffer.substr(firstCR + 1);
-
-    // Remember the current time and navigate to the new URL    
-    MPLT.timeLoadStarted = new Date();
-    gBrowser.loadURIWithFlags(MPLT.URL, gBrowser.LOAD_FLAGS_BYPASS_CACHE);
-    setTimeout('MPLT.onTimeOut()', MPLT.TIME_OUT);
-  },
-  
-  /**
-   * Page load completion handler
-   */
-  onPageLoad : function(e) {
-    // Ignore loads of non-HTML documents
-    if (!(e.originalTarget instanceof HTMLDocument))
-      return;
-    
-    // Also ignore subframe loads
-    if (e.originalTarget.defaultView.frameElement)
-      return;
-      
-    clearTimeout();
-    var timeElapsed = new Date() - MPLT.timeLoadStarted;
-    
-    MPLT.outputResult(timeElapsed);
-  },
-  
-  /**
-   * Timeout handler
-   */
-  onTimeOut : function() {
-    gBrowser.stop();
-    
-    MPLT.outputResult('timeout');
-  },
-  
-  
-  /**
-   * Sends a properly-formatted result to the client
-   * @param {string} result  The value to send along with the URL
-   */
-  outputResult : function(result) {
-
-    if (MPLT.URL) {
-      var outputString = MPLT.URL + ',' + result + '\n';
-      MPLT.streamOutput.write(outputString, outputString.length);
-      MPLT.URL = '';
-    }
-    
-    MPLT.timeLoadStarted = null;
-    MPLT.process();
-  },
-
-  /**
-   * Time the page load started. If null, we're waiting for the
-   * initial page load, or otherwise don't care about the page
-   * that's currently loading
-   * @type {number}
-   */
-  timeLoadStarted : null,
-
-  /*
-   * TODO(jhaas): add support for nsIWebProgressListener
-   * If the URL being visited died as part of a network error
-   * (host not found, connection reset by peer, etc), the onload
-   * event doesn't fire. The only way to catch it would be in
-   * a web progress listener. However, nsIWebProgress is not
-   * behaving according to documentation. More research is needed.
-   * For now, omitting it means that if any of our URLs are "dirty" 
-   * (do not point to real web servers with real responses), we'll log 
-   * them as timeouts. This doesn't affect pages where the server
-   * exists but returns an error code.
-   */
-     
-  /**
-   * Initialize the plugin, create the socket and listen
-   */
-  initialize: function() {
-    // Register for page load events
-    gBrowser.addEventListener('load', this.onPageLoad, true);
-    
-    // Set a timeout to wait for the initial page to load
-    MPLT.timeLoadStarted = new Date();
-    setTimeout('MPLT.onTimeOut()', MPLT.TIME_OUT);
-    
-    // Create the listening socket
-    MPLT.serverSocket = Components.classes['@mozilla.org/network/server-socket;1']
-                         .createInstance(Components.interfaces.nsIServerSocket);
-
-    MPLT.serverSocket.init(MPLT.PORT_NUMBER, true, 1);
-    MPLT.serverSocket.asyncListen(this.acceptListener);  
-  },
-
-  /**
-   * Close the socket(s)
-   */
-  deinitialize: function() {
-    if (MPLT.streamInput)  MPLT.streamInput.close();
-    if (MPLT.streamOutput) MPLT.streamOutput.close();
-    if (MPLT.serverSocket) MPLT.serverSocket.close();
-  }
-};
-
-window.addEventListener('load', function(e) { MPLT.initialize(); }, false);
-window.addEventListener('unload', function(e) { MPLT.deinitialize(); }, false);
diff --git a/tools/measure_page_load_time/ff_ext/install.rdf b/tools/measure_page_load_time/ff_ext/install.rdf
deleted file mode 100644
index c79d24e..0000000
--- a/tools/measure_page_load_time/ff_ext/install.rdf
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>

-<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 

- xmlns:em="http://www.mozilla.org/2004/em-rdf#">

-  <Description about="urn:mozilla:install-manifest">

-    <em:id>measurepageloadtimeextension@google.com</em:id>

-    <em:name>MeasurePageLoadTime</em:name>

-    <em:version>1.0</em:version>

-    <em:creator>Jonathan Haas</em:creator>

-    <em:targetApplication>

-      <Description>

-        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <!-- firefox -->

-        <em:minVersion>1.5</em:minVersion>

-        <em:maxVersion>3.0.*</em:maxVersion>

-      </Description>

-    </em:targetApplication>

-  </Description>

-</RDF>

diff --git a/tools/measure_page_load_time/ie_bho/MeasurePageLoadTime.cpp b/tools/measure_page_load_time/ie_bho/MeasurePageLoadTime.cpp
deleted file mode 100644
index d3a8d54..0000000
--- a/tools/measure_page_load_time/ie_bho/MeasurePageLoadTime.cpp
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// MeasurePageLoadTime.cpp : Implementation of DLL Exports.
-
-#include "stdafx.h"
-#include "resource.h"
-#include "MeasurePageLoadTime.h"
-
-
-class CMeasurePageLoadTimeModule : public CAtlDllModuleT< CMeasurePageLoadTimeModule >
-{
-public :
-	DECLARE_LIBID(LIBID_MeasurePageLoadTimeLib)
-	DECLARE_REGISTRY_APPID_RESOURCEID(IDR_MEASUREPAGELOADTIME, "{56C6D9F9-643C-4F6E-906C-5F7CECB23C24}")
-};
-
-CMeasurePageLoadTimeModule _AtlModule;
-
-
-#ifdef _MANAGED
-#pragma managed(push, off)
-#endif
-
-// DLL Entry Point
-extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
-{
-    if (dwReason == DLL_PROCESS_ATTACH)
-    {
-        DisableThreadLibraryCalls(hInstance);
-    }
-    return _AtlModule.DllMain(dwReason, lpReserved);
-}
-
-#ifdef _MANAGED
-#pragma managed(pop)
-#endif
-
-
-
-
-// Used to determine whether the DLL can be unloaded by OLE
-STDAPI DllCanUnloadNow(void)
-{
-    return _AtlModule.DllCanUnloadNow();
-}
-
-
-// Returns a class factory to create an object of the requested type
-STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
-{
-    return _AtlModule.DllGetClassObject(rclsid, riid, ppv);
-}
-
-
-// DllRegisterServer - Adds entries to the system registry
-STDAPI DllRegisterServer(void)
-{
-    // registers object, typelib and all interfaces in typelib
-    HRESULT hr = _AtlModule.DllRegisterServer();
-	return hr;
-}
-
-
-// DllUnregisterServer - Removes entries from the system registry
-STDAPI DllUnregisterServer(void)
-{
-	HRESULT hr = _AtlModule.DllUnregisterServer();
-	return hr;
-}
-
diff --git a/tools/measure_page_load_time/ie_bho/MeasurePageLoadTime.def b/tools/measure_page_load_time/ie_bho/MeasurePageLoadTime.def
deleted file mode 100644
index 55529233..0000000
--- a/tools/measure_page_load_time/ie_bho/MeasurePageLoadTime.def
+++ /dev/null
@@ -1,9 +0,0 @@
-; MeasurePageLoadTime.def : Declares the module parameters.
-
-LIBRARY      "MeasurePageLoadTime.DLL"
-
-EXPORTS
-	DllCanUnloadNow		PRIVATE
-	DllGetClassObject	PRIVATE
-	DllRegisterServer	PRIVATE
-	DllUnregisterServer	PRIVATE
diff --git a/tools/measure_page_load_time/ie_bho/MeasurePageLoadTime.idl b/tools/measure_page_load_time/ie_bho/MeasurePageLoadTime.idl
deleted file mode 100644
index d2f98da3..0000000
--- a/tools/measure_page_load_time/ie_bho/MeasurePageLoadTime.idl
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// MeasurePageLoadTime.idl : IDL source for MeasurePageLoadTime
-//
-
-// This file will be processed by the MIDL tool to
-// produce the type library (MeasurePageLoadTime.tlb) and marshalling code.
-
-import "oaidl.idl";
-import "ocidl.idl";
-
-[
-	object,
-	uuid(019637EB-B865-485B-9A66-419477EE55A0),
-	dual,
-	nonextensible,
-	helpstring("IMeasurePageLoadTimeBHO Interface"),
-	pointer_default(unique)
-]
-interface IMeasurePageLoadTimeBHO : IDispatch{
-};
-[
-	uuid(61AC7AC4-B715-4955-A238-5F9AEA80DF4B),
-	version(1.0),
-	helpstring("MeasurePageLoadTime 1.0 Type Library")
-]
-library MeasurePageLoadTimeLib
-{
-	importlib("stdole2.tlb");
-	[
-		uuid(807E68BC-238F-4163-AE4B-0A3604F3E145),
-		helpstring("MeasurePageLoadTimeBHO Class")
-	]
-	coclass MeasurePageLoadTimeBHO
-	{
-		[default] interface IMeasurePageLoadTimeBHO;
-	};
-};
diff --git a/tools/measure_page_load_time/ie_bho/MeasurePageLoadTime.rc b/tools/measure_page_load_time/ie_bho/MeasurePageLoadTime.rc
deleted file mode 100644
index 9285a705..0000000
--- a/tools/measure_page_load_time/ie_bho/MeasurePageLoadTime.rc
+++ /dev/null
@@ -1,121 +0,0 @@
-// Microsoft Visual C++ generated resource script.
-//
-#include "resource.h"
-
-#define APSTUDIO_READONLY_SYMBOLS
-/////////////////////////////////////////////////////////////////////////////
-//
-// Generated from the TEXTINCLUDE 2 resource.
-//
-#include "winres.h"
-
-/////////////////////////////////////////////////////////////////////////////
-#undef APSTUDIO_READONLY_SYMBOLS
-
-/////////////////////////////////////////////////////////////////////////////
-// English (U.S.) resources
-
-#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
-#ifdef _WIN32
-LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
-#pragma code_page(1252)
-#endif //_WIN32
-
-#ifdef APSTUDIO_INVOKED
-/////////////////////////////////////////////////////////////////////////////
-//
-// TEXTINCLUDE
-//
-
-1 TEXTINCLUDE 
-BEGIN
-    "resource.h\0"
-END
-
-2 TEXTINCLUDE 
-BEGIN
-    "#include ""winres.h""\r\n"
-    "\0"
-END
-
-3 TEXTINCLUDE 
-BEGIN
-    "1 TYPELIB ""MeasurePageLoadTime.tlb""\r\n"
-    "\0"
-END
-
-#endif    // APSTUDIO_INVOKED
-
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// Version
-//
-
-VS_VERSION_INFO VERSIONINFO
- FILEVERSION 1,0,0,1
- PRODUCTVERSION 1,0,0,1
- FILEFLAGSMASK 0x3fL
-#ifdef _DEBUG
- FILEFLAGS 0x1L
-#else
- FILEFLAGS 0x0L
-#endif
- FILEOS 0x4L
- FILETYPE 0x2L
- FILESUBTYPE 0x0L
-BEGIN
-    BLOCK "StringFileInfo"
-    BEGIN
-        BLOCK "040904e4"
-        BEGIN
-            VALUE "CompanyName", "Google"
-            VALUE "FileDescription", "Measures page load times"
-            VALUE "FileVersion", "1.0.0.1"
-            VALUE "LegalCopyright", "(c) 2008 Google.  All rights reserved."
-            VALUE "InternalName", "MeasurePageLoadTime.dll"
-            VALUE "OriginalFilename", "MeasurePageLoadTime.dll"
-            VALUE "ProductName", "MeasurePageLoadTime"
-            VALUE "ProductVersion", "1.0.0.1"
-        END
-    END
-    BLOCK "VarFileInfo"
-    BEGIN
-        VALUE "Translation", 0x409, 1252
-    END
-END
-
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// REGISTRY
-//
-
-IDR_MEASUREPAGELOADTIME REGISTRY                "MeasurePageLoadTime.rgs"
-IDR_MEASUREPAGELOADTIMEBHO REGISTRY                "MeasurePageLoadTimeBHO.rgs"
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// String Table
-//
-
-STRINGTABLE 
-BEGIN
-    IDS_PROJNAME            "MeasurePageLoadTime"
-END
-
-#endif    // English (U.S.) resources
-/////////////////////////////////////////////////////////////////////////////
-
-
-
-#ifndef APSTUDIO_INVOKED
-/////////////////////////////////////////////////////////////////////////////
-//
-// Generated from the TEXTINCLUDE 3 resource.
-//
-1 TYPELIB "MeasurePageLoadTime.tlb"
-
-/////////////////////////////////////////////////////////////////////////////
-#endif    // not APSTUDIO_INVOKED
-
diff --git a/tools/measure_page_load_time/ie_bho/MeasurePageLoadTime.rgs b/tools/measure_page_load_time/ie_bho/MeasurePageLoadTime.rgs
deleted file mode 100644
index 98e7f783..0000000
--- a/tools/measure_page_load_time/ie_bho/MeasurePageLoadTime.rgs
+++ /dev/null
@@ -1,29 +0,0 @@
-HKCR

-{

-	NoRemove AppID

-	{

-		'%APPID%' = s 'MeasurePageLoadTime'

-		'MeasurePageLoadTime.DLL'

-		{

-			val AppID = s '%APPID%'

-		}

-	}

-}

-

-HKLM {

-  NoRemove SOFTWARE {

-    NoRemove Microsoft {   

-      NoRemove Windows {

-        NoRemove CurrentVersion {

-          NoRemove Explorer {

-            NoRemove 'Browser Helper Objects' {

-              ForceRemove '{807E68BC-238F-4163-AE4B-0A3604F3E145}' = s 'MeasurePageLoadTimeBHO' {

-                val 'NoExplorer' = d '1'

-              }

-            }

-          }

-        }

-      }

-    }

-  }

-}

diff --git a/tools/measure_page_load_time/ie_bho/MeasurePageLoadTime.vcproj b/tools/measure_page_load_time/ie_bho/MeasurePageLoadTime.vcproj
deleted file mode 100644
index 9ed8327..0000000
--- a/tools/measure_page_load_time/ie_bho/MeasurePageLoadTime.vcproj
+++ /dev/null
@@ -1,320 +0,0 @@
-<?xml version="1.0" encoding="Windows-1252"?>
-<VisualStudioProject
-	ProjectType="Visual C++"
-	Version="8.00"
-	Name="MeasurePageLoadTime"
-	ProjectGUID="{151243DF-25BE-4A88-B566-8B7AE8970E86}"
-	RootNamespace="MeasurePageLoadTime"
-	Keyword="AtlProj"
-	>
-	<Platforms>
-		<Platform
-			Name="Win32"
-		/>
-	</Platforms>
-	<ToolFiles>
-	</ToolFiles>
-	<Configurations>
-		<Configuration
-			Name="Debug|Win32"
-			OutputDirectory="$(ConfigurationName)"
-			IntermediateDirectory="$(ConfigurationName)"
-			ConfigurationType="2"
-			UseOfATL="2"
-			ATLMinimizesCRunTimeLibraryUsage="false"
-			CharacterSet="1"
-			>
-			<Tool
-				Name="VCPreBuildEventTool"
-			/>
-			<Tool
-				Name="VCCustomBuildTool"
-			/>
-			<Tool
-				Name="VCXMLDataGeneratorTool"
-			/>
-			<Tool
-				Name="VCWebServiceProxyGeneratorTool"
-			/>
-			<Tool
-				Name="VCMIDLTool"
-				PreprocessorDefinitions="_DEBUG"
-				MkTypLibCompatible="false"
-				TargetEnvironment="1"
-				GenerateStublessProxies="true"
-				TypeLibraryName="$(IntDir)/MeasurePageLoadTime.tlb"
-				HeaderFileName="MeasurePageLoadTime.h"
-				DLLDataFileName=""
-				InterfaceIdentifierFileName="MeasurePageLoadTime_i.c"
-				ProxyFileName="MeasurePageLoadTime_p.c"
-				ValidateParameters="false"
-			/>
-			<Tool
-				Name="VCCLCompilerTool"
-				Optimization="0"
-				PreprocessorDefinitions="WIN32;_WINDOWS;_DEBUG;_USRDLL"
-				MinimalRebuild="true"
-				BasicRuntimeChecks="3"
-				RuntimeLibrary="3"
-				UsePrecompiledHeader="2"
-				WarningLevel="3"
-				Detect64BitPortabilityProblems="true"
-				DebugInformationFormat="4"
-			/>
-			<Tool
-				Name="VCManagedResourceCompilerTool"
-			/>
-			<Tool
-				Name="VCResourceCompilerTool"
-				PreprocessorDefinitions="_DEBUG"
-				Culture="1033"
-				AdditionalIncludeDirectories="$(IntDir)"
-			/>
-			<Tool
-				Name="VCPreLinkEventTool"
-			/>
-			<Tool
-				Name="VCLinkerTool"
-				RegisterOutput="true"
-				IgnoreImportLibrary="true"
-				AdditionalDependencies="ws2_32.lib"
-				LinkIncremental="2"
-				ModuleDefinitionFile=".\MeasurePageLoadTime.def"
-				GenerateDebugInformation="true"
-				SubSystem="2"
-				TargetMachine="1"
-			/>
-			<Tool
-				Name="VCALinkTool"
-			/>
-			<Tool
-				Name="VCManifestTool"
-			/>
-			<Tool
-				Name="VCXDCMakeTool"
-			/>
-			<Tool
-				Name="VCBscMakeTool"
-			/>
-			<Tool
-				Name="VCFxCopTool"
-			/>
-			<Tool
-				Name="VCAppVerifierTool"
-			/>
-			<Tool
-				Name="VCWebDeploymentTool"
-			/>
-			<Tool
-				Name="VCPostBuildEventTool"
-			/>
-		</Configuration>
-		<Configuration
-			Name="Release|Win32"
-			OutputDirectory="$(ConfigurationName)"
-			IntermediateDirectory="$(ConfigurationName)"
-			ConfigurationType="2"
-			UseOfATL="1"
-			ATLMinimizesCRunTimeLibraryUsage="false"
-			CharacterSet="1"
-			>
-			<Tool
-				Name="VCPreBuildEventTool"
-			/>
-			<Tool
-				Name="VCCustomBuildTool"
-			/>
-			<Tool
-				Name="VCXMLDataGeneratorTool"
-			/>
-			<Tool
-				Name="VCWebServiceProxyGeneratorTool"
-			/>
-			<Tool
-				Name="VCMIDLTool"
-				PreprocessorDefinitions="NDEBUG"
-				MkTypLibCompatible="false"
-				TargetEnvironment="1"
-				GenerateStublessProxies="true"
-				TypeLibraryName="$(IntDir)/MeasurePageLoadTime.tlb"
-				HeaderFileName="MeasurePageLoadTime.h"
-				DLLDataFileName=""
-				InterfaceIdentifierFileName="MeasurePageLoadTime_i.c"
-				ProxyFileName="MeasurePageLoadTime_p.c"
-				ValidateParameters="false"
-			/>
-			<Tool
-				Name="VCCLCompilerTool"
-				Optimization="2"
-				PreprocessorDefinitions="WIN32;_WINDOWS;NDEBUG;_USRDLL"
-				RuntimeLibrary="0"
-				UsePrecompiledHeader="2"
-				WarningLevel="3"
-				Detect64BitPortabilityProblems="true"
-				DebugInformationFormat="3"
-			/>
-			<Tool
-				Name="VCManagedResourceCompilerTool"
-			/>
-			<Tool
-				Name="VCResourceCompilerTool"
-				PreprocessorDefinitions="NDEBUG"
-				Culture="1033"
-				AdditionalIncludeDirectories="$(IntDir)"
-			/>
-			<Tool
-				Name="VCPreLinkEventTool"
-			/>
-			<Tool
-				Name="VCLinkerTool"
-				RegisterOutput="true"
-				IgnoreImportLibrary="true"
-				AdditionalDependencies="ws2_32.lib"
-				LinkIncremental="1"
-				ModuleDefinitionFile=".\MeasurePageLoadTime.def"
-				GenerateDebugInformation="true"
-				SubSystem="2"
-				OptimizeReferences="2"
-				EnableCOMDATFolding="2"
-				TargetMachine="1"
-			/>
-			<Tool
-				Name="VCALinkTool"
-			/>
-			<Tool
-				Name="VCManifestTool"
-			/>
-			<Tool
-				Name="VCXDCMakeTool"
-			/>
-			<Tool
-				Name="VCBscMakeTool"
-			/>
-			<Tool
-				Name="VCFxCopTool"
-			/>
-			<Tool
-				Name="VCAppVerifierTool"
-			/>
-			<Tool
-				Name="VCWebDeploymentTool"
-			/>
-			<Tool
-				Name="VCPostBuildEventTool"
-			/>
-		</Configuration>
-	</Configurations>
-	<References>
-	</References>
-	<Files>
-		<Filter
-			Name="Source Files"
-			Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
-			UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
-			>
-			<File
-				RelativePath=".\MeasurePageLoadTime.cpp"
-				>
-			</File>
-			<File
-				RelativePath=".\MeasurePageLoadTime.def"
-				>
-			</File>
-			<File
-				RelativePath=".\MeasurePageLoadTime.idl"
-				>
-			</File>
-			<File
-				RelativePath=".\MeasurePageLoadTimeBHO.cpp"
-				>
-			</File>
-			<File
-				RelativePath=".\stdafx.cpp"
-				>
-				<FileConfiguration
-					Name="Debug|Win32"
-					>
-					<Tool
-						Name="VCCLCompilerTool"
-						UsePrecompiledHeader="1"
-					/>
-				</FileConfiguration>
-				<FileConfiguration
-					Name="Release|Win32"
-					>
-					<Tool
-						Name="VCCLCompilerTool"
-						UsePrecompiledHeader="1"
-					/>
-				</FileConfiguration>
-			</File>
-		</Filter>
-		<Filter
-			Name="Header Files"
-			Filter="h;hpp;hxx;hm;inl;inc;xsd"
-			UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
-			>
-			<File
-				RelativePath=".\MeasurePageLoadTimeBHO.h"
-				>
-			</File>
-			<File
-				RelativePath=".\stdafx.h"
-				>
-			</File>
-		</Filter>
-		<Filter
-			Name="Resource Files"
-			Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"
-			UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
-			>
-			<File
-				RelativePath=".\MeasurePageLoadTime.rc"
-				>
-			</File>
-			<File
-				RelativePath=".\MeasurePageLoadTime.rgs"
-				>
-			</File>
-			<File
-				RelativePath=".\MeasurePageLoadTimeBHO.rgs"
-				>
-			</File>
-		</Filter>
-		<Filter
-			Name="Generated Files"
-			SourceControlFiles="false"
-			>
-			<File
-				RelativePath=".\MeasurePageLoadTime.h"
-				>
-			</File>
-			<File
-				RelativePath=".\MeasurePageLoadTime_i.c"
-				>
-				<FileConfiguration
-					Name="Debug|Win32"
-					>
-					<Tool
-						Name="VCCLCompilerTool"
-						UsePrecompiledHeader="0"
-					/>
-				</FileConfiguration>
-				<FileConfiguration
-					Name="Release|Win32"
-					>
-					<Tool
-						Name="VCCLCompilerTool"
-						UsePrecompiledHeader="0"
-					/>
-				</FileConfiguration>
-			</File>
-		</Filter>
-		<File
-			RelativePath=".\ReadMe.txt"
-			>
-		</File>
-	</Files>
-	<Globals>
-	</Globals>
-</VisualStudioProject>
diff --git a/tools/measure_page_load_time/ie_bho/MeasurePageLoadTimeBHO.cpp b/tools/measure_page_load_time/ie_bho/MeasurePageLoadTimeBHO.cpp
deleted file mode 100644
index 3de87f3..0000000
--- a/tools/measure_page_load_time/ie_bho/MeasurePageLoadTimeBHO.cpp
+++ /dev/null
@@ -1,292 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Implements a Browser Helper Object (BHO) which opens a socket
-// and waits to receive URLs over it. Visits those URLs, measuring
-// how long it takes between the start of navigation and the
-// DocumentComplete event, and returns the time in milliseconds as
-// a string to the caller.
-
-#include "stdafx.h"
-#include "MeasurePageLoadTimeBHO.h"
-
-#define MAX_URL 1024                 // size of URL buffer
-#define MAX_PAGELOADTIME (4*60*1000) // assume all pages take < 4 minutes
-#define PORT 42492                   // port to listen on. Also jhaas's
-                                     // old MSFT employee number
-
-
-// Static function to serve as thread entry point, takes a "this"
-// pointer as pParam and calls the method in the object
-static DWORD WINAPI ProcessPageTimeRequests(LPVOID pThis) {
-  reinterpret_cast<CMeasurePageLoadTimeBHO*>(pThis)->ProcessPageTimeRequests();
-
-  return 0;
-}
-
-
-STDMETHODIMP CMeasurePageLoadTimeBHO::SetSite(IUnknown* pUnkSite)
-{
-    if (pUnkSite != NULL)
-    {
-        // Cache the pointer to IWebBrowser2.
-        HRESULT hr = pUnkSite->QueryInterface(IID_IWebBrowser2, (void **)&m_spWebBrowser);
-        if (SUCCEEDED(hr))
-        {
-            // Register to sink events from DWebBrowserEvents2.
-            hr = DispEventAdvise(m_spWebBrowser);
-            if (SUCCEEDED(hr))
-            {
-                m_fAdvised = TRUE;
-            }
-
-            // Stash the interface in the global interface table
-            CComGITPtr<IWebBrowser2> git(m_spWebBrowser);
-            m_dwCookie = git.Detach();
-
-            // Create the event to be signaled when navigation completes.
-            // Start it in nonsignaled state, and allow it to be triggered
-            // when the initial page load is done.
-            m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
-
-            // Create a thread to wait on the socket
-            HANDLE hThread = CreateThread(NULL, 0, ::ProcessPageTimeRequests, this, 0, NULL);
-        }
-    }
-    else
-    {
-        // Unregister event sink.
-        if (m_fAdvised)
-        {
-            DispEventUnadvise(m_spWebBrowser);
-            m_fAdvised = FALSE;
-        }
-
-        // Release cached pointers and other resources here.
-        m_spWebBrowser.Release();
-    }
-
-    // Call base class implementation.
-    return IObjectWithSiteImpl<CMeasurePageLoadTimeBHO>::SetSite(pUnkSite);
-}
-
-
-void STDMETHODCALLTYPE CMeasurePageLoadTimeBHO::OnDocumentComplete(IDispatch *pDisp, VARIANT *pvarURL)
-{
-    if (pDisp == m_spWebBrowser)
-    {
-        // Fire the event when the page is done loading
-        // to unblock the other thread.
-        SetEvent(m_hEvent);
-    }
-}
-
-
-void CMeasurePageLoadTimeBHO::ProcessPageTimeRequests()
-{
-    CoInitialize(NULL);
-
-    // The event will start in nonsignaled state, meaning that
-    // the initial page load isn't done yet. Wait for that to
-    // finish before doing anything.
-    //
-    // It seems to be the case that the BHO will get loaded
-    // and SetSite called always before the initial page load
-    // even begins, but just to be on the safe side, we won't
-    // wait indefinitely.
-    WaitForSingleObject(m_hEvent, MAX_PAGELOADTIME);
-
-    // Retrieve the web browser interface from the global table
-    CComGITPtr<IWebBrowser2> git(m_dwCookie);
-    IWebBrowser2* browser;
-    git.CopyTo(&browser);
-
-    // Create a listening socket
-    m_sockListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
-    if (m_sockListen == SOCKET_ERROR)
-        ErrorExit();
-
-    BOOL on = TRUE;
-    if (setsockopt(m_sockListen, SOL_SOCKET, SO_REUSEADDR,
-                   (const char*)&on, sizeof(on)))
-        ErrorExit();
-
-    // Bind the listening socket
-    SOCKADDR_IN addrBind;
-
-    addrBind.sin_family      = AF_INET;
-    addrBind.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
-    addrBind.sin_port        = htons(PORT);
-
-    if (bind(m_sockListen, (sockaddr*)&addrBind, sizeof(addrBind)))
-        ErrorExit();
-
-    // Listen for incoming connections
-    if (listen(m_sockListen, 1))
-        ErrorExit();
-
-    // Ensure the socket is blocking... it should be by default, but
-    // it can't hurt to make sure
-    unsigned long nNonblocking = 0;
-    if (ioctlsocket(m_sockListen, FIONBIO, &nNonblocking))
-        ErrorExit();
-
-    m_sockTransport = 0;
-
-    // Loop indefinitely waiting for connections
-    while(1)
-    {
-        SOCKADDR_IN addrConnected;
-        int         sConnected = sizeof(addrConnected);
-
-        // Wait for a client to connect and send a URL
-        m_sockTransport = accept(
-            m_sockListen, (sockaddr*)&addrConnected, &sConnected);
-
-        if (m_sockTransport == SOCKET_ERROR)
-            ErrorExit();
-
-        char pbBuffer[MAX_URL], strURL[MAX_URL];
-        DWORD cbRead, cbWritten;
-
-        bool fDone = false;
-
-        // Loop until we're done with this client
-        while (!fDone)
-        {
-            *strURL = '\0';
-            bool fReceivedCR = false;
-
-            do
-            {
-                // Only receive up to the first carriage return
-                cbRead = recv(m_sockTransport, pbBuffer, MAX_URL-1, MSG_PEEK);
-
-                // An error on read most likely means that the remote peer
-                // closed the connection. Go back to waiting
-                if (cbRead == 0)
-                {
-                    fDone = true;
-                    break;
-                }
-
-                // Null terminate the received characters so strchr() is safe
-                pbBuffer[cbRead] = '\0';
-
-                if(char* pchFirstCR = strchr(pbBuffer, '\n'))
-                {
-                    cbRead = (DWORD)(pchFirstCR - pbBuffer + 1);
-                    fReceivedCR = true;
-                }
-
-                // The below call will not block, since we determined with
-                // MSG_PEEK that at least cbRead bytes are in the TCP receive buffer
-                recv(m_sockTransport, pbBuffer, cbRead, 0);
-                pbBuffer[cbRead] = '\0';
-
-                strcat_s(strURL, sizeof(strURL), pbBuffer);
-            } while (!fReceivedCR);
-
-            // If an error occurred while reading, exit this loop
-            if (fDone)
-                break;
-
-            // Strip the trailing CR and/or LF
-            int i;
-            for (i = (int)strlen(strURL)-1; i >= 0 && isspace(strURL[i]); i--)
-            {
-                strURL[i] = '\0';
-            }
-
-            if (i < 0)
-            {
-                // Sending a carriage return on a line by itself means that
-                // the client is done making requests
-                fDone = true;
-            }
-            else
-            {
-                // Send the browser to the requested URL
-                CComVariant vNavFlags( navNoReadFromCache );
-                CComVariant vTargetFrame("_self");
-                CComVariant vPostData("");
-                CComVariant vHTTPHeaders("");
-
-                ResetEvent(m_hEvent);
-                DWORD dwStartTime = GetTickCount();
-
-                HRESULT hr = browser->Navigate(
-                    CComBSTR(strURL),
-                    &vNavFlags,
-                    &vTargetFrame, // TargetFrameName
-                    &vPostData, // PostData
-                    &vHTTPHeaders // Headers
-                    );
-
-                // The main browser thread will call OnDocumentComplete() when
-                // the page is done loading, which will in turn trigger
-                // m_hEvent. Wait here until then; the event will reset itself
-                // once this thread is released
-                if (WaitForSingleObject(m_hEvent, MAX_PAGELOADTIME) == WAIT_TIMEOUT)
-                {
-                    sprintf_s(pbBuffer, sizeof(pbBuffer), "%s,timeout\n", strURL);
-
-                    browser->Stop();
-                }
-                else
-                {
-                    // Format the elapsed time as a string
-                    DWORD dwLoadTime = GetTickCount() - dwStartTime;
-                    sprintf_s(
-                        pbBuffer, sizeof(pbBuffer), "%s,%d\n", strURL, dwLoadTime);
-                }
-
-                // Send the result. Just in case the TCP buffer can't handle
-                // the whole thing, send in parts if necessary
-                char *chSend = pbBuffer;
-
-                while (*chSend)
-                {
-                    cbWritten = send(
-                        m_sockTransport, chSend, (int)strlen(chSend), 0);
-
-                    // Error on send probably means connection reset by peer
-                    if (cbWritten == 0)
-                    {
-                        fDone = true;
-                        break;
-                    }
-
-                    chSend += cbWritten;
-                }
-            }
-        }
-
-        // Close the transport socket and wait for another connection
-        closesocket(m_sockTransport);
-        m_sockTransport = 0;
-    }
-}
-
-
-void CMeasurePageLoadTimeBHO::ErrorExit()
-{
-    // Unlink from IE, close the sockets, then terminate this
-    // thread
-    SetSite(NULL);
-
-    if (m_sockTransport && m_sockTransport != SOCKET_ERROR)
-    {
-        closesocket(m_sockTransport);
-        m_sockTransport = 0;
-    }
-
-    if (m_sockListen && m_sockListen != SOCKET_ERROR)
-    {
-        closesocket(m_sockListen);
-        m_sockListen = 0;
-    }
-
-    TerminateThread(GetCurrentThread(), -1);
-}
diff --git a/tools/measure_page_load_time/ie_bho/MeasurePageLoadTimeBHO.h b/tools/measure_page_load_time/ie_bho/MeasurePageLoadTimeBHO.h
deleted file mode 100644
index cabb0240..0000000
--- a/tools/measure_page_load_time/ie_bho/MeasurePageLoadTimeBHO.h
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// MeasurePageLoadTimeBHO.h : Declaration of the CMeasurePageLoadTimeBHO
-
-#include "resource.h"       // main symbols
-
-#include <shlguid.h>     // IID_IWebBrowser2, DIID_DWebBrowserEvents2, et
-#include <exdispid.h>    // DISPID_DOCUMENTCOMPLETE, etc.
-
-#include <string>
-
-#include "MeasurePageLoadTime.h"
-
-
-#if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
-#error "Single-threaded COM objects are not properly supported on Windows CE platform, such as the Windows Mobile platforms that do not include full DCOM support. Define _CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA to force ATL to support creating single-thread COM objects and allow use of its single-threaded COM object implementations. The threading model in your rgs file was set to 'Free' as that is the only threading model supported in non DCOM Windows CE platforms."
-#endif
-
-
-
-// CMeasurePageLoadTimeBHO
-
-class ATL_NO_VTABLE CMeasurePageLoadTimeBHO :
-	public CComObjectRootEx<CComSingleThreadModel>,
-	public CComCoClass<CMeasurePageLoadTimeBHO, &CLSID_MeasurePageLoadTimeBHO>,
-	public IObjectWithSiteImpl<CMeasurePageLoadTimeBHO>,
-	public IDispatchImpl<IMeasurePageLoadTimeBHO, &IID_IMeasurePageLoadTimeBHO, &LIBID_MeasurePageLoadTimeLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
-  public IDispEventImpl<1, CMeasurePageLoadTimeBHO, &DIID_DWebBrowserEvents2, &LIBID_SHDocVw, 1, 1>
-{
-public:
-	CMeasurePageLoadTimeBHO()
-	{
-	}
-
-DECLARE_REGISTRY_RESOURCEID(IDR_MEASUREPAGELOADTIMEBHO)
-
-DECLARE_NOT_AGGREGATABLE(CMeasurePageLoadTimeBHO)
-
-BEGIN_COM_MAP(CMeasurePageLoadTimeBHO)
-	COM_INTERFACE_ENTRY(IMeasurePageLoadTimeBHO)
-	COM_INTERFACE_ENTRY(IDispatch)
-	COM_INTERFACE_ENTRY(IObjectWithSite)
-END_COM_MAP()
-
-BEGIN_SINK_MAP(CMeasurePageLoadTimeBHO)
-    SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_DOCUMENTCOMPLETE, OnDocumentComplete)
-END_SINK_MAP()
-
-    // DWebBrowserEvents2
-  void STDMETHODCALLTYPE OnDocumentComplete(IDispatch *pDisp, VARIANT *pvarURL);
-  STDMETHOD(SetSite)(IUnknown *pUnkSite);
-
-	DECLARE_PROTECT_FINAL_CONSTRUCT()
-
-	HRESULT FinalConstruct()
-	{
-		return S_OK;
-	}
-
-	void FinalRelease()
-	{
-	}
-
-  void ProcessPageTimeRequests(void);
-  void VisitNextURL(void);
-  void ErrorExit(void);
-
-private:
-    CComPtr<IWebBrowser2>  m_spWebBrowser;
-    BOOL m_fAdvised;
-
-    // Handle to global interface table
-    DWORD m_dwCookie;
-
-    // Handle to event to signal when navigation completes
-    HANDLE m_hEvent;
-
-    // Socket for accepting incoming connections
-    SOCKET m_sockListen;
-
-    // Socket for communicating with remote peers
-    SOCKET m_sockTransport;
-};
-
-OBJECT_ENTRY_AUTO(__uuidof(MeasurePageLoadTimeBHO), CMeasurePageLoadTimeBHO)
diff --git a/tools/measure_page_load_time/ie_bho/MeasurePageLoadTimeBHO.rgs b/tools/measure_page_load_time/ie_bho/MeasurePageLoadTimeBHO.rgs
deleted file mode 100644
index 907015f..0000000
--- a/tools/measure_page_load_time/ie_bho/MeasurePageLoadTimeBHO.rgs
+++ /dev/null
@@ -1,27 +0,0 @@
-HKCR

-{

-	MeasurePageLoadTime.MeasurePageLoadTi.1 = s 'MeasurePageLoadTimeBHO Class'

-	{

-		CLSID = s '{807E68BC-238F-4163-AE4B-0A3604F3E145}'

-	}

-	MeasurePageLoadTime.MeasurePageLoadTime = s 'MeasurePageLoadTimeBHO Class'

-	{

-		CLSID = s '{807E68BC-238F-4163-AE4B-0A3604F3E145}'

-		CurVer = s 'MeasurePageLoadTime.MeasurePageLoadTi.1'

-	}

-	NoRemove CLSID

-	{

-		ForceRemove {807E68BC-238F-4163-AE4B-0A3604F3E145} = s 'MeasurePageLoadTimeBHO Class'

-		{

-			ProgID = s 'MeasurePageLoadTime.MeasurePageLoadTi.1'

-			VersionIndependentProgID = s 'MeasurePageLoadTime.MeasurePageLoadTime'

-			ForceRemove 'Programmable'

-			InprocServer32 = s '%MODULE%'

-			{

-				val ThreadingModel = s 'Apartment'

-			}

-			val AppID = s '%APPID%'

-			'TypeLib' = s '{61AC7AC4-B715-4955-A238-5F9AEA80DF4B}'

-		}

-	}

-}

diff --git a/tools/measure_page_load_time/ie_bho/resource.h b/tools/measure_page_load_time/ie_bho/resource.h
deleted file mode 100644
index 38dc825..0000000
--- a/tools/measure_page_load_time/ie_bho/resource.h
+++ /dev/null
@@ -1,18 +0,0 @@
-//{{NO_DEPENDENCIES}}
-// Microsoft Visual C++ generated include file.
-// Used by MeasurePageLoadTime.rc
-//
-#define IDS_PROJNAME                    100
-#define IDR_MEASUREPAGELOADTIME         101
-#define IDR_MEASUREPAGELOADTIMEBHO      102
-
-// Next default values for new objects
-//
-#ifdef APSTUDIO_INVOKED
-#ifndef APSTUDIO_READONLY_SYMBOLS
-#define _APS_NEXT_RESOURCE_VALUE        201
-#define _APS_NEXT_COMMAND_VALUE         32768
-#define _APS_NEXT_CONTROL_VALUE         201
-#define _APS_NEXT_SYMED_VALUE           103
-#endif
-#endif
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 04333c0f..42f8d7be 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -6340,6 +6340,22 @@
   </summary>
 </histogram>
 
+<histogram name="Apps.ScrollableShelf.AnimationSmoothness" units="%"
+    expires_after="2020-11-06">
+<!-- Name completed by histogram suffixes
+     name="HomeLauncherVisibility" -->
+
+  <owner>anasalazar@chromium.org</owner>
+  <owner>newcomer@chromium.org</owner>
+  <summary>
+    Relative smoothness of animations of the scrollable shelf when scrolling.
+    100% represents ideally smooth 60 frames per second. 50% represents only 30
+    frames per second is achieved during the animations. 0% should not happen.
+    This metric is recorded exactly once when the user scrolls over the
+    scrollable shelf.
+  </summary>
+</histogram>
+
 <histogram name="Apps.StateTransition.AnimationSmoothness" units="%"
     expires_after="2020-03-22">
 <!-- Name completed by histogram_suffixes
@@ -29533,6 +29549,9 @@
 <histogram name="DataReductionProxy.Protocol.AcceptTransform"
     enum="DataReductionProxyProtocolAcceptTransformEvent"
     expires_after="2019-12-12">
+  <obsolete>
+    Obsolete as of 10/2018.
+  </obsolete>
   <owner>dougarnett@chromium.org</owner>
   <summary>
     Records the sending of accepted transform headers to the data reduction
@@ -172273,6 +172292,14 @@
   <affected-histogram name="Histogram.InconsistentSnapshotBrowser"/>
 </histogram_suffixes>
 
+<histogram_suffixes name="HomeLauncherVisibility" separator=".">
+  <suffix name="LauncherHidden"
+      label="Home Launcher was hidden during this animation."/>
+  <suffix name="LauncherVisible"
+      label="Home Launcher was visible during this animation."/>
+  <affected-histogram name="Apps.ScrollableShelf.AnimationSmoothness"/>
+</histogram_suffixes>
+
 <histogram_suffixes name="HstsState" separator=".">
   <suffix name="HSTSNotEnabled" label="The HSTS is not enabled."/>
   <suffix name="WithHSTSEnabled" label="The HSTS is enabled."/>
diff --git a/tools/metrics/rappor/rappor.xml b/tools/metrics/rappor/rappor.xml
index 292703b..bc680dd4 100644
--- a/tools/metrics/rappor/rappor.xml
+++ b/tools/metrics/rappor/rappor.xml
@@ -110,15 +110,6 @@
   </summary>
 </rappor-metric>
 
-<rappor-metric name="ContentSettings.Plugins.AddedAllowException"
-    type="ETLD_PLUS_ONE">
-  <owner>tommycli@chromium.org</owner>
-  <summary>
-    The eTLD+1 of a URL that the user added to the ALLOW site exceptions for the
-    Plugins Content Setting.
-  </summary>
-</rappor-metric>
-
 <rappor-metric name="CustomTabs.ServiceClient.PackageName"
     type="UMA_RAPPOR_TYPE">
   <owner>yusufo@chromium.org</owner>
@@ -214,23 +205,6 @@
   </summary>
 </rappor-metric>
 
-<rappor-metric name="PowerfulFeatureUse.Host.DeviceMotion.Insecure"
-    type="ETLD_PLUS_ONE">
-  <owner>jww@chromium.org</owner>
-  <summary>
-    The host of the URL that uses the device motion API from an insecure origin.
-  </summary>
-</rappor-metric>
-
-<rappor-metric name="PowerfulFeatureUse.Host.DeviceOrientation.Insecure"
-    type="ETLD_PLUS_ONE">
-  <owner>jww@chromium.org</owner>
-  <summary>
-    The host of the URL that uses the device orientation API from an insecure
-    origin.
-  </summary>
-</rappor-metric>
-
 <rappor-metric name="PowerfulFeatureUse.Host.Fullscreen.Insecure"
     type="ETLD_PLUS_ONE">
   <owner>jww@chromium.org</owner>
diff --git a/tools/perf/expectations.config b/tools/perf/expectations.config
index 05aa905..e39c20c 100644
--- a/tools/perf/expectations.config
+++ b/tools/perf/expectations.config
@@ -224,7 +224,6 @@
 crbug.com/1000473 [ win ] rendering.desktop/balls_svg_animations [ Skip ]
 crbug.com/1017244 [ desktop ] rendering.desktop/youtube_2018 [ Skip ]
 crbug.com/1017244 [ desktop ] rendering.desktop/youtube_pinch_2018 [ Skip ]
-crbug.com/1021682 [ desktop ] rendering.desktop/runway [ Skip ]
 
 # Benchmark: rendering.mobile
 crbug.com/785485 [ android-webview ] rendering.mobile/kevs_3d [ Skip ]
@@ -269,7 +268,6 @@
 crbug.com/967809 [ android-webview ] rendering.mobile/microsoft_video_city [ Skip ]
 crbug.com/1000473 [ android ] rendering.mobile/balls_svg_animations [ Skip ]
 crbug.com/1017244 [ mobile ] rendering.mobile/youtube_2018 [ Skip ]
-crbug.com/1021682 [ mobile ] rendering.mobile/runway [ Skip ]
 
 # Benchmark: rasterize_and_record_micro.top_25
 crbug.com/764543 rasterize_and_record_micro.top_25/file://static_top_25/wikipedia.html [ Skip ]
diff --git a/tools/perf/page_sets/data/rendering_desktop.json b/tools/perf/page_sets/data/rendering_desktop.json
index 0f8182d..599106b 100644
--- a/tools/perf/page_sets/data/rendering_desktop.json
+++ b/tools/perf/page_sets/data/rendering_desktop.json
@@ -270,8 +270,8 @@
         "repaint_yahoo_homepage_2018": {
             "DEFAULT": "rendering_desktop_fdde4c2d8b.wprgo"
         },
-        "runway": {
-            "DEFAULT": "rendering_desktop_004.wprgo"
+        "runway_2019": {
+            "DEFAULT": "rendering_desktop_00acdd603b.wprgo"
         },
         "san_angeles": {
             "DEFAULT": "rendering_desktop_011.wprgo"
diff --git a/tools/perf/page_sets/data/rendering_desktop_00acdd603b.wprgo.sha1 b/tools/perf/page_sets/data/rendering_desktop_00acdd603b.wprgo.sha1
new file mode 100644
index 0000000..0856c7b0
--- /dev/null
+++ b/tools/perf/page_sets/data/rendering_desktop_00acdd603b.wprgo.sha1
@@ -0,0 +1 @@
+00acdd603bf569fb0800fa41c0c0cf0cb334ed6f
\ No newline at end of file
diff --git a/tools/perf/page_sets/data/rendering_mobile.json b/tools/perf/page_sets/data/rendering_mobile.json
index 815ae4b..c1943ab10 100644
--- a/tools/perf/page_sets/data/rendering_mobile.json
+++ b/tools/perf/page_sets/data/rendering_mobile.json
@@ -513,8 +513,8 @@
         "reddit_mobile_2018": {
             "DEFAULT": "rendering_mobile_011.wprgo"
         },
-        "runway": {
-            "DEFAULT": "rendering_mobile_004.wprgo"
+        "runway_2019": {
+            "DEFAULT": "rendering_mobile_4596353b1a.wprgo"
         },
         "san_angeles": {
             "DEFAULT": "rendering_mobile_026.wprgo"
diff --git a/tools/perf/page_sets/data/rendering_mobile_4596353b1a.wprgo.sha1 b/tools/perf/page_sets/data/rendering_mobile_4596353b1a.wprgo.sha1
new file mode 100644
index 0000000..afdb8a7
--- /dev/null
+++ b/tools/perf/page_sets/data/rendering_mobile_4596353b1a.wprgo.sha1
@@ -0,0 +1 @@
+4596353b1aeb5ae76f17dac2d23d660179ab0f67
\ No newline at end of file
diff --git a/tools/perf/page_sets/rendering/tough_canvas_cases.py b/tools/perf/page_sets/rendering/tough_canvas_cases.py
index 1306c4e..f696d0f 100644
--- a/tools/perf/page_sets/rendering/tough_canvas_cases.py
+++ b/tools/perf/page_sets/rendering/tough_canvas_cases.py
@@ -46,6 +46,7 @@
 class RunwayPage(ToughCanvasPage):
   BASE_NAME = 'runway'
   URL = 'http://runway.countlessprojects.com/prototype/performance_test.html'
+  YEAR = '2019'
   TAGS = ToughCanvasPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
 
diff --git a/ui/accessibility/ax_node_position_unittest.cc b/ui/accessibility/ax_node_position_unittest.cc
index a18dc39..cecb303 100644
--- a/ui/accessibility/ax_node_position_unittest.cc
+++ b/ui/accessibility/ax_node_position_unittest.cc
@@ -198,6 +198,7 @@
   // different language.
   std::unique_ptr<AXTree> CreateMultilingualDocument(
       std::vector<int>* text_offsets) {
+    EXPECT_NE(nullptr, text_offsets);
     text_offsets->push_back(0);
 
     base::string16 english_text;
@@ -212,8 +213,8 @@
     base::string16 hindi_text;
     for (int i = 3; i < 5; ++i) {
       base::string16 grapheme = base::WideToUTF16(kGraphemeClusters[i]);
-      EXPECT_LE(2u, grapheme.length())
-          << "All Hindi characters should be two or more UTF16 code units.";
+      EXPECT_LE(2u, grapheme.length()) << "All Hindi characters should be two "
+                                          "or more UTF16 code units in length.";
       text_offsets->push_back(text_offsets->back() + int{grapheme.length()});
       hindi_text.append(grapheme);
     }
@@ -222,7 +223,7 @@
     for (int i = 5; i < 8; ++i) {
       base::string16 grapheme = base::WideToUTF16(kGraphemeClusters[i]);
       EXPECT_LT(0u, grapheme.length())
-          << "One of the Thai characters should be one UTF16 code units, "
+          << "One of the Thai characters should be one UTF16 code unit, "
              "whilst others should be two or more.";
       text_offsets->push_back(text_offsets->back() + int{grapheme.length()});
       thai_text.append(grapheme);
@@ -2089,7 +2090,6 @@
       new_tree->data().tree_id, new_tree->root()->id(), 10 /* text_offset */,
       ax::mojom::TextAffinity::kUpstream);
   ASSERT_NE(nullptr, test_position);
-  ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_EQ(new_tree->root()->id(), test_position->anchor_id());
   EXPECT_EQ(10, test_position->text_offset());
@@ -3747,6 +3747,12 @@
   ASSERT_TRUE(text_position->IsTextPosition());
 
   TestPositionType test_position = text_position->CreateNextCharacterPosition(
+      AXBoundaryBehavior::StopIfAlreadyAtBoundary);
+  EXPECT_NE(nullptr, test_position);
+  EXPECT_TRUE(test_position->IsTextPosition());
+  EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
+  EXPECT_EQ(4, test_position->text_offset());
+  test_position = text_position->CreateNextCharacterPosition(
       AXBoundaryBehavior::CrossBoundary);
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
@@ -3772,6 +3778,12 @@
   ASSERT_TRUE(text_position->IsTextPosition());
 
   test_position = text_position->CreateNextCharacterPosition(
+      AXBoundaryBehavior::StopIfAlreadyAtBoundary);
+  EXPECT_NE(nullptr, test_position);
+  EXPECT_TRUE(test_position->IsTextPosition());
+  EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
+  EXPECT_EQ(5, test_position->text_offset());
+  test_position = text_position->CreateNextCharacterPosition(
       AXBoundaryBehavior::CrossBoundary);
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
@@ -3797,6 +3809,12 @@
   ASSERT_TRUE(text_position->IsTextPosition());
 
   test_position = text_position->CreateNextCharacterPosition(
+      AXBoundaryBehavior::StopIfAlreadyAtBoundary);
+  EXPECT_NE(nullptr, test_position);
+  EXPECT_TRUE(test_position->IsTextPosition());
+  EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
+  EXPECT_EQ(6, test_position->text_offset());
+  test_position = text_position->CreateNextCharacterPosition(
       AXBoundaryBehavior::CrossBoundary);
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
@@ -3826,6 +3844,12 @@
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsNullPosition());
   test_position = text_position->CreateNextCharacterPosition(
+      AXBoundaryBehavior::StopIfAlreadyAtBoundary);
+  EXPECT_NE(nullptr, test_position);
+  EXPECT_TRUE(test_position->IsTextPosition());
+  EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
+  EXPECT_EQ(6, test_position->text_offset());
+  test_position = text_position->CreateNextCharacterPosition(
       AXBoundaryBehavior::StopAtAnchorBoundary);
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
@@ -3851,6 +3875,12 @@
   EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
   EXPECT_EQ(1, test_position->text_offset());
   test_position = text_position->CreateNextCharacterPosition(
+      AXBoundaryBehavior::StopIfAlreadyAtBoundary);
+  EXPECT_NE(nullptr, test_position);
+  EXPECT_TRUE(test_position->IsTextPosition());
+  EXPECT_EQ(check_box_.id, test_position->anchor_id());
+  EXPECT_EQ(0, test_position->text_offset());
+  test_position = text_position->CreateNextCharacterPosition(
       AXBoundaryBehavior::StopAtAnchorBoundary);
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
@@ -3903,7 +3933,13 @@
 
   TestPositionType test_position =
       text_position->CreatePreviousCharacterPosition(
-          AXBoundaryBehavior::CrossBoundary);
+          AXBoundaryBehavior::StopIfAlreadyAtBoundary);
+  EXPECT_NE(nullptr, test_position);
+  EXPECT_TRUE(test_position->IsTextPosition());
+  EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
+  EXPECT_EQ(5, test_position->text_offset());
+  test_position = text_position->CreatePreviousCharacterPosition(
+      AXBoundaryBehavior::CrossBoundary);
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
@@ -3928,6 +3964,12 @@
   ASSERT_TRUE(text_position->IsTextPosition());
 
   test_position = text_position->CreatePreviousCharacterPosition(
+      AXBoundaryBehavior::StopIfAlreadyAtBoundary);
+  EXPECT_NE(nullptr, test_position);
+  EXPECT_TRUE(test_position->IsTextPosition());
+  EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
+  EXPECT_EQ(1, test_position->text_offset());
+  test_position = text_position->CreatePreviousCharacterPosition(
       AXBoundaryBehavior::CrossBoundary);
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
@@ -3953,6 +3995,12 @@
   ASSERT_TRUE(text_position->IsTextPosition());
 
   test_position = text_position->CreatePreviousCharacterPosition(
+      AXBoundaryBehavior::StopIfAlreadyAtBoundary);
+  EXPECT_NE(nullptr, test_position);
+  EXPECT_TRUE(test_position->IsTextPosition());
+  EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
+  EXPECT_EQ(0, test_position->text_offset());
+  test_position = text_position->CreatePreviousCharacterPosition(
       AXBoundaryBehavior::CrossBoundary);
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
@@ -3982,6 +4030,12 @@
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsNullPosition());
   test_position = text_position->CreatePreviousCharacterPosition(
+      AXBoundaryBehavior::StopIfAlreadyAtBoundary);
+  EXPECT_NE(nullptr, test_position);
+  EXPECT_TRUE(test_position->IsTextPosition());
+  EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
+  EXPECT_EQ(0, test_position->text_offset());
+  test_position = text_position->CreatePreviousCharacterPosition(
       AXBoundaryBehavior::StopAtAnchorBoundary);
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
@@ -4005,6 +4059,12 @@
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsNullPosition());
   test_position = text_position->CreatePreviousCharacterPosition(
+      AXBoundaryBehavior::StopIfAlreadyAtBoundary);
+  EXPECT_NE(nullptr, test_position);
+  EXPECT_TRUE(test_position->IsTextPosition());
+  EXPECT_EQ(check_box_.id, test_position->anchor_id());
+  EXPECT_EQ(0, test_position->text_offset());
+  test_position = text_position->CreatePreviousCharacterPosition(
       AXBoundaryBehavior::StopAtAnchorBoundary);
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
@@ -4062,6 +4122,51 @@
     EXPECT_EQ(text_offset, test_position->text_offset());
     EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
   }
+
+  test_position = AXNodePosition::CreateTextPosition(
+      new_tree->data().tree_id, new_tree->root()->id(), 3 /* text_offset */,
+      ax::mojom::TextAffinity::kDownstream);
+  test_position = test_position->CreateNextCharacterPosition(
+      AXBoundaryBehavior::StopIfAlreadyAtBoundary);
+  ASSERT_NE(nullptr, test_position);
+  EXPECT_TRUE(test_position->IsTextPosition());
+  EXPECT_EQ(new_tree->root()->id(), test_position->anchor_id());
+  EXPECT_EQ(3, test_position->text_offset());
+  EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
+
+  test_position = AXNodePosition::CreateTextPosition(
+      new_tree->data().tree_id, new_tree->root()->id(), 4 /* text_offset */,
+      ax::mojom::TextAffinity::kDownstream);
+  test_position = test_position->CreateNextCharacterPosition(
+      AXBoundaryBehavior::StopIfAlreadyAtBoundary);
+  ASSERT_NE(nullptr, test_position);
+  EXPECT_TRUE(test_position->IsTextPosition());
+  EXPECT_EQ(new_tree->root()->id(), test_position->anchor_id());
+  EXPECT_EQ(5, test_position->text_offset());
+  EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
+
+  test_position = AXNodePosition::CreateTextPosition(
+      new_tree->data().tree_id, new_tree->root()->id(), 9 /* text_offset */,
+      ax::mojom::TextAffinity::kUpstream);
+  test_position = test_position->CreateNextCharacterPosition(
+      AXBoundaryBehavior::StopIfAlreadyAtBoundary);
+  ASSERT_NE(nullptr, test_position);
+  EXPECT_TRUE(test_position->IsTextPosition());
+  EXPECT_EQ(new_tree->root()->id(), test_position->anchor_id());
+  EXPECT_EQ(9, test_position->text_offset());
+  EXPECT_EQ(ax::mojom::TextAffinity::kUpstream, test_position->affinity());
+
+  test_position = AXNodePosition::CreateTextPosition(
+      new_tree->data().tree_id, new_tree->root()->id(), 10 /* text_offset */,
+      ax::mojom::TextAffinity::kUpstream);
+  test_position = test_position->CreateNextCharacterPosition(
+      AXBoundaryBehavior::StopIfAlreadyAtBoundary);
+  ASSERT_NE(nullptr, test_position);
+  EXPECT_TRUE(test_position->IsTextPosition());
+  EXPECT_EQ(new_tree->root()->id(), test_position->anchor_id());
+  EXPECT_EQ(12, test_position->text_offset());
+  // Affinity should have been reset to downstream because there was a move.
+  EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
 }
 
 TEST_F(AXPositionTest, CreatePreviousCharacterPositionAtGraphemeBoundary) {
@@ -4094,6 +4199,51 @@
     EXPECT_EQ(text_offset, test_position->text_offset());
     EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
   }
+
+  test_position = AXNodePosition::CreateTextPosition(
+      new_tree->data().tree_id, new_tree->root()->id(), 3 /* text_offset */,
+      ax::mojom::TextAffinity::kDownstream);
+  test_position = test_position->CreatePreviousCharacterPosition(
+      AXBoundaryBehavior::StopIfAlreadyAtBoundary);
+  ASSERT_NE(nullptr, test_position);
+  EXPECT_TRUE(test_position->IsTextPosition());
+  EXPECT_EQ(new_tree->root()->id(), test_position->anchor_id());
+  EXPECT_EQ(3, test_position->text_offset());
+  EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
+
+  test_position = AXNodePosition::CreateTextPosition(
+      new_tree->data().tree_id, new_tree->root()->id(), 4 /* text_offset */,
+      ax::mojom::TextAffinity::kDownstream);
+  test_position = test_position->CreatePreviousCharacterPosition(
+      AXBoundaryBehavior::StopIfAlreadyAtBoundary);
+  ASSERT_NE(nullptr, test_position);
+  EXPECT_TRUE(test_position->IsTextPosition());
+  EXPECT_EQ(new_tree->root()->id(), test_position->anchor_id());
+  EXPECT_EQ(3, test_position->text_offset());
+  EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
+
+  test_position = AXNodePosition::CreateTextPosition(
+      new_tree->data().tree_id, new_tree->root()->id(), 9 /* text_offset */,
+      ax::mojom::TextAffinity::kUpstream);
+  test_position = test_position->CreatePreviousCharacterPosition(
+      AXBoundaryBehavior::StopIfAlreadyAtBoundary);
+  ASSERT_NE(nullptr, test_position);
+  EXPECT_TRUE(test_position->IsTextPosition());
+  EXPECT_EQ(new_tree->root()->id(), test_position->anchor_id());
+  EXPECT_EQ(9, test_position->text_offset());
+  EXPECT_EQ(ax::mojom::TextAffinity::kUpstream, test_position->affinity());
+
+  test_position = AXNodePosition::CreateTextPosition(
+      new_tree->data().tree_id, new_tree->root()->id(), 10 /* text_offset */,
+      ax::mojom::TextAffinity::kUpstream);
+  test_position = test_position->CreatePreviousCharacterPosition(
+      AXBoundaryBehavior::StopIfAlreadyAtBoundary);
+  ASSERT_NE(nullptr, test_position);
+  EXPECT_TRUE(test_position->IsTextPosition());
+  EXPECT_EQ(new_tree->root()->id(), test_position->anchor_id());
+  EXPECT_EQ(9, test_position->text_offset());
+  // Affinity should have been reset to downstream because there was a move.
+  EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
 }
 
 TEST_F(AXPositionTest, ReciprocalCreateNextAndPreviousCharacterPosition) {
diff --git a/ui/accessibility/ax_position.h b/ui/accessibility/ax_position.h
index 6143953..d53d8e8 100644
--- a/ui/accessibility/ax_position.h
+++ b/ui/accessibility/ax_position.h
@@ -1541,8 +1541,6 @@
   // See also http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries
   AXPositionInstance CreateNextCharacterPosition(
       AXBoundaryBehavior boundary_behavior) const {
-    DCHECK_NE(boundary_behavior, AXBoundaryBehavior::StopIfAlreadyAtBoundary)
-        << "StopIfAlreadyAtBoundary is unreasonable for character boundaries.";
     if (boundary_behavior == AXBoundaryBehavior::StopAtAnchorBoundary &&
         AtEndOfAnchor()) {
       return Clone();
@@ -1553,11 +1551,18 @@
 
     // There is no next character position.
     if (text_position->IsNullPosition()) {
-      if (boundary_behavior == AXBoundaryBehavior::StopAtLastAnchorBoundary)
+      if (boundary_behavior == AXBoundaryBehavior::StopIfAlreadyAtBoundary ||
+          boundary_behavior == AXBoundaryBehavior::StopAtLastAnchorBoundary) {
         text_position = Clone();
+      }
       return text_position;
     }
 
+    if (boundary_behavior == AXBoundaryBehavior::StopIfAlreadyAtBoundary &&
+        *text_position == *this) {
+      return Clone();
+    }
+
     DCHECK_LT(text_position->text_offset_, text_position->MaxTextOffset());
     std::unique_ptr<base::i18n::BreakIterator> grapheme_iterator =
         text_position->GetGraphemeIterator();
@@ -1598,8 +1603,6 @@
   // grapheme cluster.
   AXPositionInstance CreatePreviousCharacterPosition(
       AXBoundaryBehavior boundary_behavior) const {
-    DCHECK_NE(boundary_behavior, AXBoundaryBehavior::StopIfAlreadyAtBoundary)
-        << "StopIfAlreadyAtBoundary is unreasonable for character boundaries.";
     if (boundary_behavior == AXBoundaryBehavior::StopAtAnchorBoundary &&
         AtStartOfAnchor()) {
       return Clone();
@@ -1610,11 +1613,18 @@
 
     // There is no previous character position.
     if (text_position->IsNullPosition()) {
-      if (boundary_behavior == AXBoundaryBehavior::StopAtLastAnchorBoundary)
+      if (boundary_behavior == AXBoundaryBehavior::StopIfAlreadyAtBoundary ||
+          boundary_behavior == AXBoundaryBehavior::StopAtLastAnchorBoundary) {
         text_position = Clone();
+      }
       return text_position;
     }
 
+    if (boundary_behavior == AXBoundaryBehavior::StopIfAlreadyAtBoundary &&
+        *text_position == *this) {
+      return Clone();
+    }
+
     DCHECK_GT(text_position->text_offset_, 0);
     std::unique_ptr<base::i18n::BreakIterator> grapheme_iterator =
         text_position->GetGraphemeIterator();
@@ -2600,7 +2610,7 @@
   // platform's text representation. Some platforms use an embedded object
   // character that replaces the text coming from each child node.
   //
-  // Similar to "text_offset_", the length of the text is in Unicode code units,
+  // Similar to "text_offset_", the length of the text is in UTF16 code units,
   // not in grapheme clusters.
   virtual int MaxTextOffset() const {
     if (IsNullPosition())
@@ -3214,7 +3224,7 @@
   // For text positions, |child_index_| is initially set to |-1| and only
   // computed on demand. The same with tree positions and |text_offset_|.
   int child_index_;
-  // "text_offset_" represents the number of Unicode code units before this
+  // "text_offset_" represents the number of UTF16 code units before this
   // position. It doesn't count grapheme clusters.
   int text_offset_;
 
diff --git a/ui/accessibility/platform/ax_platform_node_auralinux.cc b/ui/accessibility/platform/ax_platform_node_auralinux.cc
index 241412e..c431d1e 100644
--- a/ui/accessibility/platform/ax_platform_node_auralinux.cc
+++ b/ui/accessibility/platform/ax_platform_node_auralinux.cc
@@ -982,10 +982,6 @@
                       AtkTextBoundary atk_boundary,
                       int* start_offset,
                       int* end_offset) {
-  if (atk_boundary == ATK_TEXT_BOUNDARY_CHAR) {
-    return GetCharacter(atk_text, offset, start_offset, end_offset);
-  }
-
   AXTextBoundary boundary = FromAtkTextBoundary(atk_boundary);
   return GetTextWithBoundaryType(atk_text, offset, boundary, start_offset,
                                  end_offset);
@@ -1130,10 +1126,6 @@
   *start_offset = -1;
   *end_offset = -1;
 
-  if (atk_granularity == ATK_TEXT_GRANULARITY_CHAR) {
-    return GetCharacter(atk_text, offset, start_offset, end_offset);
-  }
-
   AXTextBoundary boundary = FromAtkTextGranularity(atk_granularity);
   return GetTextWithBoundaryType(atk_text, offset, boundary, start_offset,
                                  end_offset);
diff --git a/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc b/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc
index fe973d51..145c045 100644
--- a/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc
+++ b/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc
@@ -1105,8 +1105,8 @@
   };
 
   verify_text_at_offset("d", 2, 2, 3);
-  verify_text_at_offset(nullptr, -1, -1, -1);
-  verify_text_at_offset(nullptr, 42342, -1, -1);
+  verify_text_at_offset(nullptr, -1, 0, 0);
+  verify_text_at_offset(nullptr, 42342, 0, 0);
   verify_text_at_offset("\xE2\x98\xBA", 23, 23, 24);
   verify_text_at_offset(" ", 24, 24, 25);
 
diff --git a/ui/views/color_chooser/color_chooser_view.cc b/ui/views/color_chooser/color_chooser_view.cc
index 3427ed0d..18664492 100644
--- a/ui/views/color_chooser/color_chooser_view.cc
+++ b/ui/views/color_chooser/color_chooser_view.cc
@@ -56,8 +56,8 @@
 
   std::string input =
       base::UTF16ToUTF8((text.size() == 6) ? text : text.substr(1));
-  std::vector<uint8_t> hex;
-  if (!base::HexStringToBytes(input, &hex))
+  std::array<uint8_t, 3> hex;
+  if (!base::HexStringToSpan(input, hex))
     return false;
 
   *result = SkColorSetRGB(hex[0], hex[1], hex[2]);
diff --git a/ui/views/style/platform_style.cc b/ui/views/style/platform_style.cc
index ec2a05cf..6aa6dc4 100644
--- a/ui/views/style/platform_style.cc
+++ b/ui/views/style/platform_style.cc
@@ -49,7 +49,7 @@
 const bool PlatformStyle::kUseRipples = true;
 const bool PlatformStyle::kTextfieldScrollsToStartOnFocusChange = false;
 const bool PlatformStyle::kTextfieldUsesDragCursorWhenDraggable = true;
-const bool PlatformStyle::kPreferFocusRings = false;
+const bool PlatformStyle::kPreferFocusRings = true;
 const bool PlatformStyle::kInactiveWidgetControlsAppearDisabled = false;
 
 // static
diff --git a/ui/webui/resources/cr_components/chromeos/multidevice_setup/fake_mojo_service.js b/ui/webui/resources/cr_components/chromeos/multidevice_setup/fake_mojo_service.js
index ee018bb..801feec 100644
--- a/ui/webui/resources/cr_components/chromeos/multidevice_setup/fake_mojo_service.js
+++ b/ui/webui/resources/cr_components/chromeos/multidevice_setup/fake_mojo_service.js
@@ -52,6 +52,21 @@
   }
 
   /** @override */
+  getEligibleActiveHostDevices() {
+    const deviceNames = ['Pixel', 'Pixel XL', 'Nexus 5', 'Nexus 6P'];
+    const devices = [];
+    for (let i = 0; i < this.deviceCount; i++) {
+      const deviceName = deviceNames[i % 4];
+      devices.push({
+        remoteDevice: {deviceName: deviceName, deviceId: deviceName + '--' + i}
+      });
+    }
+    return new Promise(function(resolve, reject) {
+      resolve({eligibleHostDevices: devices});
+    });
+  }
+
+  /** @override */
   setHostDevice(deviceId) {
     if (this.shouldSetHostSucceed) {
       console.log(
diff --git a/weblayer/browser/java/BUILD.gn b/weblayer/browser/java/BUILD.gn
index 86884bd..4e1b9bf 100644
--- a/weblayer/browser/java/BUILD.gn
+++ b/weblayer/browser/java/BUILD.gn
@@ -73,10 +73,6 @@
   # Needed for android.webkit.WebView(Delegate|Factory)
   alternative_android_sdk_dep =
       "//third_party/android_sdk:public_framework_system_java"
-
-  # TODO(timvolodine): once the downstream implementation of GmsBridgeImpl.java
-  # lands make sure to exclude this when "enable_chrome_android_internal" is true.
-  deps += [ ":gms_bridge_upstream_impl_java" ]
 }
 
 generate_jni("jni") {
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/ContentViewRenderView.java b/weblayer/browser/java/org/chromium/weblayer_private/ContentViewRenderView.java
index a5d7e70..8ece480 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/ContentViewRenderView.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/ContentViewRenderView.java
@@ -74,6 +74,32 @@
         void surfaceDestroyed(boolean cacheBackBuffer);
     }
 
+    private final ArrayList<TrackedRunnable> mPendingRunnables = new ArrayList<>();
+
+    // Runnables posted via View.postOnAnimation may not run after the view is detached,
+    // if nothing else causes animation. However a pending runnable may held by a GC root
+    // from the thread itself, and thus can cause leaks. This class here is so ensure that
+    // on destroy, all pending tasks are run immediately so they do not lead to leaks.
+    private abstract class TrackedRunnable implements Runnable {
+        private boolean mHasRun;
+        public TrackedRunnable() {
+            mPendingRunnables.add(this);
+        }
+
+        @Override
+        public final void run() {
+            // View.removeCallbacks is not always reliable, and may run the callback even
+            // after it has been removed.
+            if (mHasRun) return;
+            assert mPendingRunnables.contains(this);
+            mPendingRunnables.remove(this);
+            mHasRun = true;
+            doRun();
+        }
+
+        protected abstract void doRun();
+    }
+
     // Non-static implementation of SurfaceEventListener that forward calls to native Compositor.
     // It is also responsible for updating |mRequested| and |mCurrent|.
     private class SurfaceEventListenerImpl implements SurfaceEventListener {
@@ -122,7 +148,7 @@
 
     // Abstract differences between SurfaceView and TextureView behind this class.
     // Also responsible for holding and calling callbacks.
-    private static class SurfaceData implements SurfaceEventListener {
+    private class SurfaceData implements SurfaceEventListener {
         private class TextureViewWithInvalidate extends TextureView {
             public TextureViewWithInvalidate(Context context) {
                 super(context);
@@ -213,16 +239,19 @@
             }
 
             // This postOnAnimation is to avoid manipulating the view tree inside layout or draw.
-            parent.postOnAnimation(() -> {
-                if (mMarkedForDestroy) return;
-                View view = (mMode == MODE_SURFACE_VIEW) ? mSurfaceView : mTextureView;
-                assert view != null;
-                // Always insert view for new surface below the existing view to avoid artifacts
-                // during surface swaps. Index 0 is the lowest child.
-                mParent.addView(view, 0,
-                        new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
-                                FrameLayout.LayoutParams.MATCH_PARENT));
-                mParent.invalidate();
+            parent.postOnAnimation(new TrackedRunnable() {
+                @Override
+                protected void doRun() {
+                    if (mMarkedForDestroy) return;
+                    View view = (mMode == MODE_SURFACE_VIEW) ? mSurfaceView : mTextureView;
+                    assert view != null;
+                    // Always insert view for new surface below the existing view to avoid artifacts
+                    // during surface swaps. Index 0 is the lowest child.
+                    mParent.addView(view, 0,
+                            new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
+                                    FrameLayout.LayoutParams.MATCH_PARENT));
+                    mParent.invalidate();
+                }
             });
         }
 
@@ -272,34 +301,47 @@
             assert mMarkedForDestroy;
             runCallbacks();
             // This postOnAnimation is to avoid manipulating the view tree inside layout or draw.
-            mParent.postOnAnimation(() -> {
-                if (mMode == MODE_SURFACE_VIEW) {
-                    // Detaching a SurfaceView causes a flicker because the SurfaceView tears down
-                    // the Surface in SurfaceFlinger before removing its hole in the view tree.
-                    // This is a complicated heuristics to avoid this. It first moves the
-                    // SurfaceView behind the new View. Then wait two frames before detaching
-                    // the SurfaceView. Waiting for a single frame still causes flickers on
-                    // high end devices like Pixel 3.
-                    moveChildToBackWithoutDetach(mParent, mSurfaceView);
-                    mParent.postOnAnimation(() -> mParent.postOnAnimation(() -> {
-                        mParent.removeView(mSurfaceView);
-                        mParent.invalidate();
-                        if (mCachedSurfaceNeedsEviction) {
-                            mEvict.run();
-                            mCachedSurfaceNeedsEviction = false;
-                        }
+            mParent.postOnAnimation(new TrackedRunnable() {
+                @Override
+                protected void doRun() {
+                    if (mMode == MODE_SURFACE_VIEW) {
+                        // Detaching a SurfaceView causes a flicker because the SurfaceView tears
+                        // down the Surface in SurfaceFlinger before removing its hole in the view
+                        // tree. This is a complicated heuristics to avoid this. It first moves the
+                        // SurfaceView behind the new View. Then wait two frames before detaching
+                        // the SurfaceView. Waiting for a single frame still causes flickers on
+                        // high end devices like Pixel 3.
+                        moveChildToBackWithoutDetach(mParent, mSurfaceView);
+                        TrackedRunnable inner = new TrackedRunnable() {
+                            @Override
+                            public void doRun() {
+                                mParent.removeView(mSurfaceView);
+                                mParent.invalidate();
+                                if (mCachedSurfaceNeedsEviction) {
+                                    mEvict.run();
+                                    mCachedSurfaceNeedsEviction = false;
+                                }
+                                runCallbackOnNextSurfaceData();
+                            }
+                        };
+                        TrackedRunnable outer = new TrackedRunnable() {
+                            @Override
+                            public void doRun() {
+                                mParent.postOnAnimation(inner);
+                            }
+                        };
+                        mParent.postOnAnimation(outer);
+                    } else if (mMode == MODE_TEXTURE_VIEW) {
+                        mParent.removeView(mTextureView);
                         runCallbackOnNextSurfaceData();
-                    }));
-                } else if (mMode == MODE_TEXTURE_VIEW) {
-                    mParent.removeView(mTextureView);
-                    runCallbackOnNextSurfaceData();
-                } else {
-                    assert false;
+                    } else {
+                        assert false;
+                    }
                 }
             });
         }
 
-        private static void moveChildToBackWithoutDetach(ViewGroup parent, View child) {
+        private void moveChildToBackWithoutDetach(ViewGroup parent, View child) {
             final int numberOfChildren = parent.getChildCount();
             final int childIndex = parent.indexOfChild(child);
             if (childIndex <= 0) return;
@@ -593,6 +635,13 @@
         mCurrent = null;
 
         mWindowAndroid = null;
+
+        while (!mPendingRunnables.isEmpty()) {
+            TrackedRunnable runnable = mPendingRunnables.get(0);
+            removeCallbacks(runnable);
+            runnable.run();
+            assert !mPendingRunnables.contains(runnable);
+        }
         ContentViewRenderViewJni.get().destroy(mNativeContentViewRenderView);
         mNativeContentViewRenderView = 0;
     }
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/NavigationImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/NavigationImpl.java
index e3579fa..79aafa4 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/NavigationImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/NavigationImpl.java
@@ -14,6 +14,7 @@
 import org.chromium.weblayer_private.interfaces.INavigation;
 import org.chromium.weblayer_private.interfaces.INavigationControllerClient;
 
+import java.util.Arrays;
 import java.util.List;
 
 @JNINamespace("weblayer")
@@ -51,7 +52,8 @@
     @Override
     public List<String> getRedirectChain() {
         throwIfNativeDestroyed();
-        return NavigationImplJni.get().getRedirectChain(mNativeNavigationImpl, NavigationImpl.this);
+        return Arrays.asList(NavigationImplJni.get().getRedirectChain(
+                mNativeNavigationImpl, NavigationImpl.this));
     }
 
     @Override
@@ -77,7 +79,7 @@
         void setJavaNavigation(long nativeNavigationImpl, NavigationImpl caller);
         int getState(long nativeNavigationImpl, NavigationImpl caller);
         String getUri(long nativeNavigationImpl, NavigationImpl caller);
-        List<String> getRedirectChain(long nativeNavigationImpl, NavigationImpl caller);
+        String[] getRedirectChain(long nativeNavigationImpl, NavigationImpl caller);
         boolean isSameDocument(long nativeNavigationImpl, NavigationImpl caller);
     }
 }
diff --git a/weblayer/browser/navigation_impl.cc b/weblayer/browser/navigation_impl.cc
index aa9069a..073f043d 100644
--- a/weblayer/browser/navigation_impl.cc
+++ b/weblayer/browser/navigation_impl.cc
@@ -45,7 +45,7 @@
       base::android::ConvertUTF8ToJavaString(env, GetURL().spec()));
 }
 
-ScopedJavaLocalRef<jobject> NavigationImpl::GetRedirectChain(
+ScopedJavaLocalRef<jobjectArray> NavigationImpl::GetRedirectChain(
     JNIEnv* env,
     const base::android::JavaParamRef<jobject>& obj) {
   std::vector<std::string> jni_redirects;
diff --git a/weblayer/browser/navigation_impl.h b/weblayer/browser/navigation_impl.h
index 3ce76982..362c0c4 100644
--- a/weblayer/browser/navigation_impl.h
+++ b/weblayer/browser/navigation_impl.h
@@ -34,7 +34,7 @@
   base::android::ScopedJavaLocalRef<jstring> GetUri(
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& obj);
-  base::android::ScopedJavaLocalRef<jobject> GetRedirectChain(
+  base::android::ScopedJavaLocalRef<jobjectArray> GetRedirectChain(
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& obj);
   bool IsSameDocument(JNIEnv* env,
diff --git a/weblayer/shell/android/BUILD.gn b/weblayer/shell/android/BUILD.gn
index 7028929..fc311d2 100644
--- a/weblayer/shell/android/BUILD.gn
+++ b/weblayer/shell/android/BUILD.gn
@@ -165,6 +165,10 @@
     "//mojo/public/java:system_java",
     "//net/android:net_java",
   ]
+
+  # default upstream safebrowsing related classes
+  deps += [ "//weblayer/browser/java:gms_bridge_upstream_impl_java" ]
+
   apk_name = "WebLayerSupport"
   android_manifest = weblayer_support_manifest
   min_sdk_version = 21
diff --git a/weblayer/shell/android/javatests/src/org/chromium/weblayer/test/NavigationTest.java b/weblayer/shell/android/javatests/src/org/chromium/weblayer/test/NavigationTest.java
index 622c1e6..a03b913 100644
--- a/weblayer/shell/android/javatests/src/org/chromium/weblayer/test/NavigationTest.java
+++ b/weblayer/shell/android/javatests/src/org/chromium/weblayer/test/NavigationTest.java
@@ -28,6 +28,7 @@
 import org.chromium.weblayer.shell.InstrumentationActivity;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.TimeoutException;
@@ -50,15 +51,12 @@
         public static class NavigationCallbackHelper extends CallbackHelper {
             private Uri mUri;
             private boolean mIsSameDocument;
+            private List<Uri> mRedirectChain;
 
-            public void notifyCalled(Uri uri) {
-                mUri = uri;
-                notifyCalled();
-            }
-
-            public void notifyCalled(Uri uri, boolean isSameDocument) {
-                mUri = uri;
-                mIsSameDocument = isSameDocument;
+            public void notifyCalled(Navigation navigation) {
+                mUri = navigation.getUri();
+                mIsSameDocument = navigation.isSameDocument();
+                mRedirectChain = navigation.getRedirectChain();
                 notifyCalled();
             }
 
@@ -73,6 +71,12 @@
                 assertEquals(mUri.toString(), uri);
                 assertEquals(mIsSameDocument, isSameDocument);
             }
+
+            public void assertCalledWith(int currentCallCount, List<Uri> redirectChain)
+                    throws TimeoutException {
+                waitForCallback(currentCallCount);
+                assertEquals(mRedirectChain, redirectChain);
+            }
         }
 
         public static class NavigationCallbackValueRecorder {
@@ -101,8 +105,10 @@
         }
 
         public NavigationCallbackHelper onStartedCallback = new NavigationCallbackHelper();
+        public NavigationCallbackHelper onRedirectedCallback = new NavigationCallbackHelper();
         public NavigationCallbackHelper onReadyToCommitCallback = new NavigationCallbackHelper();
         public NavigationCallbackHelper onCompletedCallback = new NavigationCallbackHelper();
+        public NavigationCallbackHelper onFailedCallback = new NavigationCallbackHelper();
         public NavigationCallbackValueRecorder loadStateChangedCallback =
                 new NavigationCallbackValueRecorder();
         public NavigationCallbackValueRecorder loadProgressChangedCallback =
@@ -111,17 +117,27 @@
 
         @Override
         public void onNavigationStarted(Navigation navigation) {
-            onStartedCallback.notifyCalled(navigation.getUri());
+            onStartedCallback.notifyCalled(navigation);
+        }
+
+        @Override
+        public void onNavigationRedirected(Navigation navigation) {
+            onRedirectedCallback.notifyCalled(navigation);
         }
 
         @Override
         public void onReadyToCommitNavigation(Navigation navigation) {
-            onReadyToCommitCallback.notifyCalled(navigation.getUri());
+            onReadyToCommitCallback.notifyCalled(navigation);
         }
 
         @Override
         public void onNavigationCompleted(Navigation navigation) {
-            onCompletedCallback.notifyCalled(navigation.getUri(), navigation.isSameDocument());
+            onCompletedCallback.notifyCalled(navigation);
+        }
+
+        @Override
+        public void onNavigationFailed(Navigation navigation) {
+            onFailedCallback.notifyCalled(navigation);
         }
 
         @Override
@@ -248,6 +264,83 @@
                 curCompletedCount, "data:text,foo#bar", true);
     }
 
+    @Test
+    @SmallTest
+    public void testReload() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        setNavigationCallback(activity);
+
+        navigateAndWaitForCompletion(
+                URL1, () -> { activity.getTab().getNavigationController().reload(); });
+    }
+
+    @Test
+    @SmallTest
+    public void testStop() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        setNavigationCallback(activity);
+
+        int curFailedCount = mCallback.onFailedCallback.getCallCount();
+
+        runOnUiThreadBlocking(() -> {
+            NavigationController navigationController = activity.getTab().getNavigationController();
+            navigationController.registerNavigationCallback(new NavigationCallback() {
+                @Override
+                public void onNavigationStarted(Navigation navigation) {
+                    navigationController.stop();
+                }
+            });
+            navigationController.navigate(Uri.parse(URL2));
+        });
+
+        mCallback.onFailedCallback.assertCalledWith(curFailedCount, URL2);
+    }
+
+    @Test
+    @SmallTest
+    public void testRedirect() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        setNavigationCallback(activity);
+
+        int curRedirectedCount = mCallback.onRedirectedCallback.getCallCount();
+
+        String finalUrl = mActivityTestRule.getTestServer().getURL("/echo");
+        String url = mActivityTestRule.getTestServer().getURL("/server-redirect?" + finalUrl);
+        navigateAndWaitForCompletion(finalUrl,
+                () -> { activity.getTab().getNavigationController().navigate(Uri.parse(url)); });
+
+        mCallback.onRedirectedCallback.assertCalledWith(
+                curRedirectedCount, Arrays.asList(Uri.parse(url), Uri.parse(finalUrl)));
+    }
+
+    @Test
+    @SmallTest
+    public void testNavigationList() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        setNavigationCallback(activity);
+
+        mActivityTestRule.navigateAndWait(URL2);
+        mActivityTestRule.navigateAndWait(URL3);
+
+        NavigationController navigationController =
+                runOnUiThreadBlocking(() -> activity.getTab().getNavigationController());
+
+        runOnUiThreadBlocking(() -> {
+            assertEquals(3, navigationController.getNavigationListSize());
+            assertEquals(2, navigationController.getNavigationListCurrentIndex());
+            assertEquals(URL1, navigationController.getNavigationEntryDisplayUri(0).toString());
+            assertEquals(URL2, navigationController.getNavigationEntryDisplayUri(1).toString());
+            assertEquals(URL3, navigationController.getNavigationEntryDisplayUri(2).toString());
+        });
+
+        navigateAndWaitForCompletion(URL2, () -> { navigationController.goBack(); });
+
+        runOnUiThreadBlocking(() -> {
+            assertEquals(3, navigationController.getNavigationListSize());
+            assertEquals(1, navigationController.getNavigationListCurrentIndex());
+        });
+    }
+
     private void setNavigationCallback(InstrumentationActivity activity) {
         runOnUiThreadBlocking(
                 ()
diff --git a/weblayer/shell/android/javatests/src/org/chromium/weblayer/test/SmokeTest.java b/weblayer/shell/android/javatests/src/org/chromium/weblayer/test/SmokeTest.java
index 6d532857c..f7a0d56 100644
--- a/weblayer/shell/android/javatests/src/org/chromium/weblayer/test/SmokeTest.java
+++ b/weblayer/shell/android/javatests/src/org/chromium/weblayer/test/SmokeTest.java
@@ -12,9 +12,14 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.content_public.browser.test.util.Criteria;
+import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.weblayer.shell.InstrumentationActivity;
 
+import java.lang.ref.PhantomReference;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
 import java.util.concurrent.CountDownLatch;
 
 @RunWith(BaseJUnit4ClassRunner.class)
@@ -48,4 +53,34 @@
         }
         mActivityTestRule.navigateAndWait(url);
     }
+
+    @Test
+    @SmallTest
+    public void testActivityShouldNotLeak() {
+        ReferenceQueue<InstrumentationActivity> referenceQueue = new ReferenceQueue<>();
+        PhantomReference<InstrumentationActivity> reference;
+        {
+            InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+            mActivityTestRule.recreateActivity();
+            boolean destroyed =
+                    TestThreadUtils.runOnUiThreadBlockingNoException(() -> activity.isDestroyed());
+            Assert.assertTrue(destroyed);
+
+            reference = new PhantomReference<>(activity, referenceQueue);
+        }
+
+        Runtime.getRuntime().gc();
+        CriteriaHelper.pollInstrumentationThread(new Criteria() {
+            @Override
+            public boolean isSatisfied() {
+                Reference enqueuedReference = referenceQueue.poll();
+                if (enqueuedReference == null) {
+                    Runtime.getRuntime().gc();
+                    return false;
+                }
+                Assert.assertEquals(reference, enqueuedReference);
+                return true;
+            }
+        });
+    }
 }
diff --git a/weblayer/test/BUILD.gn b/weblayer/test/BUILD.gn
index db48205..815e774 100644
--- a/weblayer/test/BUILD.gn
+++ b/weblayer/test/BUILD.gn
@@ -122,7 +122,11 @@
 
       # Needed for WebLayerImpl.
       "//weblayer/browser/java",
+
+      # default upstream safebrowsing related classes
+      "//weblayer/browser/java:gms_bridge_upstream_impl_java",
     ]
+
     android_manifest =
         "${target_gen_dir}/weblayer_browsertests_manifest/AndroidManifest.xml"
     android_manifest_dep = ":weblayer_browsertests_manifest"